@nuxt/ui 3.0.2 → 3.1.1
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/.nuxt/ui/accordion.ts +2 -2
- package/.nuxt/ui/alert.ts +29 -29
- package/.nuxt/ui/avatar-group.ts +1 -1
- package/.nuxt/ui/avatar.ts +3 -3
- package/.nuxt/ui/badge.ts +33 -33
- package/.nuxt/ui/breadcrumb.ts +5 -5
- package/.nuxt/ui/button.ts +43 -43
- package/.nuxt/ui/calendar.ts +17 -17
- package/.nuxt/ui/card.ts +5 -5
- package/.nuxt/ui/carousel.ts +6 -6
- package/.nuxt/ui/checkbox-group.ts +61 -0
- package/.nuxt/ui/checkbox.ts +140 -27
- package/.nuxt/ui/chip.ts +8 -8
- package/.nuxt/ui/color-picker.ts +3 -3
- package/.nuxt/ui/command-palette.ts +14 -14
- package/.nuxt/ui/context-menu.ts +33 -33
- package/.nuxt/ui/drawer.ts +10 -10
- package/.nuxt/ui/dropdown-menu.ts +34 -34
- package/.nuxt/ui/form-field.ts +6 -6
- package/.nuxt/ui/index.ts +1 -0
- package/.nuxt/ui/input-menu.ts +42 -42
- package/.nuxt/ui/input-number.ts +20 -20
- package/.nuxt/ui/input.ts +23 -23
- package/.nuxt/ui/kbd.ts +4 -4
- package/.nuxt/ui/link.ts +3 -3
- package/.nuxt/ui/modal.ts +5 -5
- package/.nuxt/ui/navigation-menu.ts +78 -80
- package/.nuxt/ui/pin-input.ts +20 -20
- package/.nuxt/ui/popover.ts +2 -2
- package/.nuxt/ui/progress.ts +17 -17
- package/.nuxt/ui/radio-group.ts +250 -35
- package/.nuxt/ui/select-menu.ts +36 -33
- package/.nuxt/ui/select.ts +32 -32
- package/.nuxt/ui/separator.ts +8 -8
- package/.nuxt/ui/skeleton.ts +1 -1
- package/.nuxt/ui/slideover.ts +4 -4
- package/.nuxt/ui/slider.ts +16 -16
- package/.nuxt/ui/stepper.ts +18 -18
- package/.nuxt/ui/switch.ts +20 -20
- package/.nuxt/ui/table.ts +15 -15
- package/.nuxt/ui/tabs.ts +32 -32
- package/.nuxt/ui/textarea.ts +40 -35
- package/.nuxt/ui/toast.ts +24 -24
- package/.nuxt/ui/tooltip.ts +2 -2
- package/.nuxt/ui/tree.ts +18 -18
- package/.nuxt/ui.css +63 -12
- package/README.md +15 -0
- package/cli/templates.mjs +18 -17
- package/dist/module.json +5 -5
- package/dist/module.mjs +12 -7
- package/dist/runtime/components/Accordion.vue +36 -93
- package/dist/runtime/components/Accordion.vue.d.ts +75 -0
- package/dist/runtime/components/Alert.vue +37 -100
- package/dist/runtime/components/Alert.vue.d.ts +81 -0
- package/dist/runtime/components/App.vue +28 -34
- package/dist/runtime/components/App.vue.d.ts +24 -0
- package/dist/runtime/components/Avatar.vue +42 -75
- package/dist/runtime/components/Avatar.vue.d.ts +39 -0
- package/dist/runtime/components/AvatarGroup.vue +40 -77
- package/dist/runtime/components/AvatarGroup.vue.d.ts +31 -0
- package/dist/runtime/components/Badge.vue +33 -62
- package/dist/runtime/components/Badge.vue.d.ts +41 -0
- package/dist/runtime/components/Breadcrumb.vue +37 -90
- package/dist/runtime/components/Breadcrumb.vue.d.ts +64 -0
- package/dist/runtime/components/Button.vue +95 -104
- package/dist/runtime/components/Button.vue.d.ts +48 -0
- package/dist/runtime/components/ButtonGroup.vue +20 -46
- package/dist/runtime/components/ButtonGroup.vue.d.ts +34 -0
- package/dist/runtime/components/Calendar.vue +67 -150
- package/dist/runtime/components/Calendar.vue.d.ts +111 -0
- package/dist/runtime/components/Card.vue +18 -40
- package/dist/runtime/components/Card.vue.d.ts +29 -0
- package/dist/runtime/components/Carousel.vue +112 -234
- package/dist/runtime/components/Carousel.vue.d.ts +122 -0
- package/dist/runtime/components/Checkbox.vue +52 -93
- package/dist/runtime/components/Checkbox.vue.d.ts +73 -0
- package/dist/runtime/components/CheckboxGroup.vue +121 -0
- package/dist/runtime/components/CheckboxGroup.vue.d.ts +78 -0
- package/dist/runtime/components/Chip.vue +26 -69
- package/dist/runtime/components/Chip.vue.d.ts +57 -0
- package/dist/runtime/components/Collapsible.vue +22 -41
- package/dist/runtime/components/Collapsible.vue.d.ts +35 -0
- package/dist/runtime/components/ColorPicker.vue +111 -197
- package/dist/runtime/components/ColorPicker.vue.d.ts +48 -0
- package/dist/runtime/components/CommandPalette.vue +96 -237
- package/dist/runtime/components/CommandPalette.vue.d.ts +161 -0
- package/dist/runtime/components/Container.vue +15 -28
- package/dist/runtime/components/Container.vue.d.ts +18 -0
- package/dist/runtime/components/ContextMenu.vue +34 -133
- package/dist/runtime/components/ContextMenu.vue.d.ts +107 -0
- package/dist/runtime/components/ContextMenuContent.vue +73 -81
- package/dist/runtime/components/ContextMenuContent.vue.d.ts +65 -0
- package/dist/runtime/components/Drawer.vue +49 -83
- package/dist/runtime/components/Drawer.vue.d.ts +78 -0
- package/dist/runtime/components/DropdownMenu.vue +39 -142
- package/dist/runtime/components/DropdownMenu.vue.d.ts +117 -0
- package/dist/runtime/components/DropdownMenuContent.vue +78 -88
- package/dist/runtime/components/DropdownMenuContent.vue.d.ts +68 -0
- package/dist/runtime/components/Form.vue +135 -220
- package/dist/runtime/components/Form.vue.d.ts +69 -0
- package/dist/runtime/components/FormField.vue +35 -77
- package/dist/runtime/components/FormField.vue.d.ts +61 -0
- package/dist/runtime/components/Icon.vue +12 -19
- package/dist/runtime/components/Icon.vue.d.ts +8 -0
- package/dist/runtime/components/Input.vue +82 -140
- package/dist/runtime/components/Input.vue.d.ts +76 -0
- package/dist/runtime/components/InputMenu.vue +185 -332
- package/dist/runtime/components/InputMenu.vue.d.ts +324 -0
- package/dist/runtime/components/InputNumber.vue +71 -133
- package/dist/runtime/components/InputNumber.vue.d.ts +182 -0
- package/dist/runtime/components/Kbd.vue +20 -46
- package/dist/runtime/components/Kbd.vue.d.ts +34 -0
- package/dist/runtime/components/Link.vue +98 -176
- package/dist/runtime/components/Link.vue.d.ts +93 -0
- package/dist/runtime/components/LinkBase.vue +33 -42
- package/dist/runtime/components/LinkBase.vue.d.ts +25 -0
- package/dist/runtime/components/Modal.vue +53 -116
- package/dist/runtime/components/Modal.vue.d.ts +93 -0
- package/dist/runtime/components/NavigationMenu.vue +77 -209
- package/dist/runtime/components/NavigationMenu.vue.d.ts +139 -0
- package/dist/runtime/components/OverlayProvider.vue +14 -18
- package/dist/runtime/components/OverlayProvider.vue.d.ts +2 -0
- package/dist/runtime/components/Pagination.vue +46 -140
- package/dist/runtime/components/Pagination.vue.d.ts +119 -0
- package/dist/runtime/components/PinInput.vue +61 -78
- package/dist/runtime/components/PinInput.vue.d.ts +57 -0
- package/dist/runtime/components/Popover.vue +47 -84
- package/dist/runtime/components/Popover.vue.d.ts +63 -0
- package/dist/runtime/components/Progress.vue +70 -131
- package/dist/runtime/components/Progress.vue.d.ts +66 -0
- package/dist/runtime/components/RadioGroup.vue +70 -132
- package/dist/runtime/components/RadioGroup.vue.d.ts +91 -0
- package/dist/runtime/components/Select.vue +110 -224
- package/dist/runtime/components/Select.vue.d.ts +145 -0
- package/dist/runtime/components/SelectMenu.vue +161 -310
- package/dist/runtime/components/SelectMenu.vue.d.ts +196 -0
- package/dist/runtime/components/Separator.vue +29 -71
- package/dist/runtime/components/Separator.vue.d.ts +54 -0
- package/dist/runtime/components/Skeleton.vue +21 -23
- package/dist/runtime/components/Skeleton.vue.d.ts +17 -0
- package/dist/runtime/components/Slideover.vue +53 -120
- package/dist/runtime/components/Slideover.vue.d.ts +94 -0
- package/dist/runtime/components/Slider.vue +46 -84
- package/dist/runtime/components/Slider.vue.d.ts +52 -0
- package/dist/runtime/components/Stepper.vue +45 -115
- package/dist/runtime/components/Stepper.vue.d.ts +83 -0
- package/dist/runtime/components/Switch.vue +45 -93
- package/dist/runtime/components/Switch.vue.d.ts +70 -0
- package/dist/runtime/components/Table.vue +163 -267
- package/dist/runtime/components/Table.vue.d.ts +183 -0
- package/dist/runtime/components/Tabs.vue +41 -107
- package/dist/runtime/components/Tabs.vue.d.ts +88 -0
- package/dist/runtime/components/Textarea.vue +113 -158
- package/dist/runtime/components/Textarea.vue.d.ts +78 -0
- package/dist/runtime/components/Toast.vue +49 -110
- package/dist/runtime/components/Toast.vue.d.ts +157 -0
- package/dist/runtime/components/Toaster.vue +70 -107
- package/dist/runtime/components/Toaster.vue.d.ts +38 -0
- package/dist/runtime/components/Tooltip.vue +38 -65
- package/dist/runtime/components/Tooltip.vue.d.ts +50 -0
- package/dist/runtime/components/Tree.vue +59 -166
- package/dist/runtime/components/Tree.vue.d.ts +111 -0
- package/dist/runtime/composables/defineShortcuts.d.ts +1 -1
- package/dist/runtime/composables/defineShortcuts.js +6 -0
- package/dist/runtime/composables/useAvatarGroup.d.ts +1 -1
- package/dist/runtime/composables/useButtonGroup.d.ts +2 -2
- package/dist/runtime/composables/useFormField.d.ts +2 -2
- package/dist/runtime/composables/useKbd.d.ts +1 -0
- package/dist/runtime/composables/useKbd.js +4 -3
- package/dist/runtime/composables/useOverlay.d.ts +14 -8
- package/dist/runtime/composables/useOverlay.js +20 -7
- package/dist/runtime/composables/usePortal.d.ts +6 -0
- package/dist/runtime/composables/usePortal.js +17 -0
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/inertia/components/Link.vue +146 -0
- package/dist/runtime/inertia/components/Link.vue.d.ts +63 -0
- package/dist/runtime/inertia/stubs.d.ts +43 -0
- package/dist/runtime/inertia/stubs.js +82 -0
- package/dist/runtime/locale/bg.d.ts +3 -0
- package/dist/runtime/locale/bg.js +54 -0
- package/dist/runtime/locale/index.d.ts +5 -1
- package/dist/runtime/locale/index.js +5 -1
- package/dist/runtime/locale/kk.d.ts +3 -0
- package/dist/runtime/locale/kk.js +54 -0
- package/dist/runtime/locale/tj.d.ts +3 -0
- package/dist/runtime/locale/tj.js +54 -0
- package/dist/runtime/locale/ug_cn.d.ts +3 -0
- package/dist/runtime/locale/ug_cn.js +55 -0
- package/dist/runtime/types/form.d.ts +4 -1
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/tv.d.ts +53 -0
- package/dist/runtime/types/tv.js +0 -0
- package/dist/runtime/types/utils.d.ts +7 -17
- package/dist/runtime/types/utils.js +1 -0
- package/dist/runtime/utils/link.d.ts +3 -3
- package/dist/runtime/utils/link.js +1 -0
- package/dist/runtime/utils/tv.js +2 -2
- package/dist/runtime/vue/components/Icon.vue +7 -8
- package/dist/runtime/vue/components/Icon.vue.d.ts +5 -0
- package/dist/runtime/vue/components/Link.vue +118 -202
- package/dist/runtime/vue/components/Link.vue.d.ts +93 -0
- package/dist/runtime/vue/stubs.d.ts +2 -2
- package/dist/shared/{ui.BkNUP964.mjs → ui.CbtTMMzt.mjs} +631 -366
- package/dist/types.d.mts +3 -5
- package/dist/unplugin.d.mts +6 -2
- package/dist/unplugin.mjs +25 -4
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +2 -2
- package/package.json +96 -54
- package/dist/module.cjs +0 -62
- package/dist/module.d.cts +0 -45
- package/dist/module.d.ts +0 -45
- package/dist/shared/ui.BL-XB3u1.cjs +0 -3555
- package/dist/types.d.ts +0 -7
- package/dist/unplugin.cjs +0 -232
- package/dist/unplugin.d.cts +0 -62
- package/dist/unplugin.d.ts +0 -62
- package/dist/vite.cjs +0 -23
- package/dist/vite.d.cts +0 -15
- package/dist/vite.d.ts +0 -15
|
@@ -1,219 +1,82 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import
|
|
3
|
-
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps } from 'reka-ui'
|
|
4
|
-
import type { AppConfig } from '@nuxt/schema'
|
|
5
|
-
import _appConfig from '#build/app.config'
|
|
6
|
-
import theme from '#build/ui/select-menu'
|
|
7
|
-
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
|
8
|
-
import { tv } from '../utils/tv'
|
|
9
|
-
import type { AvatarProps, ChipProps, InputProps } from '../types'
|
|
10
|
-
import type {
|
|
11
|
-
AcceptableValue,
|
|
12
|
-
ArrayOrNested,
|
|
13
|
-
GetItemKeys,
|
|
14
|
-
GetItemValue,
|
|
15
|
-
GetModelValue,
|
|
16
|
-
GetModelValueEmits,
|
|
17
|
-
NestedItem,
|
|
18
|
-
PartialString,
|
|
19
|
-
EmitsToProps
|
|
20
|
-
} from '../types/utils'
|
|
21
|
-
|
|
22
|
-
const appConfigSelectMenu = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
|
|
23
|
-
|
|
24
|
-
const selectMenu = tv({ extend: tv(theme), ...(appConfigSelectMenu.ui?.selectMenu || {}) })
|
|
25
|
-
|
|
26
|
-
interface _SelectMenuItem {
|
|
27
|
-
label?: string
|
|
28
|
-
/**
|
|
29
|
-
* @IconifyIcon
|
|
30
|
-
*/
|
|
31
|
-
icon?: string
|
|
32
|
-
avatar?: AvatarProps
|
|
33
|
-
chip?: ChipProps
|
|
34
|
-
/**
|
|
35
|
-
* The item type.
|
|
36
|
-
* @defaultValue 'item'
|
|
37
|
-
*/
|
|
38
|
-
type?: 'label' | 'separator' | 'item'
|
|
39
|
-
value?: string | number
|
|
40
|
-
disabled?: boolean
|
|
41
|
-
onSelect?(e?: Event): void
|
|
42
|
-
[key: string]: any
|
|
43
|
-
}
|
|
44
|
-
export type SelectMenuItem = _SelectMenuItem | AcceptableValue | boolean
|
|
45
|
-
|
|
46
|
-
type SelectMenuVariants = VariantProps<typeof selectMenu>
|
|
47
|
-
|
|
48
|
-
export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
|
|
49
|
-
id?: string
|
|
50
|
-
/** The placeholder text when the select is empty. */
|
|
51
|
-
placeholder?: string
|
|
52
|
-
/**
|
|
53
|
-
* Whether to display the search input or not.
|
|
54
|
-
* Can be an object to pass additional props to the input.
|
|
55
|
-
* `{ placeholder: 'Search...', variant: 'none' }`{lang="ts-type"}
|
|
56
|
-
* @defaultValue true
|
|
57
|
-
*/
|
|
58
|
-
searchInput?: boolean | InputProps
|
|
59
|
-
/**
|
|
60
|
-
* @defaultValue 'primary'
|
|
61
|
-
*/
|
|
62
|
-
color?: SelectMenuVariants['color']
|
|
63
|
-
/**
|
|
64
|
-
* @defaultValue 'outline'
|
|
65
|
-
*/
|
|
66
|
-
variant?: SelectMenuVariants['variant']
|
|
67
|
-
/**
|
|
68
|
-
* @defaultValue 'md'
|
|
69
|
-
*/
|
|
70
|
-
size?: SelectMenuVariants['size']
|
|
71
|
-
required?: boolean
|
|
72
|
-
/**
|
|
73
|
-
* The icon displayed to open the menu.
|
|
74
|
-
* @defaultValue appConfig.ui.icons.chevronDown
|
|
75
|
-
* @IconifyIcon
|
|
76
|
-
*/
|
|
77
|
-
trailingIcon?: string
|
|
78
|
-
/**
|
|
79
|
-
* The icon displayed when an item is selected.
|
|
80
|
-
* @defaultValue appConfig.ui.icons.check
|
|
81
|
-
* @IconifyIcon
|
|
82
|
-
*/
|
|
83
|
-
selectedIcon?: string
|
|
84
|
-
/**
|
|
85
|
-
* The content of the menu.
|
|
86
|
-
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
|
|
87
|
-
*/
|
|
88
|
-
content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ComboboxContentEmits>>
|
|
89
|
-
/**
|
|
90
|
-
* Display an arrow alongside the menu.
|
|
91
|
-
* @defaultValue false
|
|
92
|
-
* @IconifyIcon
|
|
93
|
-
*/
|
|
94
|
-
arrow?: boolean | Omit<ComboboxArrowProps, 'as' | 'asChild'>
|
|
95
|
-
/**
|
|
96
|
-
* Render the menu in a portal.
|
|
97
|
-
* @defaultValue true
|
|
98
|
-
*/
|
|
99
|
-
portal?: boolean
|
|
100
|
-
/**
|
|
101
|
-
* When `items` is an array of objects, select the field to use as the value instead of the object itself.
|
|
102
|
-
* @defaultValue undefined
|
|
103
|
-
*/
|
|
104
|
-
valueKey?: VK
|
|
105
|
-
/**
|
|
106
|
-
* When `items` is an array of objects, select the field to use as the label.
|
|
107
|
-
* @defaultValue 'label'
|
|
108
|
-
*/
|
|
109
|
-
labelKey?: keyof NestedItem<T>
|
|
110
|
-
items?: T
|
|
111
|
-
/** The value of the SelectMenu when initially rendered. Use when you do not need to control the state of the SelectMenu. */
|
|
112
|
-
defaultValue?: GetModelValue<T, VK, M>
|
|
113
|
-
/** The controlled value of the SelectMenu. Can be binded-with with `v-model`. */
|
|
114
|
-
modelValue?: GetModelValue<T, VK, M>
|
|
115
|
-
/** Whether multiple options can be selected or not. */
|
|
116
|
-
multiple?: M & boolean
|
|
117
|
-
/** Highlight the ring color like a focus state. */
|
|
118
|
-
highlight?: boolean
|
|
119
|
-
/**
|
|
120
|
-
* Determines if custom user input that does not exist in options can be added.
|
|
121
|
-
* @defaultValue false
|
|
122
|
-
*/
|
|
123
|
-
createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
|
124
|
-
/**
|
|
125
|
-
* Fields to filter items by.
|
|
126
|
-
* @defaultValue [labelKey]
|
|
127
|
-
*/
|
|
128
|
-
filterFields?: string[]
|
|
129
|
-
/**
|
|
130
|
-
* When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
|
|
131
|
-
* @defaultValue false
|
|
132
|
-
*/
|
|
133
|
-
ignoreFilter?: boolean
|
|
134
|
-
class?: any
|
|
135
|
-
ui?: PartialString<typeof selectMenu.slots>
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export type SelectMenuEmits<A extends ArrayOrNested<SelectMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
|
|
139
|
-
change: [payload: Event]
|
|
140
|
-
blur: [payload: FocusEvent]
|
|
141
|
-
focus: [payload: FocusEvent]
|
|
142
|
-
create: [item: string]
|
|
143
|
-
/** Event handler when highlighted element changes. */
|
|
144
|
-
highlight: [payload: {
|
|
145
|
-
ref: HTMLElement
|
|
146
|
-
value: GetModelValue<A, VK, M>
|
|
147
|
-
} | undefined]
|
|
148
|
-
} & GetModelValueEmits<A, VK, M>
|
|
149
|
-
|
|
150
|
-
type SlotProps<T extends SelectMenuItem> = (props: { item: T, index: number }) => any
|
|
151
|
-
|
|
152
|
-
export interface SelectMenuSlots<
|
|
153
|
-
A extends ArrayOrNested<SelectMenuItem> = ArrayOrNested<SelectMenuItem>,
|
|
154
|
-
VK extends GetItemKeys<A> | undefined = undefined,
|
|
155
|
-
M extends boolean = false,
|
|
156
|
-
T extends NestedItem<A> = NestedItem<A>
|
|
157
|
-
> {
|
|
158
|
-
'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof selectMenu> }): any
|
|
159
|
-
'default'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean }): any
|
|
160
|
-
'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, ui: ReturnType<typeof selectMenu> }): any
|
|
161
|
-
'empty'(props: { searchTerm?: string }): any
|
|
162
|
-
'item': SlotProps<T>
|
|
163
|
-
'item-leading': SlotProps<T>
|
|
164
|
-
'item-label': SlotProps<T>
|
|
165
|
-
'item-trailing': SlotProps<T>
|
|
166
|
-
'create-item-label'(props: { item: string }): any
|
|
167
|
-
}
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui/select-menu";
|
|
168
3
|
</script>
|
|
169
4
|
|
|
170
|
-
<script setup
|
|
171
|
-
import { computed, toRef, toRaw } from
|
|
172
|
-
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope, useForwardPropsEmits, useFilter } from
|
|
173
|
-
import { defu } from
|
|
174
|
-
import { reactivePick, createReusableTemplate } from
|
|
175
|
-
import { useAppConfig } from
|
|
176
|
-
import { useButtonGroup } from
|
|
177
|
-
import { useComponentIcons } from
|
|
178
|
-
import { useFormField } from
|
|
179
|
-
import { useLocale } from
|
|
180
|
-
import {
|
|
181
|
-
import
|
|
182
|
-
import
|
|
183
|
-
import
|
|
184
|
-
import
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const props =
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
5
|
+
<script setup>
|
|
6
|
+
import { computed, toRef, toRaw } from "vue";
|
|
7
|
+
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope, useForwardPropsEmits, useFilter } from "reka-ui";
|
|
8
|
+
import { defu } from "defu";
|
|
9
|
+
import { reactivePick, createReusableTemplate } from "@vueuse/core";
|
|
10
|
+
import { useAppConfig } from "#imports";
|
|
11
|
+
import { useButtonGroup } from "../composables/useButtonGroup";
|
|
12
|
+
import { useComponentIcons } from "../composables/useComponentIcons";
|
|
13
|
+
import { useFormField } from "../composables/useFormField";
|
|
14
|
+
import { useLocale } from "../composables/useLocale";
|
|
15
|
+
import { usePortal } from "../composables/usePortal";
|
|
16
|
+
import { compare, get, isArrayOfArray } from "../utils";
|
|
17
|
+
import { tv } from "../utils/tv";
|
|
18
|
+
import UIcon from "./Icon.vue";
|
|
19
|
+
import UAvatar from "./Avatar.vue";
|
|
20
|
+
import UChip from "./Chip.vue";
|
|
21
|
+
import UInput from "./Input.vue";
|
|
22
|
+
defineOptions({ inheritAttrs: false });
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
id: { type: String, required: false },
|
|
25
|
+
placeholder: { type: String, required: false },
|
|
26
|
+
searchInput: { type: [Boolean, Object], required: false, default: true },
|
|
27
|
+
color: { type: null, required: false },
|
|
28
|
+
variant: { type: null, required: false },
|
|
29
|
+
size: { type: null, required: false },
|
|
30
|
+
required: { type: Boolean, required: false },
|
|
31
|
+
trailingIcon: { type: String, required: false },
|
|
32
|
+
selectedIcon: { type: String, required: false },
|
|
33
|
+
content: { type: Object, required: false },
|
|
34
|
+
arrow: { type: [Boolean, Object], required: false },
|
|
35
|
+
portal: { type: [Boolean, String], required: false, skipCheck: true, default: true },
|
|
36
|
+
valueKey: { type: null, required: false },
|
|
37
|
+
labelKey: { type: null, required: false, default: "label" },
|
|
38
|
+
items: { type: null, required: false },
|
|
39
|
+
defaultValue: { type: null, required: false },
|
|
40
|
+
modelValue: { type: null, required: false },
|
|
41
|
+
multiple: { type: Boolean, required: false },
|
|
42
|
+
highlight: { type: Boolean, required: false },
|
|
43
|
+
createItem: { type: [Boolean, String, Object], required: false },
|
|
44
|
+
filterFields: { type: Array, required: false },
|
|
45
|
+
ignoreFilter: { type: Boolean, required: false },
|
|
46
|
+
class: { type: null, required: false },
|
|
47
|
+
ui: { type: null, required: false },
|
|
48
|
+
open: { type: Boolean, required: false },
|
|
49
|
+
defaultOpen: { type: Boolean, required: false },
|
|
50
|
+
disabled: { type: Boolean, required: false },
|
|
51
|
+
name: { type: String, required: false },
|
|
52
|
+
resetSearchTermOnBlur: { type: Boolean, required: false, default: true },
|
|
53
|
+
resetSearchTermOnSelect: { type: Boolean, required: false, default: true },
|
|
54
|
+
highlightOnHover: { type: Boolean, required: false },
|
|
55
|
+
icon: { type: String, required: false },
|
|
56
|
+
avatar: { type: Object, required: false },
|
|
57
|
+
leading: { type: Boolean, required: false },
|
|
58
|
+
leadingIcon: { type: String, required: false },
|
|
59
|
+
trailing: { type: Boolean, required: false },
|
|
60
|
+
loading: { type: Boolean, required: false },
|
|
61
|
+
loadingIcon: { type: String, required: false }
|
|
62
|
+
});
|
|
63
|
+
const emits = defineEmits(["update:open", "change", "blur", "focus", "create", "highlight", "update:modelValue"]);
|
|
64
|
+
const slots = defineSlots();
|
|
65
|
+
const searchTerm = defineModel("searchTerm", { type: String, ...{ default: "" } });
|
|
66
|
+
const { t } = useLocale();
|
|
67
|
+
const appConfig = useAppConfig();
|
|
68
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
|
69
|
+
const rootProps = useForwardPropsEmits(reactivePick(props, "modelValue", "defaultValue", "open", "defaultOpen", "required", "multiple", "resetSearchTermOnBlur", "resetSearchTermOnSelect", "highlightOnHover"), emits);
|
|
70
|
+
const portalProps = usePortal(toRef(() => props.portal));
|
|
71
|
+
const contentProps = toRef(() => defu(props.content, { side: "bottom", sideOffset: 8, collisionPadding: 8, position: "popper" }));
|
|
72
|
+
const arrowProps = toRef(() => props.arrow);
|
|
73
|
+
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t("selectMenu.search"), variant: "none" }));
|
|
74
|
+
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField(props);
|
|
75
|
+
const { orientation, size: buttonGroupSize } = useButtonGroup(props);
|
|
76
|
+
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })));
|
|
77
|
+
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value);
|
|
78
|
+
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate();
|
|
79
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.selectMenu || {} })({
|
|
217
80
|
color: color.value,
|
|
218
81
|
variant: props.variant,
|
|
219
82
|
size: selectSize?.value,
|
|
@@ -222,112 +85,96 @@ const ui = computed(() => selectMenu({
|
|
|
222
85
|
leading: isLeading.value || !!props.avatar || !!slots.leading,
|
|
223
86
|
trailing: isTrailing.value || !!slots.trailing,
|
|
224
87
|
buttonGroup: orientation.value
|
|
225
|
-
}))
|
|
226
|
-
|
|
227
|
-
function displayValue(value: GetItemValue<T, VK> | GetItemValue<T, VK>[]): string {
|
|
88
|
+
}));
|
|
89
|
+
function displayValue(value) {
|
|
228
90
|
if (props.multiple && Array.isArray(value)) {
|
|
229
|
-
return value.map(v => displayValue(v)).filter(Boolean).join(
|
|
91
|
+
return value.map((v) => displayValue(v)).filter(Boolean).join(", ");
|
|
230
92
|
}
|
|
231
|
-
|
|
232
93
|
if (!props.valueKey) {
|
|
233
|
-
return value && (typeof value ===
|
|
94
|
+
return value && (typeof value === "object" ? get(value, props.labelKey) : value);
|
|
234
95
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
|
96
|
+
const item = items.value.find((item2) => compare(typeof item2 === "object" ? get(item2, props.valueKey) : item2, value));
|
|
97
|
+
return item && (typeof item === "object" ? get(item, props.labelKey) : item);
|
|
238
98
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
? props.items
|
|
244
|
-
: [props.items]
|
|
245
|
-
: []
|
|
246
|
-
)
|
|
247
|
-
// eslint-disable-next-line vue/no-dupe-keys
|
|
248
|
-
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
|
249
|
-
|
|
99
|
+
const groups = computed(
|
|
100
|
+
() => props.items?.length ? isArrayOfArray(props.items) ? props.items : [props.items] : []
|
|
101
|
+
);
|
|
102
|
+
const items = computed(() => groups.value.flatMap((group) => group));
|
|
250
103
|
const filteredGroups = computed(() => {
|
|
251
104
|
if (props.ignoreFilter || !searchTerm.value) {
|
|
252
|
-
return groups.value
|
|
105
|
+
return groups.value;
|
|
253
106
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (typeof item !== 'object' || item === null) {
|
|
259
|
-
return contains(String(item), searchTerm.value)
|
|
107
|
+
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey];
|
|
108
|
+
return groups.value.map((items2) => items2.filter((item) => {
|
|
109
|
+
if (typeof item !== "object" || item === null) {
|
|
110
|
+
return contains(String(item), searchTerm.value);
|
|
260
111
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return true
|
|
112
|
+
if (item.type && ["label", "separator"].includes(item.type)) {
|
|
113
|
+
return true;
|
|
264
114
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const filteredItems = computed(() => filteredGroups.value.flatMap(group => group))
|
|
272
|
-
|
|
115
|
+
return fields.some((field) => contains(get(item, field), searchTerm.value));
|
|
116
|
+
})).filter((group) => group.filter(
|
|
117
|
+
(item) => !isSelectItem(item) || (!item.type || !["label", "separator"].includes(item.type))
|
|
118
|
+
).length > 0);
|
|
119
|
+
});
|
|
120
|
+
const filteredItems = computed(() => filteredGroups.value.flatMap((group) => group));
|
|
273
121
|
const createItem = computed(() => {
|
|
274
122
|
if (!props.createItem || !searchTerm.value) {
|
|
275
|
-
return false
|
|
123
|
+
return false;
|
|
276
124
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
|
|
281
|
-
return !filteredItems.value.find(item => compare(item, newItem, String(props.valueKey)))
|
|
125
|
+
const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } : searchTerm.value;
|
|
126
|
+
if (typeof props.createItem === "object" && props.createItem.when === "always" || props.createItem === "always") {
|
|
127
|
+
return !filteredItems.value.find((item) => compare(item, newItem, props.valueKey));
|
|
282
128
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
function onUpdate(value: any) {
|
|
129
|
+
return !filteredItems.value.length;
|
|
130
|
+
});
|
|
131
|
+
const createItemPosition = computed(() => typeof props.createItem === "object" ? props.createItem.position : "bottom");
|
|
132
|
+
function onUpdate(value) {
|
|
289
133
|
if (toRaw(props.modelValue) === value) {
|
|
290
|
-
return
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const event = new Event("change", { target: { value } });
|
|
137
|
+
emits("change", event);
|
|
138
|
+
emitFormChange();
|
|
139
|
+
emitFormInput();
|
|
140
|
+
if (props.resetSearchTermOnSelect) {
|
|
141
|
+
searchTerm.value = "";
|
|
291
142
|
}
|
|
292
|
-
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
|
293
|
-
const event = new Event('change', { target: { value } })
|
|
294
|
-
emits('change', event)
|
|
295
|
-
emitFormChange()
|
|
296
|
-
emitFormInput()
|
|
297
143
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
let timeoutId
|
|
301
|
-
|
|
144
|
+
function onUpdateOpen(value) {
|
|
145
|
+
let timeoutId;
|
|
302
146
|
if (!value) {
|
|
303
|
-
const event = new FocusEvent(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
emitFormBlur()
|
|
307
|
-
|
|
308
|
-
// Since we use `displayValue` prop inside ComboboxInput we should reset searchTerm manually
|
|
309
|
-
// https://reka-ui.com/docs/components/combobox#api-reference
|
|
147
|
+
const event = new FocusEvent("blur");
|
|
148
|
+
emits("blur", event);
|
|
149
|
+
emitFormBlur();
|
|
310
150
|
if (props.resetSearchTermOnBlur) {
|
|
311
|
-
const STATE_ANIMATION_DELAY_MS = 100
|
|
312
|
-
|
|
151
|
+
const STATE_ANIMATION_DELAY_MS = 100;
|
|
313
152
|
timeoutId = setTimeout(() => {
|
|
314
|
-
searchTerm.value =
|
|
315
|
-
}, STATE_ANIMATION_DELAY_MS)
|
|
153
|
+
searchTerm.value = "";
|
|
154
|
+
}, STATE_ANIMATION_DELAY_MS);
|
|
316
155
|
}
|
|
317
156
|
} else {
|
|
318
|
-
const event = new FocusEvent(
|
|
319
|
-
emits(
|
|
320
|
-
emitFormFocus()
|
|
321
|
-
clearTimeout(timeoutId)
|
|
157
|
+
const event = new FocusEvent("focus");
|
|
158
|
+
emits("focus", event);
|
|
159
|
+
emitFormFocus();
|
|
160
|
+
clearTimeout(timeoutId);
|
|
322
161
|
}
|
|
323
162
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
163
|
+
function onSelect(e, item) {
|
|
164
|
+
if (!isSelectItem(item)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (item.disabled) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
item.onSelect?.(e);
|
|
172
|
+
}
|
|
173
|
+
function isSelectItem(item) {
|
|
174
|
+
return typeof item === "object" && item !== null;
|
|
327
175
|
}
|
|
328
176
|
</script>
|
|
329
177
|
|
|
330
|
-
<!-- eslint-disable vue/no-template-shadow -->
|
|
331
178
|
<template>
|
|
332
179
|
<DefineCreateItemTemplate>
|
|
333
180
|
<ComboboxGroup :class="ui.group({ class: props.ui?.group })">
|
|
@@ -359,34 +206,36 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
|
|
359
206
|
<ComboboxAnchor as-child>
|
|
360
207
|
<ComboboxTrigger :class="ui.base({ class: [props.class, props.ui?.base] })" tabindex="0">
|
|
361
208
|
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
|
|
362
|
-
<slot name="leading" :model-value="
|
|
209
|
+
<slot name="leading" :model-value="modelValue" :open="open" :ui="ui">
|
|
363
210
|
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
|
364
|
-
<UAvatar v-else-if="!!avatar" :size="
|
|
211
|
+
<UAvatar v-else-if="!!avatar" :size="props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
365
212
|
</slot>
|
|
366
213
|
</span>
|
|
367
214
|
|
|
368
|
-
<slot :model-value="
|
|
369
|
-
<template v-for="displayedModelValue in [displayValue(modelValue
|
|
215
|
+
<slot :model-value="modelValue" :open="open">
|
|
216
|
+
<template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
|
|
370
217
|
<span v-if="displayedModelValue" :class="ui.value({ class: props.ui?.value })">
|
|
371
218
|
{{ displayedModelValue }}
|
|
372
219
|
</span>
|
|
373
220
|
<span v-else :class="ui.placeholder({ class: props.ui?.placeholder })">
|
|
374
|
-
{{ placeholder ?? '
|
|
221
|
+
{{ placeholder ?? '\xA0' }}
|
|
375
222
|
</span>
|
|
376
223
|
</template>
|
|
377
224
|
</slot>
|
|
378
225
|
|
|
379
226
|
<span v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
|
|
380
|
-
<slot name="trailing" :model-value="
|
|
227
|
+
<slot name="trailing" :model-value="modelValue" :open="open" :ui="ui">
|
|
381
228
|
<UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
|
382
229
|
</slot>
|
|
383
230
|
</span>
|
|
384
231
|
</ComboboxTrigger>
|
|
385
232
|
</ComboboxAnchor>
|
|
386
233
|
|
|
387
|
-
<ComboboxPortal
|
|
234
|
+
<ComboboxPortal v-bind="portalProps">
|
|
388
235
|
<ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
|
|
389
236
|
<FocusScope trapped :class="ui.focusScope({ class: props.ui?.focusScope })">
|
|
237
|
+
<slot name="content-top" />
|
|
238
|
+
|
|
390
239
|
<ComboboxInput v-if="!!searchInput" v-model="searchTerm" :display-value="() => searchTerm" as-child>
|
|
391
240
|
<UInput autofocus autocomplete="off" v-bind="searchInputProps" :class="ui.input({ class: props.ui?.input })" />
|
|
392
241
|
</ComboboxInput>
|
|
@@ -403,7 +252,7 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
|
|
403
252
|
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
|
|
404
253
|
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
|
405
254
|
<ComboboxLabel v-if="isSelectItem(item) && item.type === 'label'" :class="ui.label({ class: props.ui?.label })">
|
|
406
|
-
{{ get(item, props.labelKey
|
|
255
|
+
{{ get(item, props.labelKey) }}
|
|
407
256
|
</ComboboxLabel>
|
|
408
257
|
|
|
409
258
|
<ComboboxSeparator v-else-if="isSelectItem(item) && item.type === 'separator'" :class="ui.separator({ class: props.ui?.separator })" />
|
|
@@ -412,16 +261,16 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
|
|
412
261
|
v-else
|
|
413
262
|
:class="ui.item({ class: props.ui?.item })"
|
|
414
263
|
:disabled="isSelectItem(item) && item.disabled"
|
|
415
|
-
:value="props.valueKey && isSelectItem(item) ? get(item, props.valueKey
|
|
416
|
-
@select="
|
|
264
|
+
:value="props.valueKey && isSelectItem(item) ? get(item, props.valueKey) : item"
|
|
265
|
+
@select="onSelect($event, item)"
|
|
417
266
|
>
|
|
418
|
-
<slot name="item" :item="
|
|
419
|
-
<slot name="item-leading" :item="
|
|
267
|
+
<slot name="item" :item="item" :index="index">
|
|
268
|
+
<slot name="item-leading" :item="item" :index="index">
|
|
420
269
|
<UIcon v-if="isSelectItem(item) && item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: props.ui?.itemLeadingIcon })" />
|
|
421
|
-
<UAvatar v-else-if="isSelectItem(item) && item.avatar" :size="
|
|
270
|
+
<UAvatar v-else-if="isSelectItem(item) && item.avatar" :size="props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
|
|
422
271
|
<UChip
|
|
423
272
|
v-else-if="isSelectItem(item) && item.chip"
|
|
424
|
-
:size="
|
|
273
|
+
:size="props.ui?.itemLeadingChipSize || ui.itemLeadingChipSize()"
|
|
425
274
|
inset
|
|
426
275
|
standalone
|
|
427
276
|
v-bind="item.chip"
|
|
@@ -430,13 +279,13 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
|
|
430
279
|
</slot>
|
|
431
280
|
|
|
432
281
|
<span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
|
|
433
|
-
<slot name="item-label" :item="
|
|
434
|
-
{{ isSelectItem(item) ? get(item, props.labelKey
|
|
282
|
+
<slot name="item-label" :item="item" :index="index">
|
|
283
|
+
{{ isSelectItem(item) ? get(item, props.labelKey) : item }}
|
|
435
284
|
</slot>
|
|
436
285
|
</span>
|
|
437
286
|
|
|
438
287
|
<span :class="ui.itemTrailing({ class: props.ui?.itemTrailing })">
|
|
439
|
-
<slot name="item-trailing" :item="
|
|
288
|
+
<slot name="item-trailing" :item="item" :index="index" />
|
|
440
289
|
|
|
441
290
|
<ComboboxItemIndicator as-child>
|
|
442
291
|
<UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: props.ui?.itemTrailingIcon })" />
|
|
@@ -449,6 +298,8 @@ function isSelectItem(item: SelectMenuItem): item is _SelectMenuItem {
|
|
|
449
298
|
|
|
450
299
|
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
|
|
451
300
|
</ComboboxViewport>
|
|
301
|
+
|
|
302
|
+
<slot name="content-bottom" />
|
|
452
303
|
</FocusScope>
|
|
453
304
|
|
|
454
305
|
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
|