@nuxt/ui 3.0.0-alpha.5 → 3.0.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,7 @@ export interface AlertProps {
31
31
  actions?: ButtonProps[]
32
32
  /**
33
33
  * Display a close button to dismiss the alert.
34
- * `{ size: 'md', color: 'gray', variant: 'link' }`{lang="ts-type"}
34
+ * `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
35
35
  * @emits `close`
36
36
  * @defaultValue false
37
37
  */
@@ -117,7 +117,7 @@ const ui = computed(() => alert({
117
117
  v-if="close"
118
118
  :icon="closeIcon || appConfig.ui.icons.close"
119
119
  size="md"
120
- color="gray"
120
+ color="neutral"
121
121
  variant="link"
122
122
  aria-label="Close"
123
123
  v-bind="typeof close === 'object' ? close : undefined"
@@ -5,7 +5,7 @@ import type { AppConfig } from '@nuxt/schema'
5
5
  import _appConfig from '#build/app.config'
6
6
  import theme from '#build/ui/breadcrumb'
7
7
  import type { AvatarProps, LinkProps } from '../types'
8
- import type { DynamicSlots } from '../types/utils'
8
+ import type { DynamicSlots, PartialString } from '../types/utils'
9
9
 
10
10
  const appConfig = _appConfig as AppConfig & { ui: { breadcrumb: Partial<typeof theme> } }
11
11
 
@@ -31,7 +31,7 @@ export interface BreadcrumbProps<T> {
31
31
  */
32
32
  separatorIcon?: string
33
33
  class?: any
34
- ui?: Partial<typeof breadcrumb.slots>
34
+ ui?: PartialString<typeof breadcrumb.slots>
35
35
  }
36
36
 
37
37
  type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
@@ -0,0 +1,306 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
2
+ <script lang="ts">
3
+ import { tv, type VariantProps } from 'tailwind-variants'
4
+ import type { AppConfig } from '@nuxt/schema'
5
+ import type { EmblaCarouselType, EmblaOptionsType, EmblaPluginType } from 'embla-carousel'
6
+ import type { AutoplayOptionsType } from 'embla-carousel-autoplay'
7
+ import type { AutoScrollOptionsType } from 'embla-carousel-auto-scroll'
8
+ import type { AutoHeightOptionsType } from 'embla-carousel-auto-height'
9
+ import type { ClassNamesOptionsType } from 'embla-carousel-class-names'
10
+ import type { FadeOptionsType } from 'embla-carousel-fade'
11
+ import type { WheelGesturesPluginOptions } from 'embla-carousel-wheel-gestures'
12
+ import _appConfig from '#build/app.config'
13
+ import theme from '#build/ui/carousel'
14
+ import type { ButtonProps } from '../types'
15
+ import type { AcceptableValue, PartialString } from '../types/utils'
16
+
17
+ const appConfig = _appConfig as AppConfig & { ui: { carousel: Partial<typeof theme> } }
18
+
19
+ const carousel = tv({ extend: tv(theme), ...(appConfig.ui?.carousel || {}) })
20
+
21
+ type CarouselVariants = VariantProps<typeof carousel>
22
+
23
+ export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'container' | 'slides' | 'direction'> {
24
+ /**
25
+ * Configure the prev button when arrows are enabled.
26
+ * @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
27
+ */
28
+ prev?: ButtonProps
29
+ /**
30
+ * The icon displayed in the prev button.
31
+ * @defaultValue appConfig.ui.icons.arrowLeft
32
+ */
33
+ prevIcon?: string
34
+ /**
35
+ * Configure the next button when arrows are enabled.
36
+ * @defaultValue { size: 'md', color: 'neutral', variant: 'link' }
37
+ */
38
+ next?: ButtonProps
39
+ /**
40
+ * The icon displayed in the next button.
41
+ * @defaultValue appConfig.ui.icons.arrowRight
42
+ */
43
+ nextIcon?: string
44
+ /**
45
+ * Display prev and next buttons to scroll the carousel.
46
+ * @defaultValue false
47
+ */
48
+ arrows?: boolean
49
+ /**
50
+ * Display dots to scroll to a specific slide.
51
+ * @defaultValue false
52
+ */
53
+ dots?: boolean
54
+ orientation?: CarouselVariants['orientation']
55
+ items?: T[]
56
+ /**
57
+ * Enable Autoplay plugin
58
+ * @see https://www.embla-carousel.com/plugins/autoplay/
59
+ */
60
+ autoplay?: boolean | AutoplayOptionsType
61
+ /**
62
+ * Enable Auto Scroll plugin
63
+ * @see https://www.embla-carousel.com/plugins/auto-scroll/
64
+ */
65
+ autoScroll?: boolean | AutoScrollOptionsType
66
+ /**
67
+ * Enable Auto Height plugin
68
+ * @see https://www.embla-carousel.com/plugins/auto-height/
69
+ */
70
+ autoHeight?: boolean | AutoHeightOptionsType
71
+ /**
72
+ * Enable Class Names plugin
73
+ * @see https://www.embla-carousel.com/plugins/class-names/
74
+ */
75
+ classNames?: boolean | ClassNamesOptionsType
76
+ /**
77
+ * Enable Fade plugin
78
+ * @see https://www.embla-carousel.com/plugins/fade/
79
+ */
80
+ fade?: boolean | FadeOptionsType
81
+ /**
82
+ * Enable Wheel Gestures plugin
83
+ * @see https://www.embla-carousel.com/plugins/wheel-gestures/
84
+ */
85
+ wheelGestures?: boolean | WheelGesturesPluginOptions
86
+ class?: any
87
+ ui?: PartialString<typeof carousel.slots>
88
+ }
89
+
90
+ export type CarouselSlots<T> = {
91
+ default(props: { item: T, index: number }): any
92
+ }
93
+
94
+ </script>
95
+
96
+ <script setup lang="ts" generic="T extends AcceptableValue">
97
+ import { computed, ref, watch, onMounted } from 'vue'
98
+ import useEmblaCarousel from 'embla-carousel-vue'
99
+ import { useForwardProps } from 'radix-vue'
100
+ import { reactivePick, computedAsync } from '@vueuse/core'
101
+ import { useAppConfig } from '#imports'
102
+ import UButton from './Button.vue'
103
+
104
+ const props = withDefaults(defineProps<CarouselProps<T>>(), {
105
+ orientation: 'horizontal',
106
+ arrows: false,
107
+ dots: false,
108
+ // Embla Options
109
+ active: true,
110
+ align: 'center',
111
+ breakpoints: () => ({}),
112
+ containScroll: 'trimSnaps',
113
+ dragFree: false,
114
+ dragThreshold: 10,
115
+ duration: 25,
116
+ inViewThreshold: 0,
117
+ loop: false,
118
+ skipSnaps: false,
119
+ slidesToScroll: 1,
120
+ startIndex: 0,
121
+ watchDrag: true,
122
+ watchResize: true,
123
+ watchSlides: true,
124
+ watchFocus: true,
125
+ // Embla Plugins
126
+ autoplay: false,
127
+ autoScroll: false,
128
+ autoHeight: false,
129
+ classNames: false,
130
+ fade: false,
131
+ wheelGestures: false
132
+ })
133
+ defineSlots<CarouselSlots<T>>()
134
+
135
+ const appConfig = useAppConfig()
136
+ const rootProps = useForwardProps(reactivePick(props, 'active', 'align', 'breakpoints', 'containScroll', 'dragFree', 'dragThreshold', 'duration', 'inViewThreshold', 'loop', 'skipSnaps', 'slidesToScroll', 'startIndex', 'watchDrag', 'watchResize', 'watchSlides', 'watchFocus'))
137
+
138
+ const ui = computed(() => carousel({
139
+ orientation: props.orientation
140
+ }))
141
+
142
+ const options = computed<EmblaOptionsType>(() => ({
143
+ ...(props.fade ? { align: 'center', containScroll: false } : {}),
144
+ ...rootProps.value,
145
+ axis: props.orientation === 'horizontal' ? 'x' : 'y',
146
+ // TODO: Get from ConfigProvider
147
+ direction: 'ltr'
148
+ }))
149
+
150
+ const plugins = computedAsync<EmblaPluginType[]>(async () => {
151
+ const plugins = []
152
+
153
+ if (props.autoplay) {
154
+ const AutoplayPlugin = await import('embla-carousel-autoplay').then(r => r.default)
155
+ plugins.push(AutoplayPlugin(typeof props.autoplay === 'boolean' ? {} : props.autoplay))
156
+ }
157
+
158
+ if (props.autoScroll) {
159
+ const AutoScrollPlugin = await import('embla-carousel-auto-scroll').then(r => r.default)
160
+ plugins.push(AutoScrollPlugin(typeof props.autoScroll === 'boolean' ? {} : props.autoScroll))
161
+ }
162
+
163
+ if (props.autoHeight) {
164
+ const AutoHeightPlugin = await import('embla-carousel-auto-height').then(r => r.default)
165
+ plugins.push(AutoHeightPlugin(typeof props.autoHeight === 'boolean' ? {} : props.autoHeight))
166
+ }
167
+
168
+ if (props.classNames) {
169
+ const ClassNamesPlugin = await import('embla-carousel-class-names').then(r => r.default)
170
+ plugins.push(ClassNamesPlugin(typeof props.classNames === 'boolean' ? {} : props.classNames))
171
+ }
172
+
173
+ if (props.fade) {
174
+ const FadePlugin = await import('embla-carousel-fade').then(r => r.default)
175
+ plugins.push(FadePlugin(typeof props.fade === 'boolean' ? {} : props.fade))
176
+ }
177
+
178
+ if (props.wheelGestures) {
179
+ const { WheelGesturesPlugin } = await import('embla-carousel-wheel-gestures')
180
+ plugins.push(WheelGesturesPlugin(typeof props.wheelGestures === 'boolean' ? {} : props.wheelGestures))
181
+ }
182
+
183
+ return plugins
184
+ })
185
+
186
+ const [emblaRef, emblaApi] = useEmblaCarousel(options.value, plugins.value)
187
+
188
+ watch([options, plugins], () => {
189
+ emblaApi.value?.reInit(options.value, plugins.value)
190
+ })
191
+
192
+ function scrollPrev() {
193
+ emblaApi.value?.scrollPrev()
194
+ }
195
+ function scrollNext() {
196
+ emblaApi.value?.scrollNext()
197
+ }
198
+ function scrollTo(index: number) {
199
+ emblaApi.value?.scrollTo(index)
200
+ }
201
+
202
+ function onKeyDown(event: KeyboardEvent) {
203
+ const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
204
+ const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
205
+
206
+ if (event.key === prevKey) {
207
+ event.preventDefault()
208
+ scrollPrev()
209
+
210
+ return
211
+ }
212
+
213
+ if (event.key === nextKey) {
214
+ event.preventDefault()
215
+ scrollNext()
216
+ }
217
+ }
218
+
219
+ const canScrollNext = ref(false)
220
+ const canScrollPrev = ref(false)
221
+ const selectedIndex = ref<number>(0)
222
+ const scrollSnaps = ref<number[]>([])
223
+
224
+ function onInit(api: EmblaCarouselType) {
225
+ scrollSnaps.value = api?.scrollSnapList() || []
226
+ }
227
+ function onSelect(api: EmblaCarouselType) {
228
+ canScrollNext.value = api?.canScrollNext() || false
229
+ canScrollPrev.value = api?.canScrollPrev() || false
230
+ selectedIndex.value = api?.selectedScrollSnap() || 0
231
+ }
232
+
233
+ onMounted(() => {
234
+ if (!emblaApi.value) {
235
+ return
236
+ }
237
+
238
+ emblaApi.value?.on('init', onInit)
239
+ emblaApi.value?.on('init', onSelect)
240
+ emblaApi.value?.on('reInit', onInit)
241
+ emblaApi.value?.on('reInit', onSelect)
242
+ emblaApi.value?.on('select', onSelect)
243
+ })
244
+
245
+ defineExpose({
246
+ emblaRef,
247
+ emblaApi
248
+ })
249
+ </script>
250
+
251
+ <template>
252
+ <div
253
+ role="region"
254
+ aria-roledescription="carousel"
255
+ tabindex="0"
256
+ :class="ui.root({ class: [props.class, props.ui?.root] })"
257
+ @keydown="onKeyDown"
258
+ >
259
+ <div ref="emblaRef" :class="ui.viewport({ class: props.ui?.viewport })">
260
+ <div :class="ui.container({ class: props.ui?.container })">
261
+ <div
262
+ v-for="(item, index) in items"
263
+ :key="index"
264
+ role="group"
265
+ aria-roledescription="slide"
266
+ :class="ui.item({ class: props.ui?.item })"
267
+ >
268
+ <slot :item="item" :index="index" />
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ <div v-if="arrows || dots" :class="ui.controls({ class: props.ui?.controls })">
274
+ <div v-if="arrows" :class="ui.arrows({ class: props.ui?.arrows })">
275
+ <UButton
276
+ :disabled="!canScrollPrev"
277
+ :icon="prevIcon || appConfig.ui.icons.arrowLeft"
278
+ size="md"
279
+ color="neutral"
280
+ variant="outline"
281
+ aria-label="Prev"
282
+ v-bind="typeof prev === 'object' ? prev : undefined"
283
+ :class="ui.prev({ class: props.ui?.prev })"
284
+ @click="scrollPrev"
285
+ />
286
+ <UButton
287
+ :disabled="!canScrollNext"
288
+ :icon="nextIcon || appConfig.ui.icons.arrowRight"
289
+ size="md"
290
+ color="neutral"
291
+ variant="outline"
292
+ aria-label="Next"
293
+ v-bind="typeof next === 'object' ? next : undefined"
294
+ :class="ui.next({ class: props.ui?.next })"
295
+ @click="scrollNext"
296
+ />
297
+ </div>
298
+
299
+ <div v-if="dots" :class="ui.dots({ class: props.ui?.dots })">
300
+ <template v-for="(_, index) in scrollSnaps" :key="index">
301
+ <button :class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })" @click="scrollTo(index)" />
302
+ </template>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </template>
@@ -67,7 +67,7 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
67
67
  placeholder?: InputProps['placeholder']
68
68
  /**
69
69
  * Display a close button in the input (useful when inside a Modal for example).
70
- * `{ size: 'md', color: 'gray', variant: 'ghost' }`{lang="ts-type"}
70
+ * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
71
71
  * @defaultValue false
72
72
  */
73
73
  close?: ButtonProps | boolean
@@ -233,7 +233,7 @@ const groups = computed(() => {
233
233
  v-if="close"
234
234
  :icon="closeIcon || appConfig.ui.icons.close"
235
235
  size="md"
236
- color="gray"
236
+ color="neutral"
237
237
  variant="ghost"
238
238
  aria-label="Close"
239
239
  v-bind="typeof close === 'object' ? close : undefined"
@@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema'
6
6
  import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/context-menu'
8
8
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
9
- import type { DynamicSlots } from '../types/utils'
9
+ import type { DynamicSlots, PartialString } from '../types/utils'
10
10
 
11
11
  const appConfig = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
12
12
 
@@ -43,7 +43,7 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'>,
43
43
  */
44
44
  portal?: boolean
45
45
  class?: any
46
- ui?: Partial<typeof contextMenu.slots>
46
+ ui?: PartialString<typeof contextMenu.slots>
47
47
  }
48
48
 
49
49
  export interface ContextMenuEmits extends ContextMenuRootEmits {}
@@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema'
6
6
  import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/dropdown-menu'
8
8
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
9
- import type { DynamicSlots } from '../types/utils'
9
+ import type { DynamicSlots, PartialString } from '../types/utils'
10
10
 
11
11
  const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
12
12
 
@@ -51,7 +51,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
51
51
  */
52
52
  portal?: boolean
53
53
  class?: any
54
- ui?: Partial<typeof dropdownMenu.slots>
54
+ ui?: PartialString<typeof dropdownMenu.slots>
55
55
  }
56
56
 
57
57
  export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
@@ -35,7 +35,7 @@ export interface FormSlots {
35
35
  import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
36
36
  import { useEventBus } from '@vueuse/core'
37
37
  import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
38
- import { getYupErrors, isYupSchema, getValibotError, isValibotSchema, getZodErrors, isZodSchema, getJoiErrors, isJoiSchema } from '../utils/form'
38
+ import { getYupErrors, isYupSchema, getValibotErrors, isValibotSchema, getZodErrors, isZodSchema, getJoiErrors, isJoiSchema, getStandardErrors, isStandardSchema } from '../utils/form'
39
39
  import { FormValidationException } from '../types/form'
40
40
 
41
41
  const props = withDefaults(defineProps<FormProps<T>>(), {
@@ -112,7 +112,9 @@ async function getErrors(): Promise<FormErrorWithId[]> {
112
112
  } else if (isJoiSchema(props.schema)) {
113
113
  errs = errs.concat(await getJoiErrors(props.state, props.schema))
114
114
  } else if (isValibotSchema(props.schema)) {
115
- errs = errs.concat(await getValibotError(props.state, props.schema))
115
+ errs = errs.concat(await getValibotErrors(props.state, props.schema))
116
+ } else if (isStandardSchema(props.schema)) {
117
+ errs = errs.concat(await getStandardErrors(props.state, props.schema))
116
118
  } else {
117
119
  throw new Error('Form validation failed: Unsupported form schema')
118
120
  }
@@ -37,7 +37,7 @@ export interface ModalProps extends DialogRootProps {
37
37
  portal?: boolean
38
38
  /**
39
39
  * Display a close button to dismiss the modal.
40
- * `{ size: 'md', color: 'gray', variant: 'ghost' }`{lang="ts-type"}
40
+ * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
41
41
  * @defaultValue true
42
42
  */
43
43
  close?: ButtonProps | boolean
@@ -138,7 +138,7 @@ const ui = computed(() => modal({
138
138
  v-if="close"
139
139
  :icon="closeIcon || appConfig.ui.icons.close"
140
140
  size="md"
141
- color="gray"
141
+ color="neutral"
142
142
  variant="ghost"
143
143
  aria-label="Close"
144
144
  v-bind="typeof close === 'object' ? close : undefined"
@@ -6,7 +6,7 @@ import type { AppConfig } from '@nuxt/schema'
6
6
  import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/navigation-menu'
8
8
  import type { AvatarProps, BadgeProps, LinkProps } from '../types'
9
- import type { DynamicSlots } from '../types/utils'
9
+ import type { DynamicSlots, PartialString } from '../types/utils'
10
10
 
11
11
  const appConfig = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
12
12
 
@@ -62,7 +62,7 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'd
62
62
  */
63
63
  arrow?: boolean
64
64
  class?: any
65
- ui?: Partial<typeof navigationMenu.slots>
65
+ ui?: PartialString<typeof navigationMenu.slots>
66
66
  }
67
67
 
68
68
  export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
@@ -142,7 +142,7 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
142
142
  <slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="item" :active="active" :index="index">
143
143
  <UBadge
144
144
  v-if="item.badge"
145
- color="gray"
145
+ color="neutral"
146
146
  variant="outline"
147
147
  :size="((props.ui?.linkTrailingBadgeSize || ui.linkTrailingBadgeSize()) as BadgeProps['size'])"
148
148
  v-bind="(typeof item.badge === 'string' || typeof item.badge === 'number') ? { label: item.badge } : item.badge"
@@ -44,7 +44,7 @@ export interface PaginationProps extends Pick<PaginationRootProps, 'defaultPage'
44
44
  ellipsisIcon?: string
45
45
  /**
46
46
  * The color of the pagination controls.
47
- * @defaultValue 'gray'
47
+ * @defaultValue 'neutral'
48
48
  */
49
49
  color?: ButtonProps['color']
50
50
  /**
@@ -106,7 +106,7 @@ import { useAppConfig } from '#imports'
106
106
 
107
107
  const props = withDefaults(defineProps<PaginationProps>(), {
108
108
  size: 'md',
109
- color: 'gray',
109
+ color: 'neutral',
110
110
  variant: 'outline',
111
111
  activeColor: 'primary',
112
112
  activeVariant: 'solid',
@@ -35,7 +35,7 @@ export interface SlideoverProps extends DialogRootProps {
35
35
  portal?: boolean
36
36
  /**
37
37
  * Display a close button to dismiss the slideover.
38
- * `{ size: 'md', color: 'gray', variant: 'ghost' }`{lang="ts-type"}
38
+ * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
39
39
  * @defaultValue true
40
40
  */
41
41
  close?: ButtonProps | boolean
@@ -137,7 +137,7 @@ const ui = computed(() => slideover({
137
137
  v-if="close"
138
138
  :icon="closeIcon || appConfig.ui.icons.close"
139
139
  size="md"
140
- color="gray"
140
+ color="neutral"
141
141
  variant="ghost"
142
142
  aria-label="Close"
143
143
  v-bind="typeof close === 'object' ? close : undefined"
@@ -31,7 +31,7 @@ export interface ToastProps extends Pick<ToastRootProps, 'defaultOpen' | 'open'
31
31
  actions?: ButtonProps[]
32
32
  /**
33
33
  * Display a close button to dismiss the toast.
34
- * `{ size: 'md', color: 'gray', variant: 'link' }`{lang="ts-type"}
34
+ * `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
35
35
  * @defaultValue true
36
36
  */
37
37
  close?: ButtonProps | boolean
@@ -145,7 +145,7 @@ defineExpose({
145
145
  v-if="close"
146
146
  :icon="closeIcon || appConfig.ui.icons.close"
147
147
  size="md"
148
- color="gray"
148
+ color="neutral"
149
149
  variant="link"
150
150
  aria-label="Close"
151
151
  v-bind="typeof close === 'object' ? close : undefined"
@@ -1 +1 @@
1
- @theme{--color-gray-*:initial;--color-cool-50:#f9fafb;--color-cool-100:#f3f4f6;--color-cool-200:#e5e7eb;--color-cool-300:#d1d5db;--color-cool-400:#9ca3af;--color-cool-500:#6b7280;--color-cool-600:#4b5563;--color-cool-700:#374151;--color-cool-800:#1f2937;--color-cool-900:#111827;--color-cool-950:#030712;--spacing-4_5:1.125rem;--color-primary-50:var(--color-primary-50);--color-primary-100:var(--color-primary-100);--color-primary-200:var(--color-primary-200);--color-primary-300:var(--color-primary-300);--color-primary-400:var(--color-primary-400);--color-primary-500:var(--color-primary-500);--color-primary-600:var(--color-primary-600);--color-primary-700:var(--color-primary-700);--color-primary-800:var(--color-primary-800);--color-primary-900:var(--color-primary-900);--color-primary-950:var(--color-primary-950);--color-error-50:var(--color-error-50);--color-error-100:var(--color-error-100);--color-error-200:var(--color-error-200);--color-error-300:var(--color-error-300);--color-error-400:var(--color-error-400);--color-error-500:var(--color-error-500);--color-error-600:var(--color-error-600);--color-error-700:var(--color-error-700);--color-error-800:var(--color-error-800);--color-error-900:var(--color-error-900);--color-error-950:var(--color-error-950);--color-gray-50:var(--color-gray-50);--color-gray-100:var(--color-gray-100);--color-gray-200:var(--color-gray-200);--color-gray-300:var(--color-gray-300);--color-gray-400:var(--color-gray-400);--color-gray-500:var(--color-gray-500);--color-gray-600:var(--color-gray-600);--color-gray-700:var(--color-gray-700);--color-gray-800:var(--color-gray-800);--color-gray-900:var(--color-gray-900);--color-gray-950:var(--color-gray-950)}@variant dark (&:where(.dark, .dark *));@layer base{:root{color-scheme:light dark}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}@keyframes collapsible-up{0%{height:var(--radix-collapsible-content-height)}to{height:0}}@keyframes collapsible-down{0%{height:0}to{height:var(--radix-collapsible-content-height)}}@keyframes toast-collapsed-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--before) - var(--height))*var(--gap))) scale(var(--scale))}}@keyframes toast-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--offset) - var(--height))*var(--translate-factor)))}}@keyframes toast-slide-left{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(-100%) translateY(var(--translate))}}@keyframes toast-slide-right{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(100%) translateY(var(--translate))}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slide-in-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes slide-out-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes slide-in-from-right{0%{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes slide-out-to-right{0%{transform:translateX(0)}to{transform:translateX(100%)}}@keyframes slide-in-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slide-out-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes slide-in-from-left{0%{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes slide-out-to-left{0%{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes slide-in-from-top-and-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-top-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes slide-in-from-right-and-fade{0%{opacity:0;transform:translateX(4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-right-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(4px)}}@keyframes slide-in-from-bottom-and-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-bottom-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(4px)}}@keyframes slide-in-from-left-and-fade{0%{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-left-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-4px)}}@keyframes enter-from-right{0%{opacity:0;transform:translateX(200px)}to{opacity:1;transform:translateX(0)}}@keyframes enter-from-left{0%{opacity:0;transform:translateX(-200px)}to{opacity:1;transform:translateX(0)}}@keyframes exit-to-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(200px)}}@keyframes exit-to-left{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-200px)}}@keyframes carousel{0%,to{width:50%}0%{transform:translateX(-100%)}to{transform:translateX(200%)}}@keyframes carousel-vertical{0%,to{height:50%}0%{transform:translateY(-100%)}to{transform:translateY(200%)}}@keyframes carousel-inverse{0%,to{width:50%}0%{transform:translateX(200%)}to{transform:translateX(-100%)}}@keyframes carousel-inverse-vertical{0%,to{height:50%}0%{transform:translateY(200%)}to{transform:translateY(-100%)}}@keyframes swing{0%,to{width:50%}0%,to{transform:translateX(-25%)}50%{transform:translateX(125%)}}@keyframes swing-vertical{0%,to{height:50%}0%,to{transform:translateY(-25%)}50%{transform:translateY(125%)}}@keyframes elastic{0%,to{margin-left:25%;width:50%}50%{margin-left:5%;width:90%}}@keyframes elastic-vertical{0%,to{height:50%;margin-top:25%}50%{height:90%;margin-top:5%}}}
1
+ @theme{--spacing-4_5:1.125rem}@variant dark (&:where(.dark, .dark *));@layer base{body{@apply antialiased font-sans text-[--ui-text] bg-[--ui-bg]}:root{color-scheme:light;--ui-text-dimmed:var(--ui-color-neutral-400);--ui-text-muted:var(--ui-color-neutral-500);--ui-text-toned:var(--ui-color-neutral-600);--ui-text:var(--ui-color-neutral-700);--ui-text-highlighted:var(--ui-color-neutral-900);--ui-bg:var(--color-white);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-200);--ui-bg-inverted:var(--ui-color-neutral-900);--ui-border:var(--ui-color-neutral-200);--ui-border-accented:var(--ui-color-neutral-300);--ui-border-inverted:var(--ui-color-neutral-900);--ui-radius:var(--radius)}.dark{color-scheme:dark;--ui-text-dimmed:var(--ui-color-neutral-500);--ui-text-muted:var(--ui-color-neutral-400);--ui-text-toned:var(--ui-color-neutral-300);--ui-text:var(--ui-color-neutral-200);--ui-text-highlighted:var(--color-white);--ui-bg:var(--ui-color-neutral-900);--ui-bg-elevated:var(--ui-color-neutral-800);--ui-bg-accented:var(--ui-color-neutral-700);--ui-bg-inverted:var(--color-white);--ui-border:var(--ui-color-neutral-800);--ui-border-accented:var(--ui-color-neutral-700);--ui-border-inverted:var(--color-white)}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}@keyframes collapsible-up{0%{height:var(--radix-collapsible-content-height)}to{height:0}}@keyframes collapsible-down{0%{height:0}to{height:var(--radix-collapsible-content-height)}}@keyframes toast-collapsed-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--before) - var(--height))*var(--gap))) scale(var(--scale))}}@keyframes toast-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--offset) - var(--height))*var(--translate-factor)))}}@keyframes toast-slide-left{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(-100%) translateY(var(--translate))}}@keyframes toast-slide-right{0%{transform:translateX(0) translateY(var(--translate))}to{transform:translateX(100%) translateY(var(--translate))}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slide-in-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes slide-out-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes slide-in-from-right{0%{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes slide-out-to-right{0%{transform:translateX(0)}to{transform:translateX(100%)}}@keyframes slide-in-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slide-out-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes slide-in-from-left{0%{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes slide-out-to-left{0%{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes slide-in-from-top-and-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-top-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes slide-in-from-right-and-fade{0%{opacity:0;transform:translateX(4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-right-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(4px)}}@keyframes slide-in-from-bottom-and-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-bottom-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(4px)}}@keyframes slide-in-from-left-and-fade{0%{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-to-left-and-fade{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-4px)}}@keyframes enter-from-right{0%{opacity:0;transform:translateX(200px)}to{opacity:1;transform:translateX(0)}}@keyframes enter-from-left{0%{opacity:0;transform:translateX(-200px)}to{opacity:1;transform:translateX(0)}}@keyframes exit-to-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(200px)}}@keyframes exit-to-left{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-200px)}}@keyframes carousel{0%,to{width:50%}0%{transform:translateX(-100%)}to{transform:translateX(200%)}}@keyframes carousel-vertical{0%,to{height:50%}0%{transform:translateY(-100%)}to{transform:translateY(200%)}}@keyframes carousel-inverse{0%,to{width:50%}0%{transform:translateX(200%)}to{transform:translateX(-100%)}}@keyframes carousel-inverse-vertical{0%,to{height:50%}0%{transform:translateY(200%)}to{transform:translateY(-100%)}}@keyframes swing{0%,to{width:50%}0%,to{transform:translateX(-25%)}50%{transform:translateX(125%)}}@keyframes swing-vertical{0%,to{height:50%}0%,to{transform:translateY(-25%)}50%{transform:translateY(125%)}}@keyframes elastic{0%,to{margin-left:25%;width:50%}50%{margin-left:5%;width:90%}}@keyframes elastic-vertical{0%,to{height:50%;margin-top:25%}50%{height:90%;margin-top:5%}}}
@@ -4,18 +4,22 @@ export default defineNuxtPlugin(() => {
4
4
  const appConfig = useAppConfig();
5
5
  const nuxtApp = useNuxtApp();
6
6
  const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
7
- function generateColor(key, value) {
8
- return `${shades.map((shade) => `--color-${key}-${shade}: var(--color-${value}-${shade});`).join("\n ")}`;
7
+ function generateShades(key, value) {
8
+ return `${shades.map((shade) => `--ui-color-${key}-${shade}: var(--color-${value}-${shade});`).join("\n ")}`;
9
+ }
10
+ function generateColor(key, shade) {
11
+ return `--ui-${key}: var(--ui-color-${key}-${shade});`;
9
12
  }
10
13
  const root = computed(() => {
14
+ const { neutral, ...colors } = appConfig.ui.colors;
11
15
  return `:root {
12
- ${Object.entries(appConfig.ui.colors).map(([key, value]) => generateColor(key, value)).join("\n ")}
13
- --color-primary-DEFAULT: var(--color-primary-500);
16
+ ${Object.entries(appConfig.ui.colors).map(([key, value]) => generateShades(key, value)).join("\n ")}
17
+
18
+ ${Object.keys(colors).map((key) => generateColor(key, 500)).join("\n ")}
14
19
  }
15
20
  .dark {
16
- --color-primary-DEFAULT: var(--color-primary-400);
17
- }
18
- `;
21
+ ${Object.keys(colors).map((key) => generateColor(key, 400)).join("\n ")}
22
+ }`;
19
23
  });
20
24
  const headData = {
21
25
  style: [{
@@ -1,3 +1,4 @@
1
+ import type { StandardSchema } from '@standard-schema/spec';
1
2
  import type { ComputedRef, Ref } from 'vue';
2
3
  import type { ZodSchema } from 'zod';
3
4
  import type { Schema as JoiSchema } from 'joi';
@@ -17,7 +18,7 @@ export interface Form<T> {
17
18
  submit(): Promise<void>;
18
19
  disabled: ComputedRef<boolean>;
19
20
  }
20
- export type FormSchema<T extends Record<string, any>> = ZodSchema | YupObjectSchema<T> | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any> | JoiSchema<T>;
21
+ export type FormSchema<T extends Record<string, any>> = ZodSchema | YupObjectSchema<T> | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any> | JoiSchema<T> | StandardSchema;
21
22
  export type FormInputEvents = 'input' | 'blur' | 'change';
22
23
  export interface FormError<P extends string = string> {
23
24
  name: P;
@@ -7,6 +7,7 @@ export * from '../components/Badge.vue';
7
7
  export * from '../components/Breadcrumb.vue';
8
8
  export * from '../components/Button.vue';
9
9
  export * from '../components/Card.vue';
10
+ export * from '../components/Carousel.vue';
10
11
  export * from '../components/Checkbox.vue';
11
12
  export * from '../components/Chip.vue';
12
13
  export * from '../components/Collapsible.vue';
@@ -7,6 +7,7 @@ export * from "../components/Badge.vue";
7
7
  export * from "../components/Breadcrumb.vue";
8
8
  export * from "../components/Button.vue";
9
9
  export * from "../components/Card.vue";
10
+ export * from "../components/Carousel.vue";
10
11
  export * from "../components/Checkbox.vue";
11
12
  export * from "../components/Chip.vue";
12
13
  export * from "../components/Collapsible.vue";
@@ -1,3 +1,4 @@
1
+ import type { StandardSchema } from '@standard-schema/spec';
1
2
  import type { ZodSchema } from 'zod';
2
3
  import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi';
3
4
  import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup';
@@ -12,4 +13,6 @@ export declare function isJoiSchema(schema: any): schema is JoiSchema;
12
13
  export declare function isJoiError(error: any): error is JoiError;
13
14
  export declare function getJoiErrors(state: any, schema: JoiSchema): Promise<FormError[]>;
14
15
  export declare function isValibotSchema(schema: any): schema is ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>;
15
- export declare function getValibotError(state: any, schema: ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>): Promise<FormError[]>;
16
+ export declare function getValibotErrors(state: any, schema: ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>): Promise<FormError[]>;
17
+ export declare function isStandardSchema(schema: any): schema is StandardSchema;
18
+ export declare function getStandardErrors(state: any, schema: StandardSchema): Promise<FormError[]>;
@@ -56,7 +56,7 @@ export async function getJoiErrors(state, schema) {
56
56
  export function isValibotSchema(schema) {
57
57
  return "_run" in schema || typeof schema === "function" && "schema" in schema;
58
58
  }
59
- export async function getValibotError(state, schema) {
59
+ export async function getValibotErrors(state, schema) {
60
60
  const result = await ("_run" in schema ? schema._run({ typed: false, value: state }, {}) : schema(state));
61
61
  return result.issues?.map((issue) => ({
62
62
  // We know that the key for a form schema is always a string or a number
@@ -64,3 +64,13 @@ export async function getValibotError(state, schema) {
64
64
  message: issue.message
65
65
  })) || [];
66
66
  }
67
+ export function isStandardSchema(schema) {
68
+ return "~standard" in schema;
69
+ }
70
+ export async function getStandardErrors(state, schema) {
71
+ const result = await schema["~validate"]({ value: state });
72
+ return result.issues?.map((issue) => ({
73
+ name: issue.path?.map((item) => typeof item === "object" ? item.key : item).join(".") || "",
74
+ message: issue.message
75
+ })) || [];
76
+ }