@nuxt/ui 2.11.1 → 2.12.0

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.
Files changed (63) hide show
  1. package/dist/module.d.mts +100 -28
  2. package/dist/module.d.ts +100 -28
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +2 -2
  5. package/dist/runtime/components/data/Table.vue +26 -18
  6. package/dist/runtime/components/data/Table.vue.d.ts +21 -12
  7. package/dist/runtime/components/elements/Accordion.vue +8 -4
  8. package/dist/runtime/components/elements/Accordion.vue.d.ts +4 -4
  9. package/dist/runtime/components/elements/Alert.vue +8 -2
  10. package/dist/runtime/components/elements/Alert.vue.d.ts +5 -10
  11. package/dist/runtime/components/elements/Button.vue.d.ts +1 -1
  12. package/dist/runtime/components/elements/Chip.vue.d.ts +2 -2
  13. package/dist/runtime/components/elements/Dropdown.vue +64 -15
  14. package/dist/runtime/components/elements/Dropdown.vue.d.ts +30 -2
  15. package/dist/runtime/components/elements/Link.vue +2 -2
  16. package/dist/runtime/components/forms/Form.vue +16 -7
  17. package/dist/runtime/components/forms/Form.vue.d.ts +1 -1
  18. package/dist/runtime/components/forms/Input.vue +1 -1
  19. package/dist/runtime/components/forms/Input.vue.d.ts +1 -1
  20. package/dist/runtime/components/forms/InputMenu.vue +410 -0
  21. package/dist/runtime/components/forms/InputMenu.vue.d.ts +331 -0
  22. package/dist/runtime/components/forms/RadioGroup.vue +1 -1
  23. package/dist/runtime/components/forms/Select.vue +1 -1
  24. package/dist/runtime/components/forms/Select.vue.d.ts +1 -1
  25. package/dist/runtime/components/forms/SelectMenu.vue +52 -19
  26. package/dist/runtime/components/forms/SelectMenu.vue.d.ts +28 -8
  27. package/dist/runtime/components/forms/Toggle.vue +1 -1
  28. package/dist/runtime/components/forms/Toggle.vue.d.ts +2 -1
  29. package/dist/runtime/components/navigation/Breadcrumb.vue +7 -4
  30. package/dist/runtime/components/navigation/Breadcrumb.vue.d.ts +3 -0
  31. package/dist/runtime/components/navigation/CommandPalette.vue.d.ts +1 -1
  32. package/dist/runtime/components/navigation/Pagination.vue +9 -4
  33. package/dist/runtime/components/navigation/Pagination.vue.d.ts +9 -0
  34. package/dist/runtime/components/navigation/VerticalNavigation.vue +54 -37
  35. package/dist/runtime/components/navigation/VerticalNavigation.vue.d.ts +7 -3
  36. package/dist/runtime/components/overlays/Modal.vue.d.ts +1 -1
  37. package/dist/runtime/components/overlays/Popover.vue +49 -13
  38. package/dist/runtime/components/overlays/Popover.vue.d.ts +14 -4
  39. package/dist/runtime/components/overlays/Slideover.vue.d.ts +1 -1
  40. package/dist/runtime/composables/useShortcuts.mjs +3 -1
  41. package/dist/runtime/types/alert.d.ts +5 -0
  42. package/dist/runtime/types/breadcrumb.d.ts +1 -0
  43. package/dist/runtime/types/dropdown.d.ts +2 -0
  44. package/dist/runtime/types/form.d.ts +1 -0
  45. package/dist/runtime/types/vertical-navigation.d.ts +1 -0
  46. package/dist/runtime/ui.config/forms/input.mjs +12 -12
  47. package/dist/runtime/ui.config/forms/inputMenu.d.ts +64 -0
  48. package/dist/runtime/ui.config/forms/inputMenu.mjs +62 -0
  49. package/dist/runtime/ui.config/forms/selectMenu.d.ts +14 -12
  50. package/dist/runtime/ui.config/forms/selectMenu.mjs +7 -41
  51. package/dist/runtime/ui.config/index.d.ts +1 -0
  52. package/dist/runtime/ui.config/index.mjs +1 -0
  53. package/dist/runtime/ui.config/layout/card.mjs +1 -1
  54. package/dist/runtime/ui.config/navigation/breadcrumb.d.ts +1 -0
  55. package/dist/runtime/ui.config/navigation/breadcrumb.mjs +1 -0
  56. package/dist/runtime/ui.config/navigation/verticalNavigation.d.ts +5 -0
  57. package/dist/runtime/ui.config/navigation/verticalNavigation.mjs +5 -0
  58. package/dist/runtime/ui.config/popper.mjs +2 -1
  59. package/dist/runtime/utils/index.mjs +11 -3
  60. package/dist/runtime/utils/uid.mjs +2 -1
  61. package/dist/types.d.mts +2 -2
  62. package/dist/types.d.ts +1 -1
  63. package/package.json +27 -24
@@ -0,0 +1,410 @@
1
+ <template>
2
+ <HCombobox
3
+ v-slot="{ open }"
4
+ :by="by"
5
+ :name="name"
6
+ :model-value="modelValue"
7
+ :disabled="disabled"
8
+ as="div"
9
+ :class="ui.wrapper"
10
+ @update:model-value="onUpdate"
11
+ >
12
+ <div :class="uiMenu.trigger">
13
+ <HComboboxInput
14
+ :id="inputId"
15
+ :name="name"
16
+ :required="required"
17
+ :placeholder="placeholder"
18
+ :disabled="disabled"
19
+ :class="inputClass"
20
+ autocomplete="off"
21
+ v-bind="attrs"
22
+ :display-value="() => query ? query : ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute]"
23
+ @change="onChange"
24
+ />
25
+
26
+ <span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
27
+ <slot name="leading" :disabled="disabled" :loading="loading">
28
+ <UIcon :name="leadingIconName" :class="leadingIconClass" />
29
+ </slot>
30
+ </span>
31
+
32
+ <HComboboxButton v-if="(isTrailing && trailingIconName) || $slots.trailing" ref="trigger" :class="trailingWrapperIconClass">
33
+ <slot name="trailing" :disabled="disabled" :loading="loading">
34
+ <UIcon :name="trailingIconName" :class="trailingIconClass" />
35
+ </slot>
36
+ </HComboboxButton>
37
+ </div>
38
+
39
+ <div v-if="open" ref="container" :class="[uiMenu.container, uiMenu.width]">
40
+ <Transition appear v-bind="uiMenu.transition">
41
+ <div>
42
+ <div v-if="popper.arrow" data-popper-arrow :class="Object.values(uiMenu.arrow)" />
43
+
44
+ <HComboboxOptions static :class="[uiMenu.base, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]">
45
+ <HComboboxOption
46
+ v-for="(option, index) in filteredOptions"
47
+ v-slot="{ active, selected, disabled: optionDisabled }"
48
+ :key="index"
49
+ as="template"
50
+ :value="valueAttribute ? option[valueAttribute] : option"
51
+ :disabled="option.disabled"
52
+ >
53
+ <li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, selected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
54
+ <div :class="uiMenu.option.container">
55
+ <slot name="option" :option="option" :active="active" :selected="selected">
56
+ <UIcon v-if="option.icon" :name="option.icon" :class="[uiMenu.option.icon.base, active ? uiMenu.option.icon.active : uiMenu.option.icon.inactive, option.iconClass]" aria-hidden="true" />
57
+ <UAvatar
58
+ v-else-if="option.avatar"
59
+ v-bind="{ size: uiMenu.option.avatar.size, ...option.avatar }"
60
+ :class="uiMenu.option.avatar.base"
61
+ aria-hidden="true"
62
+ />
63
+ <span v-else-if="option.chip" :class="uiMenu.option.chip.base" :style="{ background: `#${option.chip}` }" />
64
+
65
+ <span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }}</span>
66
+ </slot>
67
+ </div>
68
+
69
+ <span v-if="selected" :class="[uiMenu.option.selectedIcon.wrapper, uiMenu.option.selectedIcon.padding]">
70
+ <UIcon :name="selectedIcon" :class="uiMenu.option.selectedIcon.base" aria-hidden="true" />
71
+ </span>
72
+ </li>
73
+ </HComboboxOption>
74
+
75
+ <p v-if="query && !filteredOptions.length" :class="uiMenu.option.empty">
76
+ <slot name="option-empty" :query="query">
77
+ No results for "{{ query }}".
78
+ </slot>
79
+ </p>
80
+ <p v-else-if="!filteredOptions.length" :class="uiMenu.empty">
81
+ <slot name="empty" :query="query">
82
+ No options.
83
+ </slot>
84
+ </p>
85
+ </HComboboxOptions>
86
+ </div>
87
+ </Transition>
88
+ </div>
89
+ </HCombobox>
90
+ </template>
91
+
92
+ <script>
93
+ import { ref, computed, toRef, watch, defineComponent } from "vue";
94
+ import {
95
+ Combobox as HCombobox,
96
+ ComboboxButton as HComboboxButton,
97
+ ComboboxOptions as HComboboxOptions,
98
+ ComboboxOption as HComboboxOption,
99
+ ComboboxInput as HComboboxInput
100
+ } from "@headlessui/vue";
101
+ import { computedAsync, useDebounceFn } from "@vueuse/core";
102
+ import { defu } from "defu";
103
+ import { twMerge, twJoin } from "tailwind-merge";
104
+ import UIcon from "../elements/Icon.vue";
105
+ import UAvatar from "../elements/Avatar.vue";
106
+ import { useUI } from "../../composables/useUI";
107
+ import { usePopper } from "../../composables/usePopper";
108
+ import { useFormGroup } from "../../composables/useFormGroup";
109
+ import { get, mergeConfig } from "../../utils";
110
+ import { useInjectButtonGroup } from "../../composables/useButtonGroup";
111
+ import appConfig from "#build/app.config";
112
+ import { input, inputMenu } from "#ui/ui.config";
113
+ const config = mergeConfig(appConfig.ui.strategy, appConfig.ui.input, input);
114
+ const configMenu = mergeConfig(appConfig.ui.strategy, appConfig.ui.inputMenu, inputMenu);
115
+ export default defineComponent({
116
+ components: {
117
+ HCombobox,
118
+ HComboboxButton,
119
+ HComboboxOptions,
120
+ HComboboxOption,
121
+ HComboboxInput,
122
+ UIcon,
123
+ UAvatar
124
+ },
125
+ inheritAttrs: false,
126
+ props: {
127
+ modelValue: {
128
+ type: [String, Number, Object, Array],
129
+ default: ""
130
+ },
131
+ query: {
132
+ type: String,
133
+ default: null
134
+ },
135
+ by: {
136
+ type: String,
137
+ default: void 0
138
+ },
139
+ options: {
140
+ type: Array,
141
+ default: () => []
142
+ },
143
+ id: {
144
+ type: String,
145
+ default: null
146
+ },
147
+ name: {
148
+ type: String,
149
+ default: null
150
+ },
151
+ required: {
152
+ type: Boolean,
153
+ default: false
154
+ },
155
+ icon: {
156
+ type: String,
157
+ default: null
158
+ },
159
+ loadingIcon: {
160
+ type: String,
161
+ default: () => config.default.loadingIcon
162
+ },
163
+ leadingIcon: {
164
+ type: String,
165
+ default: null
166
+ },
167
+ trailingIcon: {
168
+ type: String,
169
+ default: () => configMenu.default.trailingIcon
170
+ },
171
+ trailing: {
172
+ type: Boolean,
173
+ default: false
174
+ },
175
+ leading: {
176
+ type: Boolean,
177
+ default: false
178
+ },
179
+ loading: {
180
+ type: Boolean,
181
+ default: false
182
+ },
183
+ selectedIcon: {
184
+ type: String,
185
+ default: () => configMenu.default.selectedIcon
186
+ },
187
+ disabled: {
188
+ type: Boolean,
189
+ default: false
190
+ },
191
+ placeholder: {
192
+ type: String,
193
+ default: null
194
+ },
195
+ padded: {
196
+ type: Boolean,
197
+ default: true
198
+ },
199
+ size: {
200
+ type: String,
201
+ default: null,
202
+ validator(value) {
203
+ return Object.keys(config.size).includes(value);
204
+ }
205
+ },
206
+ color: {
207
+ type: String,
208
+ default: () => config.default.color,
209
+ validator(value) {
210
+ return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value);
211
+ }
212
+ },
213
+ variant: {
214
+ type: String,
215
+ default: () => config.default.variant,
216
+ validator(value) {
217
+ return [
218
+ ...Object.keys(config.variant),
219
+ ...Object.values(config.color).flatMap((value2) => Object.keys(value2))
220
+ ].includes(value);
221
+ }
222
+ },
223
+ optionAttribute: {
224
+ type: String,
225
+ default: "label"
226
+ },
227
+ valueAttribute: {
228
+ type: String,
229
+ default: null
230
+ },
231
+ search: {
232
+ type: Function,
233
+ default: void 0
234
+ },
235
+ searchAttributes: {
236
+ type: Array,
237
+ default: null
238
+ },
239
+ debounce: {
240
+ type: Number,
241
+ default: 200
242
+ },
243
+ popper: {
244
+ type: Object,
245
+ default: () => ({})
246
+ },
247
+ inputClass: {
248
+ type: String,
249
+ default: null
250
+ },
251
+ class: {
252
+ type: [String, Object, Array],
253
+ default: () => ""
254
+ },
255
+ ui: {
256
+ type: Object,
257
+ default: () => ({})
258
+ },
259
+ uiMenu: {
260
+ type: Object,
261
+ default: () => ({})
262
+ }
263
+ },
264
+ emits: ["update:modelValue", "update:query", "open", "close", "change"],
265
+ setup(props, { emit, slots }) {
266
+ const { ui, attrs } = useUI("input", toRef(props, "ui"), config, toRef(props, "class"));
267
+ const { ui: uiMenu } = useUI("inputMenu", toRef(props, "uiMenu"), configMenu);
268
+ const popper = computed(() => defu({}, props.popper, uiMenu.value.popper));
269
+ const [trigger, container] = usePopper(popper.value);
270
+ const { size: sizeButtonGroup, rounded } = useInjectButtonGroup({ ui, props });
271
+ const { emitFormBlur, emitFormChange, inputId, color, size: sizeFormGroup, name } = useFormGroup(props, config);
272
+ const size = computed(() => sizeButtonGroup.value || sizeFormGroup.value);
273
+ const internalQuery = ref("");
274
+ const query = computed({
275
+ get() {
276
+ return props.query ?? internalQuery.value;
277
+ },
278
+ set(value) {
279
+ internalQuery.value = value;
280
+ emit("update:query", value);
281
+ }
282
+ });
283
+ const inputClass = computed(() => {
284
+ const variant = ui.value.color?.[color.value]?.[props.variant] || ui.value.variant[props.variant];
285
+ return twMerge(twJoin(
286
+ ui.value.base,
287
+ ui.value.form,
288
+ rounded.value,
289
+ ui.value.placeholder,
290
+ ui.value.size[size.value],
291
+ props.padded ? ui.value.padding[size.value] : "p-0",
292
+ variant?.replaceAll("{color}", color.value),
293
+ (isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
294
+ (isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value]
295
+ ), props.inputClass);
296
+ });
297
+ const isLeading = computed(() => {
298
+ return props.icon && props.leading || props.icon && !props.trailing || props.loading && !props.trailing || props.leadingIcon;
299
+ });
300
+ const isTrailing = computed(() => {
301
+ return props.icon && props.trailing || props.loading && props.trailing || props.trailingIcon;
302
+ });
303
+ const leadingIconName = computed(() => {
304
+ if (props.loading) {
305
+ return props.loadingIcon;
306
+ }
307
+ return props.leadingIcon || props.icon;
308
+ });
309
+ const trailingIconName = computed(() => {
310
+ if (props.loading && !isLeading.value) {
311
+ return props.loadingIcon;
312
+ }
313
+ return props.trailingIcon || props.icon;
314
+ });
315
+ const leadingWrapperIconClass = computed(() => {
316
+ return twJoin(
317
+ ui.value.icon.leading.wrapper,
318
+ ui.value.icon.leading.pointer,
319
+ ui.value.icon.leading.padding[size.value]
320
+ );
321
+ });
322
+ const leadingIconClass = computed(() => {
323
+ return twJoin(
324
+ ui.value.icon.base,
325
+ color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll("{color}", color.value),
326
+ ui.value.icon.size[size.value],
327
+ props.loading && ui.value.icon.loading
328
+ );
329
+ });
330
+ const trailingWrapperIconClass = computed(() => {
331
+ return twJoin(
332
+ ui.value.icon.trailing.wrapper,
333
+ ui.value.icon.trailing.padding[size.value]
334
+ );
335
+ });
336
+ const trailingIconClass = computed(() => {
337
+ return twJoin(
338
+ ui.value.icon.base,
339
+ color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll("{color}", color.value),
340
+ ui.value.icon.size[size.value],
341
+ props.loading && !isLeading.value && ui.value.icon.loading
342
+ );
343
+ });
344
+ const debouncedSearch = props.search && typeof props.search === "function" ? useDebounceFn(props.search, props.debounce) : void 0;
345
+ const filteredOptions = computedAsync(async () => {
346
+ if (debouncedSearch) {
347
+ return await debouncedSearch(query.value);
348
+ }
349
+ if (query.value === "") {
350
+ return props.options;
351
+ }
352
+ return props.options.filter((option) => {
353
+ return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute) => {
354
+ if (["string", "number"].includes(typeof option)) {
355
+ return String(option).search(new RegExp(query.value, "i")) !== -1;
356
+ }
357
+ const child = get(option, searchAttribute);
358
+ return child !== null && child !== void 0 && String(child).search(new RegExp(query.value, "i")) !== -1;
359
+ });
360
+ });
361
+ });
362
+ watch(container, (value) => {
363
+ if (value) {
364
+ emit("open");
365
+ } else {
366
+ emit("close");
367
+ emitFormBlur();
368
+ }
369
+ });
370
+ function onUpdate(event) {
371
+ query.value = "";
372
+ emit("update:modelValue", event);
373
+ emit("change", event);
374
+ emitFormChange();
375
+ }
376
+ function onChange(event) {
377
+ query.value = event.target.value;
378
+ }
379
+ return {
380
+ // eslint-disable-next-line vue/no-dupe-keys
381
+ ui,
382
+ // eslint-disable-next-line vue/no-dupe-keys
383
+ uiMenu,
384
+ attrs,
385
+ // eslint-disable-next-line vue/no-dupe-keys
386
+ name,
387
+ inputId,
388
+ // eslint-disable-next-line vue/no-dupe-keys
389
+ popper,
390
+ trigger,
391
+ container,
392
+ isLeading,
393
+ isTrailing,
394
+ // eslint-disable-next-line vue/no-dupe-keys
395
+ inputClass,
396
+ leadingIconName,
397
+ leadingIconClass,
398
+ leadingWrapperIconClass,
399
+ trailingIconName,
400
+ trailingIconClass,
401
+ trailingWrapperIconClass,
402
+ filteredOptions,
403
+ // eslint-disable-next-line vue/no-dupe-keys
404
+ query,
405
+ onUpdate,
406
+ onChange
407
+ };
408
+ }
409
+ });
410
+ </script>