mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
docs: improve with examples
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<UContainer class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
|
||||
<div class="lg:col-span-8">
|
||||
<div class="lg:col-span-8 min-h-0 flex flex-col">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</UContainer>
|
||||
@@ -45,7 +45,7 @@ useHead({
|
||||
lang: 'en'
|
||||
},
|
||||
bodyAttrs: {
|
||||
class: 'antialiased font-sans text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-900'
|
||||
class: 'antialiased font-sans text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
46
docs/components/ColorPicker.vue
Normal file
46
docs/components/ColorPicker.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-2">
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerButton v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryColors = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryColors.value.find(option => option.value === appConfig.ui.primary)
|
||||
},
|
||||
set (option) {
|
||||
appConfig.ui.primary = option.value
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.primary)
|
||||
}
|
||||
})
|
||||
|
||||
const grayColors = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get () {
|
||||
return grayColors.value.find(option => option.value === appConfig.ui.gray)
|
||||
},
|
||||
set (option) {
|
||||
appConfig.ui.gray = option.value
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-gray', appConfig.ui.gray)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
25
docs/components/ColorPickerButton.vue
Normal file
25
docs/components/ColorPickerButton.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<UTooltip :text="color.value" class="capitalize" :open-delay="500">
|
||||
<UButton
|
||||
color="gray"
|
||||
square
|
||||
:ui="{
|
||||
color: {
|
||||
gray: {
|
||||
solid: 'bg-gray-100 dark:bg-gray-800',
|
||||
ghost: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'
|
||||
}
|
||||
}
|
||||
}"
|
||||
:variant="color.value === selected.value ? 'solid' : 'ghost'"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
>
|
||||
<span class="inline-block w-3 h-3 rounded-full" :style="{ backgroundColor: color.hex }" />
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ color: { value: string, hex: string }, selected: { value: string} }>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
76
docs/components/DatePicker.vue
Normal file
76
docs/components/DatePicker.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'close'])
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed(() => colorMode.value === 'dark')
|
||||
|
||||
const date = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:model-value', value)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = [{
|
||||
key: 'today',
|
||||
highlight: {
|
||||
color: 'blue',
|
||||
fillMode: 'outline',
|
||||
class: '!bg-gray-100 dark:!bg-gray-800'
|
||||
},
|
||||
dates: new Date()
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker
|
||||
v-model="date"
|
||||
transparent
|
||||
borderless
|
||||
:attributes="attrs"
|
||||
:is-dark="isDark"
|
||||
title-position="left"
|
||||
trim-weeks
|
||||
:first-day-of-week="2"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--vc-gray-50: rgb(var(--color-gray-50));
|
||||
--vc-gray-100: rgb(var(--color-gray-100));
|
||||
--vc-gray-200: rgb(var(--color-gray-200));
|
||||
--vc-gray-300: rgb(var(--color-gray-300));
|
||||
--vc-gray-400: rgb(var(--color-gray-400));
|
||||
--vc-gray-500: rgb(var(--color-gray-500));
|
||||
--vc-gray-600: rgb(var(--color-gray-600));
|
||||
--vc-gray-700: rgb(var(--color-gray-700));
|
||||
--vc-gray-800: rgb(var(--color-gray-800));
|
||||
--vc-gray-900: rgb(var(--color-gray-900));
|
||||
}
|
||||
|
||||
.vc-blue {
|
||||
--vc-accent-50: rgb(var(--color-primary-50));
|
||||
--vc-accent-100: rgb(var(--color-primary-100));
|
||||
--vc-accent-200: rgb(var(--color-primary-200));
|
||||
--vc-accent-300: rgb(var(--color-primary-300));
|
||||
--vc-accent-400: rgb(var(--color-primary-400));
|
||||
--vc-accent-500: rgb(var(--color-primary-500));
|
||||
--vc-accent-600: rgb(var(--color-primary-600));
|
||||
--vc-accent-700: rgb(var(--color-primary-700));
|
||||
--vc-accent-800: rgb(var(--color-primary-800));
|
||||
--vc-accent-900: rgb(var(--color-primary-900));
|
||||
}
|
||||
</style>
|
||||
@@ -1,86 +1,16 @@
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75">
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75">
|
||||
<UContainer>
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center -mr-1.5 gap-1.5">
|
||||
<div class="hidden lg:block">
|
||||
<ThemeSelect />
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
@click="openDocsSearch"
|
||||
/>
|
||||
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon' : 'i-heroicons-sun'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-bars-3-20-solid"
|
||||
@click="isDialogOpen = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</UContainer>
|
||||
|
||||
<TransitionRoot :show="isDialogOpen" as="template">
|
||||
<Dialog as="div" @close="isDialogOpen = false">
|
||||
<DialogPanel class="fixed inset-0 z-50 overflow-y-auto bg-white dark:bg-gray-900 lg:hidden">
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex -mr-1.5">
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
@click="isDialogOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-200 dark:border-gray-800 bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<HeaderLinks v-model="isDialogOpen" :links="links" />
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 py-4 sm:py-6">
|
||||
<ThemeSelect class="mb-4 sm:mb-6 w-full" />
|
||||
|
||||
<DocsAsideLinks @click="isDialogOpen = false" />
|
||||
</div>
|
||||
</DialogPanel>
|
||||
@@ -92,25 +22,11 @@
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogPanel, TransitionRoot } from '@headlessui/vue'
|
||||
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDialogOpen = ref(false)
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
|
||||
function openDocsSearch () {
|
||||
isDialogOpen.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
isSearchModalOpen.value = true
|
||||
}, 100)
|
||||
}
|
||||
const links = [
|
||||
{ label: 'Documentation', to: '/getting-started' },
|
||||
{ label: 'Components', to: '/elements/avatar' },
|
||||
{ label: 'Examples', to: '/examples' }
|
||||
]
|
||||
</script>
|
||||
|
||||
64
docs/components/HeaderLinks.vue
Normal file
64
docs/components/HeaderLinks.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-3 h-16">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
<span class="hidden sm:block">NuxtLabs</span><span class="sm:text-primary-500 dark:sm:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end flex-1 -mr-1.5 gap-3">
|
||||
<DocsSearchButton class="ml-1.5 flex-1 lg:flex-none lg:w-48" />
|
||||
|
||||
<div class="flex items-center lg:gap-1.5">
|
||||
<UPopover>
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<ColorPicker />
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<ColorModeButton />
|
||||
|
||||
<UButton
|
||||
to="https://twitter.com/nuxtlabs"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-twitter"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
:icon="isDialogOpen ? 'i-heroicons-x-mark-20-solid' : 'i-heroicons-bars-3-20-solid'"
|
||||
@click="isDialogOpen = !isDialogOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ modelValue: boolean, links: { to: string, label: string }[] }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const isDialogOpen = useVModel(props, 'modelValue', emit)
|
||||
</script>
|
||||
@@ -1,128 +0,0 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div class="inline-flex shadow-sm rounded-md">
|
||||
<USelectMenu
|
||||
v-model="primary"
|
||||
name="primary"
|
||||
class="!rounded-r-none !shadow-none focus:z-[1]"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
:options="primaryOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${primary.hex}`}" />
|
||||
|
||||
{{ primary.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
<USelectMenu
|
||||
v-model="gray"
|
||||
name="gray"
|
||||
class="!rounded-l-none !shadow-none"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]', wrapper: '-ml-px' }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
:options="grayOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${gray.hex}`}" />
|
||||
|
||||
{{ gray.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const primaryCookie = useCookie('primary', { path: '/', default: () => appConfig.ui.primary })
|
||||
const grayCookie = useCookie('gray', { path: '/', default: () => appConfig.ui.gray })
|
||||
|
||||
watch(primaryCookie, (primary) => {
|
||||
appConfig.ui.primary = primary
|
||||
}, { immediate: true })
|
||||
|
||||
watch(grayCookie, (gray) => {
|
||||
appConfig.ui.gray = gray
|
||||
}, { immediate: true })
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryOptions = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryOptions.value.find(option => option.value === primaryCookie.value) || primaryOptions.value.find(option => option.value === 'green')
|
||||
},
|
||||
set (option) {
|
||||
primaryCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
const grayOptions = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get () {
|
||||
return grayOptions.value.find(option => option.value === grayCookie.value) || grayOptions.value.find(option => option.value === 'cool')
|
||||
},
|
||||
set (option) {
|
||||
grayCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
// Hack for SSG
|
||||
const hexToRgb = (hex) => {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
|
||||
hex = hex.replace(shorthandRegex, function (_, r, g, b) {
|
||||
return r + r + g + g + b + b
|
||||
})
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
|
||||
: null
|
||||
}
|
||||
const root = computed(() => {
|
||||
return `:root {
|
||||
${Object.entries(colors[primary.value.value] || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
${Object.entries(colors[gray.value.value] || colors.cool).map(([key, value]) => `--color-gray-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
}`
|
||||
})
|
||||
if (process.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
}, { immediate: true })
|
||||
}
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('nuxt-ui-root')) {
|
||||
document.querySelector('style#nuxt-ui-colors').innerHTML = localStorage.getItem('nuxt-ui-root')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
28
docs/components/content/ColorModeButton.vue
Normal file
28
docs/components/content/ColorModeButton.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
16
docs/components/content/examples/DatePickerExample.vue
Normal file
16
docs/components/content/examples/DatePickerExample.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const date = ref(new Date())
|
||||
|
||||
const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' })
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="label" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
15
docs/components/content/prose/ProseH4.vue
Normal file
15
docs/components/content/prose/ProseH4.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 :id="id" class="scroll-mt-[145px] lg:scroll-mt-[96px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h3>
|
||||
</template>
|
||||
@@ -1,13 +1,22 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Introduction',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
label: 'Theming',
|
||||
to: '/getting-started/theming'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
label: 'Shortcuts',
|
||||
to: '/getting-started/shortcuts'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
to: '/getting-started/roadmap'
|
||||
}]
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
<template>
|
||||
<aside class="hidden pb-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-64px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<aside class="hidden py-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-65px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<div class="relative">
|
||||
<div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<!-- <div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<div class="h-8 bg-white dark:bg-gray-900" />
|
||||
<div class="bg-white dark:bg-gray-900 relative pointer-events-auto">
|
||||
<UButton
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
class="w-full"
|
||||
color="gray"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
Search
|
||||
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</UButton>
|
||||
<DocsSearchButton class="w-full" />
|
||||
</div>
|
||||
<div class="h-8 bg-gradient-to-b from-white dark:from-gray-900" />
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<DocsAsideLinks />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<div v-for="(group, index) in navigation" :key="index" class="space-y-3">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-200">
|
||||
<span class="truncate">{{ group.title }}</span>
|
||||
</div>
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-200 truncate leading-6">
|
||||
{{ group.title }}
|
||||
</p>
|
||||
|
||||
<UVerticalNavigation
|
||||
:links="mapContentLinks(group.children)"
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<footer class="flex items-center justify-end gap-1.5">
|
||||
<footer class="flex items-center justify-between gap-1.5">
|
||||
<div class="flex items-baseline gap-1.5 text-sm text-center text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-primary-500 w-14 h-auto dark:text-primary-400" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<NuxtLink to="https://github.com/nuxtlabs/ui/releases" target="_blank">
|
||||
<UBadge label="v2.4.0" />
|
||||
</NuxtLink>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="page.description" class="mt-4 text-lg">
|
||||
<p v-if="page.description" class="mt-4 text-lg text-gray-500 dark:text-gray-400">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/25 group">
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50 group">
|
||||
<div v-if="icon" class="inline-flex items-center rounded-full p-1.5 bg-gray-50 dark:bg-gray-800 group-hover:bg-primary-50 dark:group-hover:bg-primary-400/10 ring-1 ring-gray-300 dark:ring-gray-700 mb-4 group-hover:ring-primary-500/50 dark:group-hover:ring-primary-400/50">
|
||||
<UIcon :name="icon" class="w-5 h-5 text-gray-900 dark:text-gray-100 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
command-attribute="title"
|
||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'ghost', size: 'sm', class: '-mr-1.5' }"
|
||||
:ui="{ input: { height: 'h-16 sm:h-12', icon: { size: 'h-5 w-5', padding: 'pl-11' } } }"
|
||||
:fuse="{
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
resultLimit: 10
|
||||
|
||||
33
docs/components/docs/DocsSearchButton.vue
Normal file
33
docs/components/docs/DocsSearchButton.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<UButton
|
||||
color="white"
|
||||
variant="outline"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
label="Search..."
|
||||
truncate
|
||||
:ui="{
|
||||
color: {
|
||||
white: {
|
||||
outline: 'ring-1 ring-inset ring-gray-200 dark:ring-gray-800 hover:ring-gray-300 dark:hover:ring-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
|
||||
}
|
||||
}
|
||||
}"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
<template #trailing>
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1 flex-shrink-0">
|
||||
<UKbd class="!text-inherit">
|
||||
{{ metaSymbol }}
|
||||
</UKbd>
|
||||
<UKbd class="!text-inherit">
|
||||
K
|
||||
</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
@@ -20,7 +20,7 @@ export default defineAppConfig({
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors in the navbar and see the documentation change live.
|
||||
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
|
||||
::
|
||||
|
||||
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
|
||||
@@ -73,6 +73,10 @@ All the components are styled with dark mode in mind.
|
||||
|
||||
Thanks to [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) class strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
|
||||
|
||||
::alert{icon="i-heroicons-puzzle-piece"}
|
||||
Learn how to build a color mode button in the [Examples](/getting-started/examples#color-mode-button) page.
|
||||
::
|
||||
|
||||
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
@@ -87,34 +91,6 @@ export default defineNuxtConfig({
|
||||
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
|
||||
::
|
||||
|
||||
You can easily build a dark mode switcher by using the `useColorMode` composable from `@nuxtjs/color-mode`.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon' : 'i-heroicons-sun'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
Components are styled with Tailwind CSS but classes are all defined in the default [app.config.ts](https://github.com/nuxtlabs/ui/blob/dev/src/runtime/app.config.ts) file. You can override them in your `app.config.ts`.
|
||||
|
||||
251
docs/content/1.getting-started/5.examples.md
Normal file
251
docs/content/1.getting-started/5.examples.md
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
title: Examples
|
||||
description: Discover some real-life examples of components you can build.
|
||||
---
|
||||
|
||||
::alert{icon="i-heroicons-wrench-screwdriver"}
|
||||
If you have any ideas of examples you'd like to see, please comment on [this issue](https://github.com/nuxtlabs/ui/issues/297).
|
||||
::
|
||||
|
||||
## Components
|
||||
|
||||
You can mix and match components to build your own UI.
|
||||
|
||||
### ColorModeButton
|
||||
|
||||
You can easily build a color mode button by using the `useColorMode` composable from `@nuxtjs/color-mode`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:color-mode-button
|
||||
|
||||
#code
|
||||
```vue [components/ColorModeButton.vue]
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### DatePicker
|
||||
|
||||
Here is an example of a date picker component built with [v-calendar](https://github.com/nathanreyes/v-calendar).
|
||||
|
||||
```vue [components/DatePicker.vue]
|
||||
<script setup lang="ts">
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'close'])
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed(() => colorMode.value === 'dark')
|
||||
|
||||
const date = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:model-value', value)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = [{
|
||||
key: 'today',
|
||||
highlight: {
|
||||
color: 'blue',
|
||||
fillMode: 'outline',
|
||||
class: '!bg-gray-100 dark:!bg-gray-800'
|
||||
},
|
||||
dates: new Date()
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker
|
||||
v-model="date"
|
||||
transparent
|
||||
borderless
|
||||
:attributes="attrs"
|
||||
:is-dark="isDark"
|
||||
title-position="left"
|
||||
trim-weeks
|
||||
:first-day-of-week="2"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
You can use it inside a [Popover](/overlays/popover) component to display it when clicking on a [Button](/elements/button).
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:date-picker-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const date = ref(new Date())
|
||||
|
||||
const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' })
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="label" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Theming
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the components.
|
||||
|
||||
### CommandPalette
|
||||
|
||||
Here is some examples of what you can do with the [CommandPalette](/navigation/command-palette).
|
||||
|
||||
#### Algolia
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
#### Raycast
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
### VerticalNavigation
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Introduction',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
to: '/getting-started/theming'
|
||||
}, {
|
||||
label: 'Shortcuts',
|
||||
to: '/getting-started/shortcuts'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
to: '/getting-started/roadmap'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-theme-rounded
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:ui="{
|
||||
wrapper: 'flex items-center gap-1',
|
||||
rounded: 'rounded-full min-w-[32px] justify-center'
|
||||
}"
|
||||
:prev-button="null"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
color: 'primary',
|
||||
variant: 'outline'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
@@ -39,46 +39,6 @@ const links = [{
|
||||
```
|
||||
::
|
||||
|
||||
## Theme
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is an example of what you can do.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -323,40 +323,6 @@ const groups = computed(() => {
|
||||
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behavior by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
|
||||
### Algolia
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
### Raycast
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `empty-state` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full"}
|
||||
|
||||
@@ -111,39 +111,6 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
## Theme
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is an example of what you can do.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-theme-rounded
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:ui="{
|
||||
wrapper: 'flex items-center gap-1',
|
||||
rounded: 'rounded-full min-w-[32px] justify-center'
|
||||
}"
|
||||
:prev-button="null"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
color: 'primary',
|
||||
variant: 'outline'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `prev` / `next`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<div v-if="page" class="grid lg:grid-cols-10 lg:gap-8">
|
||||
<div class="pt-8 pb-16" :class="page.body?.toc ? 'lg:col-span-8' : 'lg:col-span-10'">
|
||||
<DocsPageHeader :page="page" />
|
||||
|
||||
@@ -16,6 +16,25 @@
|
||||
|
||||
<DocsToc v-if="page.body?.toc" :toc="page.body.toc" class="lg:col-span-2" />
|
||||
</div>
|
||||
<div v-else class="flex-1 flex flex-col items-center justify-center">
|
||||
<div class="text-center">
|
||||
<p class="text-base font-semibold text-primary-500 dark:text-primary-400">
|
||||
404
|
||||
</p>
|
||||
<h1 class="mt-2 text-4xl tracking-tight font-extrabold u-text-gray-900 sm:text-5xl">
|
||||
Page not found
|
||||
</h1>
|
||||
<p class="mt-2 text-base u-text-gray-500">
|
||||
Sorry, we couldn’t find the page you’re looking for.
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<NuxtLink to="/" class="text-base font-medium text-primary-500 dark:text-primary-400 hover:u-text-gray-900">
|
||||
Go back home
|
||||
<span aria-hidden="true"> →</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
42
docs/plugins/ui.ts
Normal file
42
docs/plugins/ui.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { hexToRgb } from "../../src/runtime/utils"
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
enforce: 'post',
|
||||
setup () {
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const root = computed(() => {
|
||||
const primary = colors[appConfig.ui.primary]
|
||||
const gray = colors[appConfig.ui.gray]
|
||||
|
||||
return `:root {
|
||||
${Object.entries(primary || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
${Object.entries(gray || colors.cool).map(([key, value]) => `--color-gray-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
}`
|
||||
})
|
||||
|
||||
if (process.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
})
|
||||
|
||||
appConfig.ui.primary = window.localStorage.getItem('nuxt-ui-primary') || appConfig.ui.primary
|
||||
appConfig.ui.gray = window.localStorage.getItem('nuxt-ui-gray') || appConfig.ui.gray
|
||||
}
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('nuxt-ui-root')) {
|
||||
document.querySelector('style#nuxt-ui-colors').innerHTML = localStorage.getItem('nuxt-ui-root')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -65,6 +65,7 @@
|
||||
"nuxt-lodash": "^2.4.1",
|
||||
"release-it": "^15.11.0",
|
||||
"unbuild": "^1.2.1",
|
||||
"v-calendar": "^3.0.3",
|
||||
"vue-tsc": "1.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
58
pnpm-lock.yaml
generated
58
pnpm-lock.yaml
generated
@@ -108,6 +108,9 @@ devDependencies:
|
||||
unbuild:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
v-calendar:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(@popperjs/core@2.11.8)(vue@3.3.4)
|
||||
vue-tsc:
|
||||
specifier: 1.6.3
|
||||
version: 1.6.3(typescript@5.1.3)
|
||||
@@ -388,6 +391,13 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/runtime@7.22.5:
|
||||
resolution: {integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.11
|
||||
dev: true
|
||||
|
||||
/@babel/standalone@7.22.5:
|
||||
resolution: {integrity: sha512-6Lwhzral4YDEbIM3dBC8/w0BMDvOosGBGaJWSORLkerx8byawkmwwzXKUB0jGlI1Zp90+cK2uyTl62UPtLbUjQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1527,7 +1537,6 @@ packages:
|
||||
|
||||
/@popperjs/core@2.11.8:
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
dev: false
|
||||
|
||||
/@release-it/conventional-changelog@5.1.1(release-it@15.11.0):
|
||||
resolution: {integrity: sha512-QtbDBe36dQfzexAfDYrbLPvd5Cb5bMWmLcjcGhCOWBss7fe1/gCjoxDULVz+7N7G5Nu2UMeBwHcUp/w8RDh5VQ==}
|
||||
@@ -1873,6 +1882,10 @@ packages:
|
||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||
dev: true
|
||||
|
||||
/@types/resize-observer-browser@0.1.7:
|
||||
resolution: {integrity: sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==}
|
||||
dev: true
|
||||
|
||||
/@types/resolve@1.20.2:
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
dev: true
|
||||
@@ -3843,6 +3856,21 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/date-fns-tz@1.3.8(date-fns@2.30.0):
|
||||
resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==}
|
||||
peerDependencies:
|
||||
date-fns: '>=2.0.0'
|
||||
dependencies:
|
||||
date-fns: 2.30.0
|
||||
dev: true
|
||||
|
||||
/date-fns@2.30.0:
|
||||
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
|
||||
engines: {node: '>=0.11'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.5
|
||||
dev: true
|
||||
|
||||
/dateformat@3.0.3:
|
||||
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
|
||||
dev: true
|
||||
@@ -9047,6 +9075,10 @@ packages:
|
||||
redis-errors: 1.2.0
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime@0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
dev: true
|
||||
|
||||
/regexp.prototype.flags@1.5.0:
|
||||
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -10688,6 +10720,22 @@ packages:
|
||||
sade: 1.8.1
|
||||
dev: true
|
||||
|
||||
/v-calendar@3.0.3(@popperjs/core@2.11.8)(vue@3.3.4):
|
||||
resolution: {integrity: sha512-Skpp/nMoFqFadm94aWj0oOfazoux5T5Ug3/pbRbdolkoDrnVcL7Ronw1/SGFRUPGOwnLdYwhKPhrhSE1segW6w==}
|
||||
peerDependencies:
|
||||
'@popperjs/core': ^2.0.0
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/lodash': 4.14.195
|
||||
'@types/resize-observer-browser': 0.1.7
|
||||
date-fns: 2.30.0
|
||||
date-fns-tz: 1.3.8(date-fns@2.30.0)
|
||||
lodash: 4.17.21
|
||||
vue: 3.3.4
|
||||
vue-screen-utils: 1.0.0-beta.13(vue@3.3.4)
|
||||
dev: true
|
||||
|
||||
/validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
dependencies:
|
||||
@@ -10992,6 +11040,14 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: true
|
||||
|
||||
/vue-screen-utils@1.0.0-beta.13(vue@3.3.4):
|
||||
resolution: {integrity: sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
vue: 3.3.4
|
||||
dev: true
|
||||
|
||||
/vue-template-compiler@2.7.14:
|
||||
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user