@bitrix24/b24ui-nuxt 0.1.6 → 0.1.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/.nuxt/b24ui/index.ts +1 -0
- package/.nuxt/b24ui/select-menu.ts +517 -0
- package/.nuxt/b24ui/select.ts +4 -4
- package/dist/meta.cjs +5131 -212
- package/dist/meta.d.cts +5131 -212
- package/dist/meta.d.mts +5131 -212
- package/dist/meta.d.ts +5131 -212
- package/dist/meta.mjs +5131 -212
- package/dist/module.cjs +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/App.vue +2 -3
- package/dist/runtime/components/Button.vue +1 -3
- package/dist/runtime/components/SelectMenu.vue +465 -0
- package/dist/runtime/composables/useComponentIcons.d.ts +2 -2
- package/dist/runtime/types/index.d.ts +2 -0
- package/dist/runtime/types/index.js +2 -0
- package/dist/shared/{b24ui-nuxt.BAQG__ma.cjs → b24ui-nuxt.ZUYaG6CJ.cjs} +40 -5
- package/dist/shared/{b24ui-nuxt.n3bAiAAD.mjs → b24ui-nuxt.vQRZieQw.mjs} +41 -6
- package/dist/unplugin.cjs +1 -1
- package/dist/unplugin.mjs +1 -1
- package/dist/vite.cjs +1 -1
- package/dist/vite.mjs +1 -1
- package/package.json +6 -2
package/dist/module.cjs
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defu } from 'defu';
|
|
2
2
|
import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addComponentsDir, addImportsDir, hasNuxtModule, installModule } from '@nuxt/kit';
|
|
3
|
-
import { d as defaultOptions, a as getDefaultUiConfig, b as addTemplates } from './shared/b24ui-nuxt.
|
|
3
|
+
import { d as defaultOptions, a as getDefaultUiConfig, b as addTemplates } from './shared/b24ui-nuxt.vQRZieQw.mjs';
|
|
4
4
|
import 'node:url';
|
|
5
5
|
import 'scule';
|
|
6
6
|
|
|
@@ -44,9 +44,8 @@ provide(localeContextInjectionKey, locale)
|
|
|
44
44
|
<slot />
|
|
45
45
|
</B24Toaster>
|
|
46
46
|
<slot v-else />
|
|
47
|
+
<!-- B24ModalProvider / -->
|
|
48
|
+
<!-- B24SlideoverProvider / -->
|
|
47
49
|
</TooltipProvider>
|
|
48
|
-
|
|
49
|
-
<!-- B24ModalProvider / -->
|
|
50
|
-
<!-- B24SlideoverProvider / -->
|
|
51
50
|
</ConfigProvider>
|
|
52
51
|
</template>
|
|
@@ -130,10 +130,8 @@ const b24ui = computed(() => button({
|
|
|
130
130
|
@click="onClickWrapper"
|
|
131
131
|
>
|
|
132
132
|
<div
|
|
133
|
+
v-if="isLoading"
|
|
133
134
|
class="h-full w-full absolute inset-0 flex flex-row flex-nowrap items-center justify-center"
|
|
134
|
-
:class="[
|
|
135
|
-
isLoading ? 'visible' : 'invisible'
|
|
136
|
-
]"
|
|
137
135
|
>
|
|
138
136
|
<LoaderWaitIcon v-if="useWait" class="size-2xl" aria-hidden="true" />
|
|
139
137
|
<LoaderClockIcon v-else-if="useClock" class="size-2xl" aria-hidden="true" />
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { VariantProps } from 'tailwind-variants'
|
|
3
|
+
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
|
|
4
|
+
import type { AppConfig } from '@nuxt/schema'
|
|
5
|
+
import _appConfig from '#build/app.config'
|
|
6
|
+
import theme from '#build/b24ui/select-menu'
|
|
7
|
+
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
|
|
8
|
+
import { tv } from '../utils/tv'
|
|
9
|
+
import type { AvatarProps, ChipProps, InputProps, IconComponent } from '../types'
|
|
10
|
+
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
|
|
11
|
+
|
|
12
|
+
const appConfigSelectMenu = _appConfig as AppConfig & { b24ui: { selectMenu: Partial<typeof theme> } }
|
|
13
|
+
|
|
14
|
+
const selectMenu = tv({ extend: tv(theme), ...(appConfigSelectMenu.b24ui?.selectMenu || {}) })
|
|
15
|
+
|
|
16
|
+
export interface SelectMenuItem {
|
|
17
|
+
label?: string
|
|
18
|
+
icon?: IconComponent
|
|
19
|
+
avatar?: AvatarProps
|
|
20
|
+
chip?: ChipProps
|
|
21
|
+
/**
|
|
22
|
+
* The item type.
|
|
23
|
+
* @defaultValue 'item'
|
|
24
|
+
*/
|
|
25
|
+
type?: 'label' | 'separator' | 'item'
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
onSelect?(e?: Event): void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type SelectMenuVariants = VariantProps<typeof selectMenu>
|
|
31
|
+
|
|
32
|
+
export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
|
|
33
|
+
id?: string
|
|
34
|
+
/** The placeholder text when the select is empty. */
|
|
35
|
+
placeholder?: string
|
|
36
|
+
/**
|
|
37
|
+
* Whether to display the search input or not.
|
|
38
|
+
* Can be an object to pass additional props to the input.
|
|
39
|
+
* `{ placeholder: 'Search...', type: 'search' }`{lang="ts-type"}
|
|
40
|
+
* @defaultValue true
|
|
41
|
+
*/
|
|
42
|
+
searchInput?: boolean | InputProps
|
|
43
|
+
color?: SelectMenuVariants['color']
|
|
44
|
+
size?: SelectMenuVariants['size']
|
|
45
|
+
/** Removes padding from input. */
|
|
46
|
+
noPadding?: boolean
|
|
47
|
+
/** removes all borders (rings). */
|
|
48
|
+
noBorder?: boolean
|
|
49
|
+
/** removes all borders (rings) except the bottom one. */
|
|
50
|
+
underline?: boolean
|
|
51
|
+
/** Rounds the corners of the button. */
|
|
52
|
+
rounded?: boolean
|
|
53
|
+
tag?: string
|
|
54
|
+
tagColor?: SelectMenuVariants['tagColor']
|
|
55
|
+
required?: boolean
|
|
56
|
+
/**
|
|
57
|
+
* The icon displayed to open the menu.
|
|
58
|
+
* @defaultValue icons.chevronDown = `ChevronDownIcon`
|
|
59
|
+
*/
|
|
60
|
+
trailingIcon?: IconComponent
|
|
61
|
+
/**
|
|
62
|
+
* The icon displayed when an item is selected.
|
|
63
|
+
* @defaultValue icons.check = `CheckIcon`
|
|
64
|
+
*/
|
|
65
|
+
selectedIcon?: IconComponent
|
|
66
|
+
/**
|
|
67
|
+
* The content of the menu.
|
|
68
|
+
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
|
|
69
|
+
*/
|
|
70
|
+
content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'>
|
|
71
|
+
/**
|
|
72
|
+
* Display an arrow alongside the menu.
|
|
73
|
+
* @defaultValue false
|
|
74
|
+
*/
|
|
75
|
+
arrow?: boolean | Omit<ComboboxArrowProps, 'as' | 'asChild'>
|
|
76
|
+
/**
|
|
77
|
+
* Render the menu in a portal.
|
|
78
|
+
* @defaultValue true
|
|
79
|
+
*/
|
|
80
|
+
portal?: boolean
|
|
81
|
+
/**
|
|
82
|
+
* When `items` is an array of objects, select the field to use as the value instead of the object itself.
|
|
83
|
+
* @defaultValue undefined
|
|
84
|
+
*/
|
|
85
|
+
valueKey?: V
|
|
86
|
+
/**
|
|
87
|
+
* When `items` is an array of objects, select the field to use as the label.
|
|
88
|
+
* @defaultValue 'label'
|
|
89
|
+
*/
|
|
90
|
+
labelKey?: V
|
|
91
|
+
items?: I
|
|
92
|
+
/** The value of the SelectMenu when initially rendered. Use when you do not need to control the state of the SelectMenu. */
|
|
93
|
+
defaultValue?: SelectModelValue<T, V, M>
|
|
94
|
+
/** The controlled value of the SelectMenu. Can be binded-with with `v-model`. */
|
|
95
|
+
modelValue?: SelectModelValue<T, V, M>
|
|
96
|
+
/** Whether multiple options can be selected or not. */
|
|
97
|
+
multiple?: M & boolean
|
|
98
|
+
/** Highlight the ring color like a focus state. */
|
|
99
|
+
highlight?: boolean
|
|
100
|
+
/**
|
|
101
|
+
* Determines if custom user input that does not exist in options can be added.
|
|
102
|
+
* @defaultValue false
|
|
103
|
+
*/
|
|
104
|
+
createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
|
|
105
|
+
/**
|
|
106
|
+
* Fields to filter items by.
|
|
107
|
+
* @defaultValue [labelKey]
|
|
108
|
+
*/
|
|
109
|
+
filterFields?: string[]
|
|
110
|
+
/**
|
|
111
|
+
* When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
|
|
112
|
+
* @defaultValue false
|
|
113
|
+
*/
|
|
114
|
+
ignoreFilter?: boolean
|
|
115
|
+
class?: any
|
|
116
|
+
b24ui?: PartialString<typeof selectMenu.slots>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type SelectMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
|
|
120
|
+
change: [payload: Event]
|
|
121
|
+
blur: [payload: FocusEvent]
|
|
122
|
+
focus: [payload: FocusEvent]
|
|
123
|
+
create: [item: string]
|
|
124
|
+
} & SelectModelValueEmits<T, V, M>
|
|
125
|
+
|
|
126
|
+
type SlotProps<T> = (props: { item: T, index: number }) => any
|
|
127
|
+
|
|
128
|
+
export interface SelectMenuSlots<T, M extends boolean> {
|
|
129
|
+
'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
|
|
130
|
+
'default'(props: { modelValue?: M extends true ? T[] : T, open: boolean }): any
|
|
131
|
+
'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
|
|
132
|
+
'empty'(props: { searchTerm?: string }): any
|
|
133
|
+
'item': SlotProps<T>
|
|
134
|
+
'item-leading': SlotProps<T>
|
|
135
|
+
'item-label': SlotProps<T>
|
|
136
|
+
'item-trailing': SlotProps<T>
|
|
137
|
+
'create-item-label'(props: { item: string }): any
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<SelectMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
|
|
142
|
+
import { computed, toRef, toRaw } from 'vue'
|
|
143
|
+
import {
|
|
144
|
+
ComboboxRoot,
|
|
145
|
+
ComboboxArrow,
|
|
146
|
+
ComboboxAnchor,
|
|
147
|
+
ComboboxInput,
|
|
148
|
+
ComboboxTrigger,
|
|
149
|
+
ComboboxPortal,
|
|
150
|
+
ComboboxContent,
|
|
151
|
+
ComboboxViewport,
|
|
152
|
+
ComboboxEmpty,
|
|
153
|
+
ComboboxGroup,
|
|
154
|
+
ComboboxLabel,
|
|
155
|
+
ComboboxSeparator,
|
|
156
|
+
ComboboxItem,
|
|
157
|
+
ComboboxItemIndicator,
|
|
158
|
+
useForwardPropsEmits,
|
|
159
|
+
useFilter,
|
|
160
|
+
Primitive
|
|
161
|
+
} from 'reka-ui'
|
|
162
|
+
import { defu } from 'defu'
|
|
163
|
+
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
|
164
|
+
import { useButtonGroup } from '../composables/useButtonGroup'
|
|
165
|
+
import { useComponentIcons } from '../composables/useComponentIcons'
|
|
166
|
+
import { useFormField } from '../composables/useFormField'
|
|
167
|
+
import { useLocale } from '../composables/useLocale'
|
|
168
|
+
import { get, compare } from '../utils'
|
|
169
|
+
import icons from '../dictionary/icons'
|
|
170
|
+
import B24Avatar from './Avatar.vue'
|
|
171
|
+
import B24Chip from './Chip.vue'
|
|
172
|
+
import B24Input from './Input.vue'
|
|
173
|
+
|
|
174
|
+
const props = withDefaults(defineProps<SelectMenuProps<T, I, V, M>>(), {
|
|
175
|
+
portal: true,
|
|
176
|
+
searchInput: true,
|
|
177
|
+
labelKey: 'label' as never,
|
|
178
|
+
resetSearchTermOnBlur: true
|
|
179
|
+
})
|
|
180
|
+
const emits = defineEmits<SelectMenuEmits<T, V, M>>()
|
|
181
|
+
const slots = defineSlots<SelectMenuSlots<T, M>>()
|
|
182
|
+
|
|
183
|
+
const searchTerm = defineModel<string>('searchTerm', { default: '' })
|
|
184
|
+
|
|
185
|
+
const { t } = useLocale()
|
|
186
|
+
const { contains } = useFilter({ sensitivity: 'base' })
|
|
187
|
+
|
|
188
|
+
const rootProps = useForwardPropsEmits(reactivePick(props, 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover'), emits)
|
|
189
|
+
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
|
|
190
|
+
const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
|
|
191
|
+
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t('selectMenu.search'), type: 'search' }) as InputProps)
|
|
192
|
+
|
|
193
|
+
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
|
|
194
|
+
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
|
195
|
+
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(
|
|
196
|
+
props,
|
|
197
|
+
{ trailingIcon: icons.chevronDown }
|
|
198
|
+
)))
|
|
199
|
+
|
|
200
|
+
const selectSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
|
201
|
+
|
|
202
|
+
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
|
|
203
|
+
|
|
204
|
+
const isTag = computed(() => {
|
|
205
|
+
return props.tag
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const b24ui = computed(() => selectMenu({
|
|
209
|
+
color: color.value,
|
|
210
|
+
size: selectSize?.value,
|
|
211
|
+
loading: props.loading,
|
|
212
|
+
rounded: Boolean(props.rounded),
|
|
213
|
+
noPadding: Boolean(props.noPadding),
|
|
214
|
+
noBorder: Boolean(props.noBorder),
|
|
215
|
+
underline: Boolean(props.underline),
|
|
216
|
+
highlight: highlight.value,
|
|
217
|
+
tagColor: props.tagColor,
|
|
218
|
+
leading: Boolean(isLeading.value || !!props.avatar || !!slots.leading),
|
|
219
|
+
trailing: Boolean(isTrailing.value || !!slots.trailing),
|
|
220
|
+
buttonGroup: orientation.value
|
|
221
|
+
}))
|
|
222
|
+
|
|
223
|
+
const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as SelectMenuItem[][] : [])
|
|
224
|
+
// eslint-disable-next-line vue/no-dupe-keys
|
|
225
|
+
const items = computed(() => groups.value.flatMap(group => group) as T[])
|
|
226
|
+
|
|
227
|
+
function displayValue(value: T | T[]): string {
|
|
228
|
+
if (props.multiple && Array.isArray(value)) {
|
|
229
|
+
return value.map(v => displayValue(v)).filter(Boolean).join(', ')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!props.valueKey) {
|
|
233
|
+
return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
|
|
237
|
+
return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const filteredGroups = computed(() => {
|
|
241
|
+
if (props.ignoreFilter || !searchTerm.value) {
|
|
242
|
+
return groups.value
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
|
|
246
|
+
|
|
247
|
+
return groups.value.map(items => items.filter((item) => {
|
|
248
|
+
if (typeof item !== 'object') {
|
|
249
|
+
return contains(item, searchTerm.value)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (item.type && ['label', 'separator'].includes(item.type)) {
|
|
253
|
+
return true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return fields.some(field => contains(get(item, field), searchTerm.value))
|
|
257
|
+
})).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
|
|
258
|
+
})
|
|
259
|
+
const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
|
|
260
|
+
|
|
261
|
+
const createItem = computed(() => {
|
|
262
|
+
if (!props.createItem || !searchTerm.value) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
|
|
267
|
+
|
|
268
|
+
if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
|
|
269
|
+
return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return !filteredItems.value.length
|
|
273
|
+
})
|
|
274
|
+
const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
|
|
275
|
+
|
|
276
|
+
function onUpdate(value: any) {
|
|
277
|
+
if (toRaw(props.modelValue) === value) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
// @ts-expect-error - 'target' does not exist in type 'EventInit'
|
|
281
|
+
const event = new Event('change', { target: { value } })
|
|
282
|
+
emits('change', event)
|
|
283
|
+
|
|
284
|
+
emitFormChange()
|
|
285
|
+
emitFormInput()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function onUpdateOpen(value: boolean) {
|
|
289
|
+
let timeoutId
|
|
290
|
+
|
|
291
|
+
if (!value) {
|
|
292
|
+
const event = new FocusEvent('blur')
|
|
293
|
+
|
|
294
|
+
emits('blur', event)
|
|
295
|
+
emitFormBlur()
|
|
296
|
+
|
|
297
|
+
// Since we use `displayValue` prop inside ComboboxInput we should reset searchTerm manually
|
|
298
|
+
// https://reka-ui.com/docs/components/combobox#api-reference
|
|
299
|
+
if (props.resetSearchTermOnBlur) {
|
|
300
|
+
const STATE_ANIMATION_DELAY_MS = 100
|
|
301
|
+
|
|
302
|
+
timeoutId = setTimeout(() => {
|
|
303
|
+
searchTerm.value = ''
|
|
304
|
+
}, STATE_ANIMATION_DELAY_MS)
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
const event = new FocusEvent('focus')
|
|
308
|
+
emits('focus', event)
|
|
309
|
+
emitFormFocus()
|
|
310
|
+
clearTimeout(timeoutId)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<!-- eslint-disable vue/no-template-shadow -->
|
|
316
|
+
<template>
|
|
317
|
+
<Primitive as="div" :class="b24ui.root({ addNew: true, class: [props.b24ui?.root] })">
|
|
318
|
+
<DefineCreateItemTemplate>
|
|
319
|
+
<ComboboxGroup :class="b24ui.group({ addNew: true, class: props.b24ui?.group })">
|
|
320
|
+
<ComboboxItem
|
|
321
|
+
:class="b24ui.item({ addNew: true, class: props.b24ui?.item })"
|
|
322
|
+
:value="searchTerm"
|
|
323
|
+
@select.prevent="emits('create', searchTerm)"
|
|
324
|
+
>
|
|
325
|
+
<span :class="b24ui.itemLabel({ addNew: true, class: props.b24ui?.itemLabel })">
|
|
326
|
+
<slot name="create-item-label" :item="searchTerm">
|
|
327
|
+
<Component
|
|
328
|
+
:is="icons.plus"
|
|
329
|
+
:class="b24ui.itemLeadingIcon({ addNew: true, class: props.b24ui?.itemLeadingIcon })"
|
|
330
|
+
/>
|
|
331
|
+
{{ t('selectMenu.create', { label: searchTerm }) }}
|
|
332
|
+
</slot>
|
|
333
|
+
</span>
|
|
334
|
+
</ComboboxItem>
|
|
335
|
+
</ComboboxGroup>
|
|
336
|
+
</DefineCreateItemTemplate>
|
|
337
|
+
|
|
338
|
+
<ComboboxRoot
|
|
339
|
+
:id="id"
|
|
340
|
+
v-slot="{ modelValue, open }"
|
|
341
|
+
v-bind="{ ...rootProps, ...ariaAttrs }"
|
|
342
|
+
ignore-filter
|
|
343
|
+
as-child
|
|
344
|
+
:name="name"
|
|
345
|
+
:disabled="disabled"
|
|
346
|
+
@update:model-value="onUpdate"
|
|
347
|
+
@update:open="onUpdateOpen"
|
|
348
|
+
>
|
|
349
|
+
<ComboboxAnchor as-child>
|
|
350
|
+
<ComboboxTrigger :class="b24ui.base({ class: [props.class, props.b24ui?.base] })" tabindex="0">
|
|
351
|
+
<div v-if="isTag" :class="b24ui.tag({ class: props.b24ui?.tag })">
|
|
352
|
+
{{ props.tag }}
|
|
353
|
+
</div>
|
|
354
|
+
<span v-if="isLeading || !!avatar || !!slots.leading" :class="b24ui.leading({ class: props.b24ui?.leading })">
|
|
355
|
+
<slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
|
|
356
|
+
<Component
|
|
357
|
+
:is="leadingIconName"
|
|
358
|
+
v-if="isLeading && leadingIconName"
|
|
359
|
+
:class="b24ui.leadingIcon({ class: props.b24ui?.leadingIcon })"
|
|
360
|
+
/>
|
|
361
|
+
<B24Avatar v-else-if="!!avatar" :size="((props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="b24ui.itemLeadingAvatar({ class: props.b24ui?.itemLeadingAvatar })" />
|
|
362
|
+
</slot>
|
|
363
|
+
</span>
|
|
364
|
+
|
|
365
|
+
<slot :model-value="(modelValue as M extends true ? T[] : T)" :open="open">
|
|
366
|
+
<template v-for="displayedModelValue in [displayValue(modelValue as M extends true ? T[] : T)]" :key="displayedModelValue">
|
|
367
|
+
<span v-if="displayedModelValue" :class="b24ui.value({ class: props.b24ui?.value })">
|
|
368
|
+
{{ displayedModelValue }}
|
|
369
|
+
</span>
|
|
370
|
+
<span v-else :class="b24ui.placeholder({ class: props.b24ui?.placeholder })">
|
|
371
|
+
{{ placeholder ?? ' ' }}
|
|
372
|
+
</span>
|
|
373
|
+
</template>
|
|
374
|
+
</slot>
|
|
375
|
+
|
|
376
|
+
<span v-if="isTrailing || !!slots.trailing" :class="b24ui.trailing({ class: props.b24ui?.trailing })">
|
|
377
|
+
<slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
|
|
378
|
+
<Component
|
|
379
|
+
:is="trailingIconName"
|
|
380
|
+
v-if="trailingIconName"
|
|
381
|
+
:class="b24ui.trailingIcon({ class: props.b24ui?.trailingIcon })"
|
|
382
|
+
/>
|
|
383
|
+
</slot>
|
|
384
|
+
</span>
|
|
385
|
+
</ComboboxTrigger>
|
|
386
|
+
</ComboboxAnchor>
|
|
387
|
+
|
|
388
|
+
<ComboboxPortal :disabled="!portal">
|
|
389
|
+
<ComboboxContent :class="b24ui.content({ class: props.b24ui?.content })" v-bind="contentProps">
|
|
390
|
+
<ComboboxInput v-if="!!searchInput" v-model="searchTerm" :display-value="() => searchTerm" as-child>
|
|
391
|
+
<B24Input no-border autofocus autocomplete="off" v-bind="searchInputProps" :class="b24ui.input({ class: props.b24ui?.input })" />
|
|
392
|
+
</ComboboxInput>
|
|
393
|
+
|
|
394
|
+
<ComboboxEmpty :class="b24ui.empty({ class: props.b24ui?.empty })">
|
|
395
|
+
<slot name="empty" :search-term="searchTerm">
|
|
396
|
+
{{ searchTerm ? t('selectMenu.noMatch', { searchTerm }) : t('selectMenu.noData') }}
|
|
397
|
+
</slot>
|
|
398
|
+
</ComboboxEmpty>
|
|
399
|
+
|
|
400
|
+
<ComboboxViewport :class="b24ui.viewport({ class: props.b24ui?.viewport })">
|
|
401
|
+
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
|
|
402
|
+
|
|
403
|
+
<ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="b24ui.group({ class: props.b24ui?.group })">
|
|
404
|
+
<template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
|
|
405
|
+
<ComboboxLabel v-if="item?.type === 'label'" :class="b24ui.label({ class: props.b24ui?.label })">
|
|
406
|
+
{{ get(item, props.labelKey as string) }}
|
|
407
|
+
</ComboboxLabel>
|
|
408
|
+
|
|
409
|
+
<ComboboxSeparator v-else-if="item?.type === 'separator'" :class="b24ui.separator({ class: props.b24ui?.separator })" />
|
|
410
|
+
|
|
411
|
+
<ComboboxItem
|
|
412
|
+
v-else
|
|
413
|
+
:class="b24ui.item({ class: props.b24ui?.item })"
|
|
414
|
+
:disabled="item.disabled"
|
|
415
|
+
:value="valueKey && typeof item === 'object' ? get(item, props.valueKey as string) : item"
|
|
416
|
+
@select="item.onSelect"
|
|
417
|
+
>
|
|
418
|
+
<slot name="item" :item="(item as T)" :index="index">
|
|
419
|
+
<slot name="item-leading" :item="(item as T)" :index="index">
|
|
420
|
+
<Component
|
|
421
|
+
:is="item.icon"
|
|
422
|
+
v-if="item.icon"
|
|
423
|
+
:class="b24ui.itemLeadingIcon({ class: props.b24ui?.itemLeadingIcon })"
|
|
424
|
+
/>
|
|
425
|
+
<B24Avatar v-else-if="item.avatar" :size="((props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="b24ui.itemLeadingAvatar({ class: props.b24ui?.itemLeadingAvatar })" />
|
|
426
|
+
<B24Chip
|
|
427
|
+
v-else-if="item.chip"
|
|
428
|
+
:size="((props.b24ui?.itemLeadingChipSize || b24ui.itemLeadingChipSize()) as ChipProps['size'])"
|
|
429
|
+
inset
|
|
430
|
+
standalone
|
|
431
|
+
v-bind="item.chip"
|
|
432
|
+
:class="b24ui.itemLeadingChip({ class: props.b24ui?.itemLeadingChip })"
|
|
433
|
+
/>
|
|
434
|
+
</slot>
|
|
435
|
+
|
|
436
|
+
<span :class="b24ui.itemLabel({ class: props.b24ui?.itemLabel })">
|
|
437
|
+
<slot name="item-label" :item="(item as T)" :index="index">
|
|
438
|
+
{{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
|
|
439
|
+
</slot>
|
|
440
|
+
</span>
|
|
441
|
+
|
|
442
|
+
<span :class="b24ui.itemTrailing({ class: props.b24ui?.itemTrailing })">
|
|
443
|
+
<slot name="item-trailing" :item="(item as T)" :index="index" />
|
|
444
|
+
|
|
445
|
+
<ComboboxItemIndicator as-child>
|
|
446
|
+
<Component
|
|
447
|
+
:is="selectedIcon || icons.check"
|
|
448
|
+
:class="b24ui.itemTrailingIcon({ class: props.b24ui?.itemTrailingIcon })"
|
|
449
|
+
/>
|
|
450
|
+
</ComboboxItemIndicator>
|
|
451
|
+
</span>
|
|
452
|
+
</slot>
|
|
453
|
+
</ComboboxItem>
|
|
454
|
+
</template>
|
|
455
|
+
</ComboboxGroup>
|
|
456
|
+
|
|
457
|
+
<ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
|
|
458
|
+
</ComboboxViewport>
|
|
459
|
+
|
|
460
|
+
<ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="b24ui.arrow({ class: props.b24ui?.arrow })" />
|
|
461
|
+
</ComboboxContent>
|
|
462
|
+
</ComboboxPortal>
|
|
463
|
+
</ComboboxRoot>
|
|
464
|
+
</Primitive>
|
|
465
|
+
</template>
|
|
@@ -15,6 +15,6 @@ export interface UseComponentIconsProps {
|
|
|
15
15
|
export declare function useComponentIcons(componentProps: MaybeRefOrGetter<UseComponentIconsProps>): {
|
|
16
16
|
isLeading: import("vue").ComputedRef<any>;
|
|
17
17
|
isTrailing: import("vue").ComputedRef<boolean>;
|
|
18
|
-
leadingIconName: import("vue").ComputedRef<
|
|
19
|
-
trailingIconName: import("vue").ComputedRef<
|
|
18
|
+
leadingIconName: import("vue").ComputedRef<import("vue").FunctionalComponent<import("vue").HTMLAttributes & import("vue").VNodeProps, {}, any, {}> | undefined>;
|
|
19
|
+
trailingIconName: import("vue").ComputedRef<import("vue").FunctionalComponent<import("vue").HTMLAttributes & import("vue").VNodeProps, {}, any, {}> | undefined>;
|
|
20
20
|
};
|
|
@@ -19,6 +19,7 @@ export * from '../components/Progress.vue';
|
|
|
19
19
|
export * from '../components/RadioGroup.vue';
|
|
20
20
|
export * from '../components/Range.vue';
|
|
21
21
|
export * from '../components/Select.vue';
|
|
22
|
+
export * from '../components/SelectMenu.vue';
|
|
22
23
|
export * from '../components/Separator.vue';
|
|
23
24
|
export * from '../components/Skeleton.vue';
|
|
24
25
|
export * from '../components/Switch.vue';
|
|
@@ -31,3 +32,4 @@ export * from '../components/content/DescriptionList.vue';
|
|
|
31
32
|
export * from './form';
|
|
32
33
|
export * from './locale';
|
|
33
34
|
export * from './icons';
|
|
35
|
+
export * from './utils';
|
|
@@ -19,6 +19,7 @@ export * from "../components/Progress.vue";
|
|
|
19
19
|
export * from "../components/RadioGroup.vue";
|
|
20
20
|
export * from "../components/Range.vue";
|
|
21
21
|
export * from "../components/Select.vue";
|
|
22
|
+
export * from "../components/SelectMenu.vue";
|
|
22
23
|
export * from "../components/Separator.vue";
|
|
23
24
|
export * from "../components/Skeleton.vue";
|
|
24
25
|
export * from "../components/Switch.vue";
|
|
@@ -31,3 +32,4 @@ export * from "../components/content/DescriptionList.vue";
|
|
|
31
32
|
export * from "./form.js";
|
|
32
33
|
export * from "./locale.js";
|
|
33
34
|
export * from "./icons.js";
|
|
35
|
+
export * from "./utils.js";
|
|
@@ -2720,7 +2720,7 @@ const select = () => {
|
|
|
2720
2720
|
"dark:ring-base-800",
|
|
2721
2721
|
"text-base-master bg-white hover:text-base-900 focus:text-base-900 active:text-base-900",
|
|
2722
2722
|
"dark:text-base-150 dark:bg-transparent dark:hover:text-base-350 dark:focus:text-base-350 dark:active:text-base-350",
|
|
2723
|
-
"font-b24-primary font-regular text-sm leading-
|
|
2723
|
+
"font-b24-primary font-regular text-sm leading-tight",
|
|
2724
2724
|
"align-middle",
|
|
2725
2725
|
"text-ellipsis whitespace-nowrap"
|
|
2726
2726
|
].join(" "),
|
|
@@ -2728,7 +2728,9 @@ const select = () => {
|
|
|
2728
2728
|
placeholder: "truncate text-base-400 dark:text-base-300",
|
|
2729
2729
|
arrow: "fill-base-master/10 dark:fill-base-100/20",
|
|
2730
2730
|
content: [
|
|
2731
|
-
"
|
|
2731
|
+
"w-[var(--reka-popper-anchor-width)]",
|
|
2732
|
+
"max-h-60",
|
|
2733
|
+
// 'h-[var(--reka-popper-available-height)]',
|
|
2732
2734
|
"bg-white dark:bg-base-dark",
|
|
2733
2735
|
"shadow-md rounded-2xs ring ring-base-300 dark:ring-base-800",
|
|
2734
2736
|
"overflow-hidden",
|
|
@@ -2754,11 +2756,15 @@ const select = () => {
|
|
|
2754
2756
|
"before:absolute before:z-[-1] before:inset-px before:rounded-2xs",
|
|
2755
2757
|
"cursor-pointer",
|
|
2756
2758
|
"data-disabled:cursor-not-allowed data-disabled:opacity-75",
|
|
2757
|
-
"text-base-master dark:text-base-150
|
|
2759
|
+
"text-base-master dark:text-base-150",
|
|
2760
|
+
"data-highlighted:text-base-900 dark:data-highlighted:text-base-200 data-highlighted:before:bg-base-100/50 dark:data-highlighted:before:bg-base-900",
|
|
2761
|
+
"data-[state=checked]:text-base-900 dark:data-[state=checked]:text-base-200 data-[state=checked]:before:bg-base-100/50 dark:data-[state=checked]:before:bg-base-900",
|
|
2758
2762
|
"transition-colors before:transition-colors"
|
|
2759
2763
|
].join(" "),
|
|
2760
2764
|
itemLeadingIcon: [
|
|
2761
|
-
"shrink-0 text-base-500 dark:text-base-700
|
|
2765
|
+
"shrink-0 text-base-500 dark:text-base-700",
|
|
2766
|
+
"group-data-highlighted:text-base-master dark:group-data-highlighted:text-base-150",
|
|
2767
|
+
"group-data-[state=checked]:text-base-master dark:group-data-[state=checked]:text-base-150",
|
|
2762
2768
|
"transition-colors"
|
|
2763
2769
|
].join(" "),
|
|
2764
2770
|
itemLeadingAvatar: "shrink-0",
|
|
@@ -2783,6 +2789,34 @@ const select = () => {
|
|
|
2783
2789
|
);
|
|
2784
2790
|
};
|
|
2785
2791
|
|
|
2792
|
+
const selectMenu = () => {
|
|
2793
|
+
return defu.defu({
|
|
2794
|
+
slots: {
|
|
2795
|
+
input: "border-b border-base-300 dark:dark:border-base-800"
|
|
2796
|
+
},
|
|
2797
|
+
variants: {
|
|
2798
|
+
addNew: {
|
|
2799
|
+
true: {
|
|
2800
|
+
group: "p-0 isolate -m-px",
|
|
2801
|
+
item: [
|
|
2802
|
+
"before:rounded-none",
|
|
2803
|
+
"text-base-master dark:text-base-150 before:bg-blue-200 dark:before:bg-blue-800",
|
|
2804
|
+
"data-highlighted:text-base-900 dark:data-highlighted:text-base-200 data-highlighted:before:bg-blue-200 dark:data-highlighted:before:bg-blue-800",
|
|
2805
|
+
"data-[state=checked]:text-base-900 dark:data-[state=checked]:text-base-200 data-[state=checked]:before:bg-blue-200 dark:data-[state=checked]:before:bg-blue-800"
|
|
2806
|
+
].join(" "),
|
|
2807
|
+
itemLabel: "flex flex-row flex-nowrap items-center justify-start gap-2",
|
|
2808
|
+
itemLeadingIcon: [
|
|
2809
|
+
"size-5 rounded-full",
|
|
2810
|
+
"text-white dark:text-base-150 bg-blue-500 dark:bg-blue-600",
|
|
2811
|
+
"group-data-highlighted:text-white dark:group-data-highlighted:text-base-150 group-data-highlighted:bg-blue-500 dark:group-data-highlighted:bg-blue-600",
|
|
2812
|
+
"group-data-[state=checked]:text-white dark:group-data-[state=checked]:text-base-150 group-data-[state=checked]:bg-blue-500 dark:group-data-[state=checked]:bg-blue-600"
|
|
2813
|
+
].join(" ")
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}, select());
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2786
2820
|
const separator = {
|
|
2787
2821
|
slots: {
|
|
2788
2822
|
root: "flex items-center align-center text-center",
|
|
@@ -3868,6 +3902,7 @@ const theme = {
|
|
|
3868
3902
|
radioGroup: radioGroup,
|
|
3869
3903
|
range: range,
|
|
3870
3904
|
select: select,
|
|
3905
|
+
selectMenu: selectMenu,
|
|
3871
3906
|
separator: separator,
|
|
3872
3907
|
skeleton: skeleton,
|
|
3873
3908
|
switch: _switch,
|
|
@@ -4046,7 +4081,7 @@ function getTemplates(options) {
|
|
|
4046
4081
|
templates.push({
|
|
4047
4082
|
filename: "types/b24ui.d.ts",
|
|
4048
4083
|
getContents: () => replaceBrackets(`import * as b24ui from '#build/b24ui'
|
|
4049
|
-
import type { DeepPartial } from '
|
|
4084
|
+
import type { DeepPartial } from '@bitrix24/b24ui-nuxt'
|
|
4050
4085
|
import type { defaultConfig } from 'tailwind-variants'
|
|
4051
4086
|
|
|
4052
4087
|
type AppConfigUI = {
|