@nuxt/ui 3.0.0-alpha.6 → 3.0.0-alpha.7
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/dist/module.d.mts +6 -6
- package/dist/module.d.ts +6 -6
- package/dist/module.json +1 -1
- package/dist/module.mjs +391 -257
- package/dist/runtime/components/Accordion.vue +13 -5
- package/dist/runtime/components/Breadcrumb.vue +14 -6
- package/dist/runtime/components/Button.vue +4 -2
- package/dist/runtime/components/Carousel.vue +6 -6
- package/dist/runtime/components/Checkbox.vue +3 -6
- package/dist/runtime/components/CommandPalette.vue +25 -16
- package/dist/runtime/components/ContextMenu.vue +31 -7
- package/dist/runtime/components/ContextMenuContent.vue +36 -8
- package/dist/runtime/components/DropdownMenu.vue +31 -7
- package/dist/runtime/components/DropdownMenuContent.vue +38 -8
- package/dist/runtime/components/Form.vue +3 -1
- package/dist/runtime/components/Input.vue +6 -4
- package/dist/runtime/components/InputMenu.vue +40 -24
- package/dist/runtime/components/LinkBase.vue +4 -2
- package/dist/runtime/components/NavigationMenu.vue +78 -41
- package/dist/runtime/components/RadioGroup.vue +22 -3
- package/dist/runtime/components/Select.vue +22 -10
- package/dist/runtime/components/SelectMenu.vue +43 -31
- package/dist/runtime/components/Table.vue +232 -0
- package/dist/runtime/components/Tabs.vue +15 -7
- package/dist/runtime/components/Toaster.vue +1 -1
- package/dist/runtime/composables/defineShortcuts.js +1 -1
- package/dist/runtime/composables/useComponentIcons.d.ts +3 -0
- package/dist/runtime/composables/useModal.d.ts +1 -1
- package/dist/runtime/composables/useSlideover.d.ts +1 -1
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/keyframes.css +1 -0
- 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 +3 -0
- package/dist/runtime/utils/form.js +14 -0
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +3 -0
- package/package.json +15 -12
- package/dist/runtime/types/component.d.ts +0 -17
- package/dist/runtime/types/component.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { tv } from 'tailwind-variants'
|
|
4
|
-
import type { AccordionRootProps, AccordionRootEmits
|
|
4
|
+
import type { AccordionRootProps, AccordionRootEmits } from 'radix-vue'
|
|
5
5
|
import type { AppConfig } from '@nuxt/schema'
|
|
6
6
|
import _appConfig from '#build/app.config'
|
|
7
7
|
import theme from '#build/ui/accordion'
|
|
@@ -11,7 +11,7 @@ const appConfig = _appConfig as AppConfig & { ui: { accordion: Partial<typeof th
|
|
|
11
11
|
|
|
12
12
|
const accordion = tv({ extend: tv(theme), ...(appConfig.ui?.accordion || {}) })
|
|
13
13
|
|
|
14
|
-
export interface AccordionItem
|
|
14
|
+
export interface AccordionItem {
|
|
15
15
|
label?: string
|
|
16
16
|
icon?: string
|
|
17
17
|
trailingIcon?: string
|
|
@@ -19,6 +19,7 @@ export interface AccordionItem extends Pick<AccordionItemProps, 'disabled'> {
|
|
|
19
19
|
content?: string
|
|
20
20
|
/** A unique value for the accordion item. Defaults to the index. */
|
|
21
21
|
value?: string
|
|
22
|
+
disabled?: boolean
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible' | 'defaultValue' | 'modelValue' | 'type' | 'disabled'> {
|
|
@@ -33,6 +34,11 @@ export interface AccordionProps<T> extends Pick<AccordionRootProps, 'collapsible
|
|
|
33
34
|
* @defaultValue appConfig.ui.icons.chevronDown
|
|
34
35
|
*/
|
|
35
36
|
trailingIcon?: string
|
|
37
|
+
/**
|
|
38
|
+
* The key used to get the label from the item.
|
|
39
|
+
* @defaultValue 'label'
|
|
40
|
+
*/
|
|
41
|
+
labelKey?: string
|
|
36
42
|
class?: any
|
|
37
43
|
ui?: Partial<typeof accordion.slots>
|
|
38
44
|
}
|
|
@@ -56,10 +62,12 @@ import { computed } from 'vue'
|
|
|
56
62
|
import { AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'radix-vue'
|
|
57
63
|
import { reactivePick } from '@vueuse/core'
|
|
58
64
|
import { useAppConfig } from '#imports'
|
|
65
|
+
import { get } from '../utils'
|
|
59
66
|
|
|
60
67
|
const props = withDefaults(defineProps<AccordionProps<T>>(), {
|
|
61
68
|
type: 'single',
|
|
62
|
-
collapsible: true
|
|
69
|
+
collapsible: true,
|
|
70
|
+
labelKey: 'label'
|
|
63
71
|
})
|
|
64
72
|
const emits = defineEmits<AccordionEmits>()
|
|
65
73
|
const slots = defineSlots<AccordionSlots<T>>()
|
|
@@ -88,8 +96,8 @@ const ui = computed(() => accordion({
|
|
|
88
96
|
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
89
97
|
</slot>
|
|
90
98
|
|
|
91
|
-
<span v-if="item.
|
|
92
|
-
<slot :item="item" :index="index" :open="open">{{ item.
|
|
99
|
+
<span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: props.ui?.label })">
|
|
100
|
+
<slot :item="item" :index="index" :open="open">{{ get(item, props.labelKey as string) }}</slot>
|
|
93
101
|
</span>
|
|
94
102
|
|
|
95
103
|
<slot name="trailing" :item="item" :index="index" :open="open">
|
|
@@ -30,6 +30,11 @@ export interface BreadcrumbProps<T> {
|
|
|
30
30
|
* @defaultValue appConfig.ui.icons.chevronRight
|
|
31
31
|
*/
|
|
32
32
|
separatorIcon?: string
|
|
33
|
+
/**
|
|
34
|
+
* The key used to get the label from the item.
|
|
35
|
+
* @defaultValue 'label'
|
|
36
|
+
*/
|
|
37
|
+
labelKey?: string
|
|
33
38
|
class?: any
|
|
34
39
|
ui?: PartialString<typeof breadcrumb.slots>
|
|
35
40
|
}
|
|
@@ -49,13 +54,16 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
|
|
|
49
54
|
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
|
50
55
|
import { Primitive } from 'radix-vue'
|
|
51
56
|
import { useAppConfig } from '#imports'
|
|
57
|
+
import { get } from '../utils'
|
|
58
|
+
import { pickLinkProps } from '../utils/link'
|
|
52
59
|
import UIcon from './Icon.vue'
|
|
53
60
|
import UAvatar from './Avatar.vue'
|
|
54
61
|
import ULinkBase from './LinkBase.vue'
|
|
55
62
|
import ULink from './Link.vue'
|
|
56
|
-
import { pickLinkProps } from '../utils/link'
|
|
57
63
|
|
|
58
|
-
const props = defineProps<BreadcrumbProps<T>>()
|
|
64
|
+
const props = withDefaults(defineProps<BreadcrumbProps<T>>(), {
|
|
65
|
+
labelKey: 'label'
|
|
66
|
+
})
|
|
59
67
|
const slots = defineSlots<BreadcrumbSlots<T>>()
|
|
60
68
|
|
|
61
69
|
const appConfig = useAppConfig()
|
|
@@ -73,13 +81,13 @@ const ui = breadcrumb()
|
|
|
73
81
|
<ULinkBase v-bind="slotProps" as="span" :aria-current="active && (index === items!.length - 1) ? 'page' : undefined" :class="ui.link({ class: [props.ui?.link, item.class], active: index === items!.length - 1, disabled: !!item.disabled, to: !!item.to })">
|
|
74
82
|
<slot :name="item.slot || 'item'" :item="item" :index="index">
|
|
75
83
|
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="item" :active="index === items!.length - 1" :index="index">
|
|
76
|
-
<
|
|
77
|
-
<
|
|
84
|
+
<UIcon v-if="item.icon" :name="item.icon" :class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon, active: index === items!.length - 1 })" />
|
|
85
|
+
<UAvatar v-else-if="item.avatar" :size="((props.ui?.linkLeadingAvatarSize || ui.linkLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.linkLeadingAvatar({ class: props.ui?.linkLeadingAvatar, active: index === items!.length - 1 })" />
|
|
78
86
|
</slot>
|
|
79
87
|
|
|
80
|
-
<span v-if="item.
|
|
88
|
+
<span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.linkLabel({ class: props.ui?.linkLabel })">
|
|
81
89
|
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="item" :active="index === items!.length - 1" :index="index">
|
|
82
|
-
{{ item.
|
|
90
|
+
{{ get(item, props.labelKey as string) }}
|
|
83
91
|
</slot>
|
|
84
92
|
</span>
|
|
85
93
|
|
|
@@ -5,6 +5,7 @@ import _appConfig from '#build/app.config'
|
|
|
5
5
|
import theme from '#build/ui/button'
|
|
6
6
|
import type { LinkProps } from './Link.vue'
|
|
7
7
|
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
|
8
|
+
import type { AvatarProps } from '../types'
|
|
8
9
|
import type { PartialString } from '../types/utils'
|
|
9
10
|
import { formLoadingInjectionKey } from '../composables/useFormField'
|
|
10
11
|
|
|
@@ -42,10 +43,10 @@ import { type Ref, computed, ref, inject } from 'vue'
|
|
|
42
43
|
import { useForwardProps } from 'radix-vue'
|
|
43
44
|
import { useComponentIcons } from '../composables/useComponentIcons'
|
|
44
45
|
import { useButtonGroup } from '../composables/useButtonGroup'
|
|
45
|
-
import UIcon from './Icon.vue'
|
|
46
|
-
import ULink from './Link.vue'
|
|
47
46
|
import { omit } from '../utils'
|
|
48
47
|
import { pickLinkProps } from '../utils/link'
|
|
48
|
+
import UIcon from './Icon.vue'
|
|
49
|
+
import ULink from './Link.vue'
|
|
49
50
|
|
|
50
51
|
const props = defineProps<ButtonProps>()
|
|
51
52
|
const slots = defineSlots<ButtonSlots>()
|
|
@@ -99,6 +100,7 @@ const ui = computed(() => button({
|
|
|
99
100
|
>
|
|
100
101
|
<slot name="leading">
|
|
101
102
|
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
103
|
+
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
|
|
102
104
|
</slot>
|
|
103
105
|
|
|
104
106
|
<slot>
|
|
@@ -55,32 +55,32 @@ export interface CarouselProps<T> extends Omit<EmblaOptionsType, 'axis' | 'conta
|
|
|
55
55
|
items?: T[]
|
|
56
56
|
/**
|
|
57
57
|
* Enable Autoplay plugin
|
|
58
|
-
* @
|
|
58
|
+
* @link https://www.embla-carousel.com/plugins/autoplay/
|
|
59
59
|
*/
|
|
60
60
|
autoplay?: boolean | AutoplayOptionsType
|
|
61
61
|
/**
|
|
62
62
|
* Enable Auto Scroll plugin
|
|
63
|
-
* @
|
|
63
|
+
* @link https://www.embla-carousel.com/plugins/auto-scroll/
|
|
64
64
|
*/
|
|
65
65
|
autoScroll?: boolean | AutoScrollOptionsType
|
|
66
66
|
/**
|
|
67
67
|
* Enable Auto Height plugin
|
|
68
|
-
* @
|
|
68
|
+
* @link https://www.embla-carousel.com/plugins/auto-height/
|
|
69
69
|
*/
|
|
70
70
|
autoHeight?: boolean | AutoHeightOptionsType
|
|
71
71
|
/**
|
|
72
72
|
* Enable Class Names plugin
|
|
73
|
-
* @
|
|
73
|
+
* @link https://www.embla-carousel.com/plugins/class-names/
|
|
74
74
|
*/
|
|
75
75
|
classNames?: boolean | ClassNamesOptionsType
|
|
76
76
|
/**
|
|
77
77
|
* Enable Fade plugin
|
|
78
|
-
* @
|
|
78
|
+
* @link https://www.embla-carousel.com/plugins/fade/
|
|
79
79
|
*/
|
|
80
80
|
fade?: boolean | FadeOptionsType
|
|
81
81
|
/**
|
|
82
82
|
* Enable Wheel Gestures plugin
|
|
83
|
-
* @
|
|
83
|
+
* @link https://www.embla-carousel.com/plugins/wheel-gestures/
|
|
84
84
|
*/
|
|
85
85
|
wheelGestures?: boolean | WheelGesturesPluginOptions
|
|
86
86
|
class?: any
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { InputHTMLAttributes } from 'vue'
|
|
3
2
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
4
3
|
import type { CheckboxRootProps } from 'radix-vue'
|
|
5
4
|
import type { AppConfig } from '@nuxt/schema'
|
|
@@ -22,7 +21,7 @@ export interface CheckboxProps extends Pick<CheckboxRootProps, 'disabled' | 'req
|
|
|
22
21
|
* @defaultValue appConfig.ui.icons.check
|
|
23
22
|
*/
|
|
24
23
|
icon?: string
|
|
25
|
-
indeterminate?:
|
|
24
|
+
indeterminate?: boolean
|
|
26
25
|
/**
|
|
27
26
|
* The icon displayed when the checkbox is indeterminate.
|
|
28
27
|
* @defaultValue appConfig.ui.icons.minus
|
|
@@ -64,11 +63,9 @@ const appConfig = useAppConfig()
|
|
|
64
63
|
const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<CheckboxProps>(props)
|
|
65
64
|
const id = _id.value ?? useId()
|
|
66
65
|
|
|
67
|
-
const indeterminate = computed(() => (modelValue.value === undefined && props.indeterminate))
|
|
68
|
-
|
|
69
66
|
const checked = computed({
|
|
70
67
|
get() {
|
|
71
|
-
return indeterminate
|
|
68
|
+
return props.indeterminate ? 'indeterminate' : modelValue.value
|
|
72
69
|
},
|
|
73
70
|
set(value) {
|
|
74
71
|
modelValue.value = value === 'indeterminate' ? undefined : value
|
|
@@ -80,7 +77,7 @@ const ui = computed(() => checkbox({
|
|
|
80
77
|
color: color.value,
|
|
81
78
|
required: props.required,
|
|
82
79
|
disabled: disabled.value,
|
|
83
|
-
checked: (modelValue.value ?? props.defaultValue) || indeterminate
|
|
80
|
+
checked: (modelValue.value ?? props.defaultValue) || props.indeterminate
|
|
84
81
|
}))
|
|
85
82
|
|
|
86
83
|
function onUpdate(value: any) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { tv } from 'tailwind-variants'
|
|
4
|
-
import type { ComboboxRootProps, ComboboxRootEmits
|
|
4
|
+
import type { ComboboxRootProps, ComboboxRootEmits } from 'radix-vue'
|
|
5
5
|
import type { FuseResult } from 'fuse.js'
|
|
6
6
|
import type { AppConfig } from '@nuxt/schema'
|
|
7
7
|
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
|
@@ -15,7 +15,7 @@ const appConfig = _appConfig as AppConfig & { ui: { commandPalette: Partial<type
|
|
|
15
15
|
|
|
16
16
|
const commandPalette = tv({ extend: tv(theme), ...(appConfig.ui?.commandPalette || {}) })
|
|
17
17
|
|
|
18
|
-
export interface CommandPaletteItem
|
|
18
|
+
export interface CommandPaletteItem {
|
|
19
19
|
prefix?: string
|
|
20
20
|
label?: string
|
|
21
21
|
suffix?: string
|
|
@@ -23,8 +23,10 @@ export interface CommandPaletteItem extends Pick<ComboboxItemProps, 'disabled'>
|
|
|
23
23
|
avatar?: AvatarProps
|
|
24
24
|
chip?: ChipProps
|
|
25
25
|
kbds?: KbdProps['value'][] | KbdProps[]
|
|
26
|
+
loading?: boolean
|
|
27
|
+
disabled?: boolean
|
|
26
28
|
slot?: string
|
|
27
|
-
|
|
29
|
+
onSelect?(e?: Event): void
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface CommandPaletteGroup<T> {
|
|
@@ -90,6 +92,11 @@ export interface CommandPaletteProps<G, T> extends Pick<ComboboxRootProps, 'mult
|
|
|
90
92
|
}
|
|
91
93
|
*/
|
|
92
94
|
fuse?: UseFuseOptions<T>
|
|
95
|
+
/**
|
|
96
|
+
* The key used to get the label from the item.
|
|
97
|
+
* @defaultValue 'label'
|
|
98
|
+
*/
|
|
99
|
+
labelKey?: string
|
|
93
100
|
class?: any
|
|
94
101
|
ui?: PartialString<typeof commandPalette.slots>
|
|
95
102
|
}
|
|
@@ -116,17 +123,18 @@ import { defu } from 'defu'
|
|
|
116
123
|
import { reactivePick } from '@vueuse/core'
|
|
117
124
|
import { useFuse } from '@vueuse/integrations/useFuse'
|
|
118
125
|
import { useAppConfig } from '#imports'
|
|
126
|
+
import { omit, get } from '../utils'
|
|
127
|
+
import { highlight } from '../utils/fuse'
|
|
119
128
|
import UIcon from './Icon.vue'
|
|
120
129
|
import UAvatar from './Avatar.vue'
|
|
121
130
|
import UChip from './Chip.vue'
|
|
122
131
|
import UKbd from './Kbd.vue'
|
|
123
132
|
import UInput from './Input.vue'
|
|
124
|
-
import { omit } from '../utils'
|
|
125
|
-
import { highlight } from '../utils/fuse'
|
|
126
133
|
|
|
127
134
|
const props = withDefaults(defineProps<CommandPaletteProps<G, T>>(), {
|
|
128
135
|
modelValue: '',
|
|
129
|
-
placeholder: 'Type a command or search...'
|
|
136
|
+
placeholder: 'Type a command or search...',
|
|
137
|
+
labelKey: 'label'
|
|
130
138
|
})
|
|
131
139
|
const emits = defineEmits<CommandPaletteEmits<T>>()
|
|
132
140
|
const slots = defineSlots<CommandPaletteSlots<G, T>>()
|
|
@@ -144,7 +152,7 @@ const fuse = computed(() => defu({}, props.fuse, {
|
|
|
144
152
|
fuseOptions: {
|
|
145
153
|
ignoreLocation: true,
|
|
146
154
|
threshold: 0.1,
|
|
147
|
-
keys: [
|
|
155
|
+
keys: [props.labelKey, 'suffix']
|
|
148
156
|
},
|
|
149
157
|
resultLimit: 12,
|
|
150
158
|
matchAllWhenSearchEmpty: true
|
|
@@ -175,8 +183,8 @@ function getGroupWithItems(group: G, items: (T & { matches?: FuseResult<T>['matc
|
|
|
175
183
|
items: items.slice(0, fuse.value.resultLimit).map((item) => {
|
|
176
184
|
return {
|
|
177
185
|
...item,
|
|
178
|
-
labelHtml: highlight<T>(item, searchTerm.value,
|
|
179
|
-
suffixHtml: highlight<T>(item, searchTerm.value, undefined, [
|
|
186
|
+
labelHtml: highlight<T>(item, searchTerm.value, props.labelKey),
|
|
187
|
+
suffixHtml: highlight<T>(item, searchTerm.value, undefined, [props.labelKey])
|
|
180
188
|
}
|
|
181
189
|
})
|
|
182
190
|
}
|
|
@@ -255,22 +263,23 @@ const groups = computed(() => {
|
|
|
255
263
|
|
|
256
264
|
<ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
|
|
257
265
|
<ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
|
258
|
-
<ComboboxLabel v-if="group.
|
|
259
|
-
{{ group.
|
|
266
|
+
<ComboboxLabel v-if="get(group, props.labelKey as string)" :class="ui.label({ class: props.ui?.label })">
|
|
267
|
+
{{ get(group, props.labelKey as string) }}
|
|
260
268
|
</ComboboxLabel>
|
|
261
269
|
|
|
262
270
|
<ComboboxItem
|
|
263
271
|
v-for="(item, index) in group.items"
|
|
264
272
|
:key="`group-${groupIndex}-${index}`"
|
|
265
|
-
:value="omit(item, ['matches' as any, 'group' as any, '
|
|
273
|
+
:value="omit(item, ['matches' as any, 'group' as any, 'onSelect', 'labelHtml', 'suffixHtml'])"
|
|
266
274
|
:disabled="item.disabled"
|
|
267
275
|
:class="ui.item({ class: props.ui?.item })"
|
|
268
|
-
@select="item.
|
|
276
|
+
@select="item.onSelect"
|
|
269
277
|
>
|
|
270
278
|
<slot :name="item.slot || group.slot || 'item'" :item="item" :index="index">
|
|
271
279
|
<slot :name="item.slot ? `${item.slot}-leading` : group.slot ? `${group.slot}-leading` : `item-leading`" :item="item" :index="index">
|
|
272
|
-
<
|
|
280
|
+
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon, loading: true })" />
|
|
273
281
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
|
|
282
|
+
<UAvatar v-else-if="item.avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
274
283
|
<UChip
|
|
275
284
|
v-else-if="item.chip"
|
|
276
285
|
:size="((props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()) as ChipProps['size'])"
|
|
@@ -281,11 +290,11 @@ const groups = computed(() => {
|
|
|
281
290
|
/>
|
|
282
291
|
</slot>
|
|
283
292
|
|
|
284
|
-
<span v-if="item.
|
|
293
|
+
<span v-if="item.labelHtml || get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`]" :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
|
285
294
|
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
|
|
286
295
|
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: props.ui?.itemLabelPrefix })">{{ item.prefix }}</span>
|
|
287
296
|
|
|
288
|
-
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase })" v-html="item.labelHtml || item.
|
|
297
|
+
<span :class="ui.itemLabelBase({ class: props.ui?.itemLabelBase })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
|
|
289
298
|
|
|
290
299
|
<span :class="ui.itemLabelSuffix({ class: props.ui?.itemLabelSuffix })" v-html="item.suffixHtml || item.suffix" />
|
|
291
300
|
</slot>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
4
|
-
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps
|
|
4
|
+
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'radix-vue'
|
|
5
5
|
import type { AppConfig } from '@nuxt/schema'
|
|
6
6
|
import _appConfig from '#build/app.config'
|
|
7
7
|
import theme from '#build/ui/context-menu'
|
|
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof
|
|
|
12
12
|
|
|
13
13
|
const contextMenu = tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu || {}) })
|
|
14
14
|
|
|
15
|
-
export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'
|
|
15
|
+
export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
|
|
16
16
|
label?: string
|
|
17
17
|
icon?: string
|
|
18
18
|
avatar?: AvatarProps
|
|
@@ -22,19 +22,33 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
|
|
|
22
22
|
* The item type.
|
|
23
23
|
* @defaultValue 'link'
|
|
24
24
|
*/
|
|
25
|
-
type?: 'label' | 'separator' | 'link'
|
|
25
|
+
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
|
26
26
|
slot?: string
|
|
27
|
+
loading?: boolean
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
checked?: boolean
|
|
27
30
|
open?: boolean
|
|
28
31
|
defaultOpen?: boolean
|
|
29
32
|
children?: ContextMenuItem[] | ContextMenuItem[][]
|
|
30
|
-
|
|
33
|
+
onSelect?(e: Event): void
|
|
34
|
+
onUpdateChecked?(checked: boolean): void
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
type ContextMenuVariants = VariantProps<typeof contextMenu>
|
|
34
38
|
|
|
35
|
-
export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'
|
|
39
|
+
export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
|
|
36
40
|
size?: ContextMenuVariants['size']
|
|
37
41
|
items?: T[] | T[][]
|
|
42
|
+
/**
|
|
43
|
+
* The icon displayed when an item is checked.
|
|
44
|
+
* @defaultValue appConfig.ui.icons.check
|
|
45
|
+
*/
|
|
46
|
+
checkedIcon?: string
|
|
47
|
+
/**
|
|
48
|
+
* The icon displayed when an item is loading.
|
|
49
|
+
* @defaultValue appConfig.ui.icons.loading
|
|
50
|
+
*/
|
|
51
|
+
loadingIcon?: string
|
|
38
52
|
/** The content of the menu. */
|
|
39
53
|
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
|
|
40
54
|
/**
|
|
@@ -42,6 +56,12 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'>,
|
|
|
42
56
|
* @defaultValue true
|
|
43
57
|
*/
|
|
44
58
|
portal?: boolean
|
|
59
|
+
/**
|
|
60
|
+
* The key used to get the label from the item.
|
|
61
|
+
* @defaultValue 'label'
|
|
62
|
+
*/
|
|
63
|
+
labelKey?: string
|
|
64
|
+
disabled?: boolean
|
|
45
65
|
class?: any
|
|
46
66
|
ui?: PartialString<typeof contextMenu.slots>
|
|
47
67
|
}
|
|
@@ -64,12 +84,13 @@ export type ContextMenuSlots<T extends { slot?: string }> = {
|
|
|
64
84
|
import { computed, toRef } from 'vue'
|
|
65
85
|
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'radix-vue'
|
|
66
86
|
import { reactivePick } from '@vueuse/core'
|
|
67
|
-
import UContextMenuContent from './ContextMenuContent.vue'
|
|
68
87
|
import { omit } from '../utils'
|
|
88
|
+
import UContextMenuContent from './ContextMenuContent.vue'
|
|
69
89
|
|
|
70
90
|
const props = withDefaults(defineProps<ContextMenuProps<T>>(), {
|
|
71
91
|
portal: true,
|
|
72
|
-
modal: true
|
|
92
|
+
modal: true,
|
|
93
|
+
labelKey: 'label'
|
|
73
94
|
})
|
|
74
95
|
const emits = defineEmits<ContextMenuEmits>()
|
|
75
96
|
const slots = defineSlots<ContextMenuSlots<T>>()
|
|
@@ -96,6 +117,9 @@ const ui = computed(() => contextMenu({
|
|
|
96
117
|
v-bind="contentProps"
|
|
97
118
|
:items="items"
|
|
98
119
|
:portal="portal"
|
|
120
|
+
:label-key="labelKey"
|
|
121
|
+
:checked-icon="checkedIcon"
|
|
122
|
+
:loading-icon="loadingIcon"
|
|
99
123
|
>
|
|
100
124
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
|
101
125
|
<slot :name="name" v-bind="slotData" />
|
|
@@ -10,6 +10,9 @@ interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps,
|
|
|
10
10
|
items?: T[] | T[][]
|
|
11
11
|
portal?: boolean
|
|
12
12
|
sub?: boolean
|
|
13
|
+
labelKey: string
|
|
14
|
+
checkedIcon?: string
|
|
15
|
+
loadingIcon?: string
|
|
13
16
|
class?: any
|
|
14
17
|
ui: typeof _contextMenu
|
|
15
18
|
uiOverride?: any
|
|
@@ -24,13 +27,13 @@ import { ContextMenu } from 'radix-vue/namespaced'
|
|
|
24
27
|
import { useForwardPropsEmits } from 'radix-vue'
|
|
25
28
|
import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
|
|
26
29
|
import { useAppConfig } from '#imports'
|
|
30
|
+
import { omit, get } from '../utils'
|
|
31
|
+
import { pickLinkProps } from '../utils/link'
|
|
27
32
|
import ULinkBase from './LinkBase.vue'
|
|
28
33
|
import ULink from './Link.vue'
|
|
29
34
|
import UAvatar from './Avatar.vue'
|
|
30
35
|
import UIcon from './Icon.vue'
|
|
31
36
|
import UKbd from './Kbd.vue'
|
|
32
|
-
import { omit } from '../utils'
|
|
33
|
-
import { pickLinkProps } from '../utils/link'
|
|
34
37
|
|
|
35
38
|
const props = defineProps<ContextMenuContentProps<T>>()
|
|
36
39
|
const emits = defineEmits<ContextMenuContentEmits>()
|
|
@@ -49,25 +52,30 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|
|
49
52
|
<DefineItemTemplate v-slot="{ item, active, index }">
|
|
50
53
|
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
|
51
54
|
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
|
52
|
-
<
|
|
55
|
+
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, loading: true })" />
|
|
53
56
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
|
|
57
|
+
<UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
|
|
54
58
|
</slot>
|
|
55
59
|
|
|
56
|
-
<span v-if="item.
|
|
60
|
+
<span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
|
|
57
61
|
<slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
|
|
58
|
-
{{ item.
|
|
62
|
+
{{ get(item, props.labelKey as string) }}
|
|
59
63
|
</slot>
|
|
60
64
|
|
|
61
65
|
<UIcon v-if="item.target === '_blank'" :name="appConfig.ui.icons.external" :class="ui.itemLabelExternalIcon({ class: uiOverride?.itemLabelExternalIcon, active })" />
|
|
62
66
|
</span>
|
|
63
67
|
|
|
64
|
-
<span
|
|
68
|
+
<span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
|
|
65
69
|
<slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
|
|
66
70
|
<UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, active })" />
|
|
67
71
|
<span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
|
|
68
72
|
<UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.uiOverride?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
|
|
69
73
|
</span>
|
|
70
74
|
</slot>
|
|
75
|
+
|
|
76
|
+
<ContextMenu.ItemIndicator as-child>
|
|
77
|
+
<UIcon :name="checkedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon })" />
|
|
78
|
+
</ContextMenu.ItemIndicator>
|
|
71
79
|
</span>
|
|
72
80
|
</slot>
|
|
73
81
|
</DefineItemTemplate>
|
|
@@ -85,7 +93,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|
|
85
93
|
as="button"
|
|
86
94
|
type="button"
|
|
87
95
|
:disabled="item.disabled"
|
|
88
|
-
:text-value="item.
|
|
96
|
+
:text-value="get(item, props.labelKey as string)"
|
|
89
97
|
:class="ui.item({ class: uiOverride?.item })"
|
|
90
98
|
>
|
|
91
99
|
<ReuseItemTemplate :item="item" :index="index" />
|
|
@@ -99,6 +107,9 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|
|
99
107
|
:portal="portal"
|
|
100
108
|
:items="item.children"
|
|
101
109
|
:align-offset="-4"
|
|
110
|
+
:label-key="labelKey"
|
|
111
|
+
:checked-icon="checkedIcon"
|
|
112
|
+
:loading-icon="loadingIcon"
|
|
102
113
|
v-bind="item.content"
|
|
103
114
|
>
|
|
104
115
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
|
@@ -106,7 +117,24 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|
|
106
117
|
</template>
|
|
107
118
|
</UContextMenuContent>
|
|
108
119
|
</ContextMenu.Sub>
|
|
109
|
-
<ContextMenu.
|
|
120
|
+
<ContextMenu.CheckboxItem
|
|
121
|
+
v-else-if="item.type === 'checkbox'"
|
|
122
|
+
:checked="item.checked"
|
|
123
|
+
:disabled="item.disabled"
|
|
124
|
+
:text-value="get(item, props.labelKey as string)"
|
|
125
|
+
:class="ui.item({ class: [uiOverride?.item, item.class] })"
|
|
126
|
+
@update:checked="item.onUpdateChecked"
|
|
127
|
+
@select="item.onSelect"
|
|
128
|
+
>
|
|
129
|
+
<ReuseItemTemplate :item="item" :index="index" />
|
|
130
|
+
</ContextMenu.CheckboxItem>
|
|
131
|
+
<ContextMenu.Item
|
|
132
|
+
v-else
|
|
133
|
+
as-child
|
|
134
|
+
:disabled="item.disabled"
|
|
135
|
+
:text-value="get(item, props.labelKey as string)"
|
|
136
|
+
@select="item.onSelect"
|
|
137
|
+
>
|
|
110
138
|
<ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item as Omit<ContextMenuItem, 'type'>)" custom>
|
|
111
139
|
<ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.class], active })">
|
|
112
140
|
<ReuseItemTemplate :item="item" :active="active" :index="index" />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- eslint-disable vue/block-tag-newline -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { tv, type VariantProps } from 'tailwind-variants'
|
|
4
|
-
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps
|
|
4
|
+
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'radix-vue'
|
|
5
5
|
import type { AppConfig } from '@nuxt/schema'
|
|
6
6
|
import _appConfig from '#build/app.config'
|
|
7
7
|
import theme from '#build/ui/dropdown-menu'
|
|
@@ -12,7 +12,7 @@ const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof
|
|
|
12
12
|
|
|
13
13
|
const dropdownMenu = tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu || {}) })
|
|
14
14
|
|
|
15
|
-
export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'
|
|
15
|
+
export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'> {
|
|
16
16
|
label?: string
|
|
17
17
|
icon?: string
|
|
18
18
|
avatar?: AvatarProps
|
|
@@ -22,19 +22,33 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
|
|
|
22
22
|
* The item type.
|
|
23
23
|
* @defaultValue 'link'
|
|
24
24
|
*/
|
|
25
|
-
type?: 'label' | 'separator' | 'link'
|
|
25
|
+
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
|
26
26
|
slot?: string
|
|
27
|
+
loading?: boolean
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
checked?: boolean
|
|
27
30
|
open?: boolean
|
|
28
31
|
defaultOpen?: boolean
|
|
29
32
|
children?: DropdownMenuItem[] | DropdownMenuItem[][]
|
|
30
|
-
|
|
33
|
+
onSelect?(e: Event): void
|
|
34
|
+
onUpdateChecked?(checked: boolean): void
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
|
|
34
38
|
|
|
35
|
-
export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'
|
|
39
|
+
export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'> {
|
|
36
40
|
size?: DropdownMenuVariants['size']
|
|
37
41
|
items?: T[] | T[][]
|
|
42
|
+
/**
|
|
43
|
+
* The icon displayed when an item is checked.
|
|
44
|
+
* @defaultValue appConfig.ui.icons.check
|
|
45
|
+
*/
|
|
46
|
+
checkedIcon?: string
|
|
47
|
+
/**
|
|
48
|
+
* The icon displayed when an item is loading.
|
|
49
|
+
* @defaultValue appConfig.ui.icons.loading
|
|
50
|
+
*/
|
|
51
|
+
loadingIcon?: string
|
|
38
52
|
/**
|
|
39
53
|
* The content of the menu.
|
|
40
54
|
* @defaultValue { side: 'bottom', sideOffset: 8 }
|
|
@@ -50,6 +64,12 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
|
|
|
50
64
|
* @defaultValue true
|
|
51
65
|
*/
|
|
52
66
|
portal?: boolean
|
|
67
|
+
/**
|
|
68
|
+
* The key used to get the label from the item.
|
|
69
|
+
* @defaultValue 'label'
|
|
70
|
+
*/
|
|
71
|
+
labelKey?: string
|
|
72
|
+
disabled?: boolean
|
|
53
73
|
class?: any
|
|
54
74
|
ui?: PartialString<typeof dropdownMenu.slots>
|
|
55
75
|
}
|
|
@@ -73,12 +93,13 @@ import { computed, toRef } from 'vue'
|
|
|
73
93
|
import { defu } from 'defu'
|
|
74
94
|
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'radix-vue'
|
|
75
95
|
import { reactivePick } from '@vueuse/core'
|
|
76
|
-
import UDropdownMenuContent from './DropdownMenuContent.vue'
|
|
77
96
|
import { omit } from '../utils'
|
|
97
|
+
import UDropdownMenuContent from './DropdownMenuContent.vue'
|
|
78
98
|
|
|
79
99
|
const props = withDefaults(defineProps<DropdownMenuProps<T>>(), {
|
|
80
100
|
portal: true,
|
|
81
|
-
modal: true
|
|
101
|
+
modal: true,
|
|
102
|
+
labelKey: 'label'
|
|
82
103
|
})
|
|
83
104
|
const emits = defineEmits<DropdownMenuEmits>()
|
|
84
105
|
const slots = defineSlots<DropdownMenuSlots<T>>()
|
|
@@ -106,6 +127,9 @@ const ui = computed(() => dropdownMenu({
|
|
|
106
127
|
v-bind="contentProps"
|
|
107
128
|
:items="items"
|
|
108
129
|
:portal="portal"
|
|
130
|
+
:label-key="labelKey"
|
|
131
|
+
:checked-icon="checkedIcon"
|
|
132
|
+
:loading-icon="loadingIcon"
|
|
109
133
|
>
|
|
110
134
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
|
111
135
|
<slot :name="name" v-bind="slotData" />
|