@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.
- package/README.md +4 -2
- package/dist/module.d.mts +19 -5
- package/dist/module.d.ts +19 -5
- package/dist/module.json +2 -1
- package/dist/module.mjs +358 -316
- package/dist/runtime/components/Alert.vue +2 -2
- package/dist/runtime/components/Breadcrumb.vue +2 -2
- package/dist/runtime/components/Carousel.vue +306 -0
- package/dist/runtime/components/CommandPalette.vue +2 -2
- package/dist/runtime/components/ContextMenu.vue +2 -2
- package/dist/runtime/components/DropdownMenu.vue +2 -2
- package/dist/runtime/components/Form.vue +4 -2
- package/dist/runtime/components/Modal.vue +2 -2
- package/dist/runtime/components/NavigationMenu.vue +3 -3
- package/dist/runtime/components/Pagination.vue +2 -2
- package/dist/runtime/components/Slideover.vue +2 -2
- package/dist/runtime/components/Toast.vue +2 -2
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/plugins/colors.js +11 -7
- package/dist/runtime/types/form.d.ts +2 -1
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/utils/form.d.ts +4 -1
- package/dist/runtime/utils/form.js +11 -1
- package/dist/runtime/utils/link.d.ts +1 -1
- package/package.json +22 -9
|
@@ -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: '
|
|
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="
|
|
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?:
|
|
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: '
|
|
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="
|
|
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?:
|
|
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?:
|
|
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,
|
|
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
|
|
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: '
|
|
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="
|
|
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?:
|
|
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="
|
|
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 '
|
|
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: '
|
|
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: '
|
|
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="
|
|
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: '
|
|
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="
|
|
148
|
+
color="neutral"
|
|
149
149
|
variant="link"
|
|
150
150
|
aria-label="Close"
|
|
151
151
|
v-bind="typeof close === 'object' ? close : undefined"
|
package/dist/runtime/index.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@theme{--
|
|
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
|
|
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]) =>
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|