@aleph-alpha/ui-library 1.11.0 → 1.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.
- package/README.md +0 -2
- package/dist/system/index.d.ts +860 -179
- package/dist/system/lib.js +33936 -9729
- package/package.json +1 -2
- package/src/compositions/UiDataTable/UiDataTable.stories.ts +6 -7
- package/src/compositions/UiDataTable/UiDataTableColumnHeader.vue +22 -8
- package/src/compositions/UiDataTable/UiDataTablePagination.vue +5 -5
- package/src/compositions/UiDatePicker/UiDatePicker.vue +2 -2
- package/src/primitives/UiAlert/UiAlert.stories.ts +8 -8
- package/src/primitives/UiBadge/UiBadge.stories.ts +9 -9
- package/src/primitives/UiButton/UiButton.stories.ts +12 -12
- package/src/primitives/UiCalendar/UiCalendar.stories.ts +4 -4
- package/src/primitives/UiDropdownMenu/UiDropdownMenu.stories.ts +2 -2
- package/src/primitives/UiIcon/UiIcon.stories.ts +59 -13
- package/src/primitives/UiIcon/UiIcon.vue +41 -3
- package/src/primitives/UiIcon/__tests__/UiIcon.test.ts +33 -4
- package/src/primitives/UiIcon/index.ts +1 -0
- package/src/primitives/UiIcon/types.ts +24 -5
- package/src/primitives/UiIconButton/UiIconButton.stories.ts +36 -36
- package/src/primitives/UiKbd/UiKbd.stories.ts +551 -0
- package/src/primitives/UiKbd/UiKbd.vue +62 -0
- package/src/primitives/UiKbd/UiKbdGroup.vue +16 -0
- package/src/primitives/UiKbd/__tests__/UiKbd.test.ts +46 -0
- package/src/primitives/UiKbd/index.ts +3 -0
- package/src/primitives/UiKbd/types.ts +32 -0
- package/src/primitives/UiLabel/UiLabel.stories.ts +192 -0
- package/src/primitives/UiLabel/UiLabel.vue +16 -0
- package/src/primitives/UiLabel/__tests__/UiLabel.test.ts +43 -0
- package/src/primitives/UiLabel/index.ts +2 -0
- package/src/primitives/UiLabel/types.ts +16 -0
- package/src/primitives/UiListbox/UiListbox.stories.ts +607 -0
- package/src/primitives/UiListbox/UiListbox.vue +30 -0
- package/src/primitives/UiListbox/UiListboxContent.vue +16 -0
- package/src/primitives/UiListbox/UiListboxFilter.vue +16 -0
- package/src/primitives/UiListbox/UiListboxGroup.vue +16 -0
- package/src/primitives/UiListbox/UiListboxGroupLabel.vue +16 -0
- package/src/primitives/UiListbox/UiListboxItem.vue +20 -0
- package/src/primitives/UiListbox/UiListboxItemIndicator.vue +16 -0
- package/src/primitives/UiListbox/__tests__/UiListbox.test.ts +42 -0
- package/src/primitives/UiListbox/index.ts +8 -0
- package/src/primitives/UiListbox/types.ts +119 -0
- package/src/primitives/UiPopover/index.ts +1 -0
- package/src/primitives/UiSeparator/UiSeparator.stories.ts +177 -0
- package/src/primitives/UiSeparator/UiSeparator.vue +17 -0
- package/src/primitives/UiSeparator/__tests__/UiSeparator.test.ts +34 -0
- package/src/primitives/UiSeparator/index.ts +2 -0
- package/src/primitives/UiSeparator/types.ts +23 -0
- package/src/primitives/UiSkeleton/UiSkeleton.stories.ts +247 -0
- package/src/primitives/UiSkeleton/UiSkeleton.vue +24 -0
- package/src/primitives/UiSkeleton/__tests__/UiSkeleton.test.ts +47 -0
- package/src/primitives/UiSkeleton/index.ts +2 -0
- package/src/primitives/UiSkeleton/types.ts +26 -0
- package/src/primitives/UiTable/UiTable.stories.ts +2 -2
- package/src/primitives/UiTagsInput/UiTagsInput.stories.ts +538 -0
- package/src/primitives/UiTagsInput/UiTagsInput.vue +27 -0
- package/src/primitives/UiTagsInput/UiTagsInputInput.vue +14 -0
- package/src/primitives/UiTagsInput/UiTagsInputItem.vue +16 -0
- package/src/primitives/UiTagsInput/UiTagsInputItemDelete.vue +16 -0
- package/src/primitives/UiTagsInput/UiTagsInputItemText.vue +14 -0
- package/src/primitives/UiTagsInput/__tests__/UiTagsInput.test.ts +44 -0
- package/src/primitives/UiTagsInput/index.ts +6 -0
- package/src/primitives/UiTagsInput/types.ts +60 -0
- package/src/primitives/UiToggle/UiToggle.stories.ts +370 -0
- package/src/primitives/UiToggle/UiToggle.vue +28 -0
- package/src/primitives/UiToggle/__tests__/UiToggle.test.ts +62 -0
- package/src/primitives/UiToggle/index.ts +2 -0
- package/src/primitives/UiToggle/types.ts +35 -0
- package/src/primitives/UiTooltip/UiTooltip.stories.ts +8 -8
- package/src/primitives/index.ts +7 -0
- package/src/primitives/shadcn/accordion/AccordionTrigger.vue +5 -4
- package/src/primitives/shadcn/calendar/CalendarNextButton.vue +2 -2
- package/src/primitives/shadcn/calendar/CalendarPrevButton.vue +2 -2
- package/src/primitives/shadcn/checkbox/Checkbox.vue +2 -2
- package/src/primitives/shadcn/dropdown-menu/DropdownMenuCheckboxItem.vue +2 -2
- package/src/primitives/shadcn/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
- package/src/primitives/shadcn/kbd/Kbd.vue +20 -0
- package/src/primitives/shadcn/kbd/KbdGroup.vue +12 -0
- package/src/primitives/shadcn/kbd/index.ts +2 -0
- package/src/primitives/shadcn/listbox/Listbox.vue +23 -0
- package/src/primitives/shadcn/listbox/ListboxContent.vue +26 -0
- package/src/primitives/shadcn/listbox/ListboxFilter.vue +30 -0
- package/src/primitives/shadcn/listbox/ListboxGroup.vue +26 -0
- package/src/primitives/shadcn/listbox/ListboxGroupLabel.vue +26 -0
- package/src/primitives/shadcn/listbox/ListboxItem.vue +32 -0
- package/src/primitives/shadcn/listbox/ListboxItemIndicator.vue +40 -0
- package/src/primitives/shadcn/listbox/index.ts +7 -0
- package/src/primitives/shadcn/native-select/NativeSelect.vue +5 -4
- package/src/primitives/shadcn/range-calendar/RangeCalendarNextButton.vue +2 -2
- package/src/primitives/shadcn/range-calendar/RangeCalendarPrevButton.vue +2 -2
- package/src/primitives/shadcn/select/SelectItem.vue +2 -2
- package/src/primitives/shadcn/select/SelectScrollDownButton.vue +2 -2
- package/src/primitives/shadcn/select/SelectScrollUpButton.vue +2 -2
- package/src/primitives/shadcn/select/SelectTrigger.vue +2 -2
- package/src/primitives/shadcn/skeleton/Skeleton.vue +10 -0
- package/src/primitives/shadcn/skeleton/index.ts +1 -0
- package/src/primitives/shadcn/spinner/Spinner.vue +5 -4
- package/src/primitives/shadcn/tags-input/TagsInput.vue +33 -0
- package/src/primitives/shadcn/tags-input/TagsInputInput.vue +24 -0
- package/src/primitives/shadcn/tags-input/TagsInputItem.vue +31 -0
- package/src/primitives/shadcn/tags-input/TagsInputItemDelete.vue +46 -0
- package/src/primitives/shadcn/tags-input/TagsInputItemText.vue +24 -0
- package/src/primitives/shadcn/tags-input/index.ts +5 -0
- package/src/primitives/shadcn/toggle/Toggle.vue +34 -0
- package/src/primitives/shadcn/toggle/index.ts +27 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { ref, computed, watch } from 'vue';
|
|
3
|
+
import { useFilter } from 'reka-ui';
|
|
4
|
+
import UiTagsInput from './UiTagsInput.vue';
|
|
5
|
+
import UiTagsInputInput from './UiTagsInputInput.vue';
|
|
6
|
+
import UiTagsInputItem from './UiTagsInputItem.vue';
|
|
7
|
+
import UiTagsInputItemText from './UiTagsInputItemText.vue';
|
|
8
|
+
import UiTagsInputItemDelete from './UiTagsInputItemDelete.vue';
|
|
9
|
+
import { UiPopover, UiPopoverTrigger, UiPopoverContent, UiPopoverAnchor } from '../UiPopover';
|
|
10
|
+
import { UiButton } from '../UiButton';
|
|
11
|
+
import {
|
|
12
|
+
UiListbox,
|
|
13
|
+
UiListboxContent,
|
|
14
|
+
UiListboxFilter,
|
|
15
|
+
UiListboxItem,
|
|
16
|
+
UiListboxItemIndicator,
|
|
17
|
+
} from '../UiListbox';
|
|
18
|
+
|
|
19
|
+
const meta: Meta<typeof UiTagsInput> = {
|
|
20
|
+
title: 'Primitives/UiTagsInput',
|
|
21
|
+
component: UiTagsInput,
|
|
22
|
+
tags: ['autodocs'],
|
|
23
|
+
argTypes: {
|
|
24
|
+
disabled: {
|
|
25
|
+
control: 'boolean',
|
|
26
|
+
description: 'Disable the tags input',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
args: {
|
|
30
|
+
disabled: false,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof UiTagsInput>;
|
|
37
|
+
|
|
38
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
39
|
+
import { ref } from 'vue'
|
|
40
|
+
import {
|
|
41
|
+
UiTagsInput,
|
|
42
|
+
UiTagsInputInput,
|
|
43
|
+
UiTagsInputItem,
|
|
44
|
+
UiTagsInputItemText,
|
|
45
|
+
UiTagsInputItemDelete
|
|
46
|
+
} from '@aleph-alpha/ui-library'
|
|
47
|
+
|
|
48
|
+
const modelValue = ref(['Apple', 'Banana'])
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]">
|
|
53
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
54
|
+
<UiTagsInputItemText />
|
|
55
|
+
<UiTagsInputItemDelete />
|
|
56
|
+
</UiTagsInputItem>
|
|
57
|
+
|
|
58
|
+
<UiTagsInputInput placeholder="Fruits..." />
|
|
59
|
+
</UiTagsInput>
|
|
60
|
+
</template>`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default tags input with pre-filled values.
|
|
64
|
+
*/
|
|
65
|
+
export const Default: Story = {
|
|
66
|
+
render: (args) => ({
|
|
67
|
+
components: {
|
|
68
|
+
UiTagsInput,
|
|
69
|
+
UiTagsInputInput,
|
|
70
|
+
UiTagsInputItem,
|
|
71
|
+
UiTagsInputItemText,
|
|
72
|
+
UiTagsInputItemDelete,
|
|
73
|
+
},
|
|
74
|
+
setup() {
|
|
75
|
+
const modelValue = ref(['Apple', 'Banana']);
|
|
76
|
+
return { args, modelValue };
|
|
77
|
+
},
|
|
78
|
+
template: `
|
|
79
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]" :disabled="args.disabled">
|
|
80
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
81
|
+
<UiTagsInputItemText />
|
|
82
|
+
<UiTagsInputItemDelete />
|
|
83
|
+
</UiTagsInputItem>
|
|
84
|
+
|
|
85
|
+
<UiTagsInputInput placeholder="Fruits..." />
|
|
86
|
+
</UiTagsInput>
|
|
87
|
+
`,
|
|
88
|
+
}),
|
|
89
|
+
parameters: {
|
|
90
|
+
docs: {
|
|
91
|
+
source: {
|
|
92
|
+
code: defaultTemplateSource,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const emptyTemplateSource = `<script setup lang="ts">
|
|
99
|
+
import { ref } from 'vue'
|
|
100
|
+
import {
|
|
101
|
+
UiTagsInput,
|
|
102
|
+
UiTagsInputInput,
|
|
103
|
+
UiTagsInputItem,
|
|
104
|
+
UiTagsInputItemText,
|
|
105
|
+
UiTagsInputItemDelete
|
|
106
|
+
} from '@aleph-alpha/ui-library'
|
|
107
|
+
|
|
108
|
+
const modelValue = ref<string[]>([])
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]">
|
|
113
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
114
|
+
<UiTagsInputItemText />
|
|
115
|
+
<UiTagsInputItemDelete />
|
|
116
|
+
</UiTagsInputItem>
|
|
117
|
+
|
|
118
|
+
<UiTagsInputInput placeholder="Add tags..." />
|
|
119
|
+
</UiTagsInput>
|
|
120
|
+
</template>`;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Empty tags input ready for user input.
|
|
124
|
+
*/
|
|
125
|
+
export const Empty: Story = {
|
|
126
|
+
render: () => ({
|
|
127
|
+
components: {
|
|
128
|
+
UiTagsInput,
|
|
129
|
+
UiTagsInputInput,
|
|
130
|
+
UiTagsInputItem,
|
|
131
|
+
UiTagsInputItemText,
|
|
132
|
+
UiTagsInputItemDelete,
|
|
133
|
+
},
|
|
134
|
+
setup() {
|
|
135
|
+
const modelValue = ref<string[]>([]);
|
|
136
|
+
return { modelValue };
|
|
137
|
+
},
|
|
138
|
+
template: `
|
|
139
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]">
|
|
140
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
141
|
+
<UiTagsInputItemText />
|
|
142
|
+
<UiTagsInputItemDelete />
|
|
143
|
+
</UiTagsInputItem>
|
|
144
|
+
|
|
145
|
+
<UiTagsInputInput placeholder="Add tags..." />
|
|
146
|
+
</UiTagsInput>
|
|
147
|
+
`,
|
|
148
|
+
}),
|
|
149
|
+
parameters: {
|
|
150
|
+
docs: {
|
|
151
|
+
source: {
|
|
152
|
+
code: emptyTemplateSource,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const disabledTemplateSource = `<script setup lang="ts">
|
|
159
|
+
import { ref } from 'vue'
|
|
160
|
+
import {
|
|
161
|
+
UiTagsInput,
|
|
162
|
+
UiTagsInputInput,
|
|
163
|
+
UiTagsInputItem,
|
|
164
|
+
UiTagsInputItemText,
|
|
165
|
+
UiTagsInputItemDelete
|
|
166
|
+
} from '@aleph-alpha/ui-library'
|
|
167
|
+
|
|
168
|
+
const modelValue = ref(['Vue', 'React', 'Angular'])
|
|
169
|
+
</script>
|
|
170
|
+
|
|
171
|
+
<template>
|
|
172
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]" disabled>
|
|
173
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
174
|
+
<UiTagsInputItemText />
|
|
175
|
+
<UiTagsInputItemDelete />
|
|
176
|
+
</UiTagsInputItem>
|
|
177
|
+
|
|
178
|
+
<UiTagsInputInput placeholder="Add framework..." />
|
|
179
|
+
</UiTagsInput>
|
|
180
|
+
</template>`;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Disabled tags input.
|
|
184
|
+
*/
|
|
185
|
+
export const Disabled: Story = {
|
|
186
|
+
args: {
|
|
187
|
+
disabled: true,
|
|
188
|
+
},
|
|
189
|
+
render: () => ({
|
|
190
|
+
components: {
|
|
191
|
+
UiTagsInput,
|
|
192
|
+
UiTagsInputInput,
|
|
193
|
+
UiTagsInputItem,
|
|
194
|
+
UiTagsInputItemText,
|
|
195
|
+
UiTagsInputItemDelete,
|
|
196
|
+
},
|
|
197
|
+
setup() {
|
|
198
|
+
const modelValue = ref(['Vue', 'React', 'Angular']);
|
|
199
|
+
return { modelValue };
|
|
200
|
+
},
|
|
201
|
+
template: `
|
|
202
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]" disabled>
|
|
203
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
204
|
+
<UiTagsInputItemText />
|
|
205
|
+
<UiTagsInputItemDelete />
|
|
206
|
+
</UiTagsInputItem>
|
|
207
|
+
|
|
208
|
+
<UiTagsInputInput placeholder="Add framework..." />
|
|
209
|
+
</UiTagsInput>
|
|
210
|
+
`,
|
|
211
|
+
}),
|
|
212
|
+
parameters: {
|
|
213
|
+
docs: {
|
|
214
|
+
source: {
|
|
215
|
+
code: disabledTemplateSource,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const maxTagsTemplateSource = `<script setup lang="ts">
|
|
222
|
+
import { ref } from 'vue'
|
|
223
|
+
import {
|
|
224
|
+
UiTagsInput,
|
|
225
|
+
UiTagsInputInput,
|
|
226
|
+
UiTagsInputItem,
|
|
227
|
+
UiTagsInputItemText,
|
|
228
|
+
UiTagsInputItemDelete
|
|
229
|
+
} from '@aleph-alpha/ui-library'
|
|
230
|
+
|
|
231
|
+
const modelValue = ref(['Tag 1', 'Tag 2'])
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<template>
|
|
235
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]" :max="3">
|
|
236
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
237
|
+
<UiTagsInputItemText />
|
|
238
|
+
<UiTagsInputItemDelete />
|
|
239
|
+
</UiTagsInputItem>
|
|
240
|
+
|
|
241
|
+
<UiTagsInputInput placeholder="Max 3 tags..." />
|
|
242
|
+
</UiTagsInput>
|
|
243
|
+
</template>`;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Tags input with a maximum number of tags (3).
|
|
247
|
+
*/
|
|
248
|
+
export const MaxTags: Story = {
|
|
249
|
+
render: () => ({
|
|
250
|
+
components: {
|
|
251
|
+
UiTagsInput,
|
|
252
|
+
UiTagsInputInput,
|
|
253
|
+
UiTagsInputItem,
|
|
254
|
+
UiTagsInputItemText,
|
|
255
|
+
UiTagsInputItemDelete,
|
|
256
|
+
},
|
|
257
|
+
setup() {
|
|
258
|
+
const modelValue = ref(['Tag 1', 'Tag 2']);
|
|
259
|
+
return { modelValue };
|
|
260
|
+
},
|
|
261
|
+
template: `
|
|
262
|
+
<UiTagsInput v-model="modelValue" class="w-[300px]" :max="3">
|
|
263
|
+
<UiTagsInputItem v-for="item in modelValue" :key="item" :value="item">
|
|
264
|
+
<UiTagsInputItemText />
|
|
265
|
+
<UiTagsInputItemDelete />
|
|
266
|
+
</UiTagsInputItem>
|
|
267
|
+
|
|
268
|
+
<UiTagsInputInput placeholder="Max 3 tags..." />
|
|
269
|
+
</UiTagsInput>
|
|
270
|
+
`,
|
|
271
|
+
}),
|
|
272
|
+
parameters: {
|
|
273
|
+
docs: {
|
|
274
|
+
source: {
|
|
275
|
+
code: maxTagsTemplateSource,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const emailsTemplateSource = `<script setup lang="ts">
|
|
282
|
+
import { ref } from 'vue'
|
|
283
|
+
import {
|
|
284
|
+
UiTagsInput,
|
|
285
|
+
UiTagsInputInput,
|
|
286
|
+
UiTagsInputItem,
|
|
287
|
+
UiTagsInputItemText,
|
|
288
|
+
UiTagsInputItemDelete
|
|
289
|
+
} from '@aleph-alpha/ui-library'
|
|
290
|
+
|
|
291
|
+
const emails = ref(['john@example.com', 'jane@example.com'])
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<template>
|
|
295
|
+
<UiTagsInput v-model="emails" class="w-[400px]">
|
|
296
|
+
<UiTagsInputItem v-for="email in emails" :key="email" :value="email">
|
|
297
|
+
<UiTagsInputItemText />
|
|
298
|
+
<UiTagsInputItemDelete />
|
|
299
|
+
</UiTagsInputItem>
|
|
300
|
+
|
|
301
|
+
<UiTagsInputInput placeholder="Add email address..." />
|
|
302
|
+
</UiTagsInput>
|
|
303
|
+
</template>`;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Tags input for email addresses.
|
|
307
|
+
*/
|
|
308
|
+
export const EmailInput: Story = {
|
|
309
|
+
render: () => ({
|
|
310
|
+
components: {
|
|
311
|
+
UiTagsInput,
|
|
312
|
+
UiTagsInputInput,
|
|
313
|
+
UiTagsInputItem,
|
|
314
|
+
UiTagsInputItemText,
|
|
315
|
+
UiTagsInputItemDelete,
|
|
316
|
+
},
|
|
317
|
+
setup() {
|
|
318
|
+
const emails = ref(['john@example.com', 'jane@example.com']);
|
|
319
|
+
return { emails };
|
|
320
|
+
},
|
|
321
|
+
template: `
|
|
322
|
+
<UiTagsInput v-model="emails" class="w-[400px]">
|
|
323
|
+
<UiTagsInputItem v-for="email in emails" :key="email" :value="email">
|
|
324
|
+
<UiTagsInputItemText />
|
|
325
|
+
<UiTagsInputItemDelete />
|
|
326
|
+
</UiTagsInputItem>
|
|
327
|
+
|
|
328
|
+
<UiTagsInputInput placeholder="Add email address..." />
|
|
329
|
+
</UiTagsInput>
|
|
330
|
+
`,
|
|
331
|
+
}),
|
|
332
|
+
parameters: {
|
|
333
|
+
docs: {
|
|
334
|
+
source: {
|
|
335
|
+
code: emailsTemplateSource,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const withListboxTemplateSource = `<script setup lang="ts">
|
|
342
|
+
import { ref, computed, watch } from 'vue'
|
|
343
|
+
import { useFilter } from 'reka-ui'
|
|
344
|
+
import {
|
|
345
|
+
UiTagsInput,
|
|
346
|
+
UiTagsInputInput,
|
|
347
|
+
UiTagsInputItem,
|
|
348
|
+
UiTagsInputItemText,
|
|
349
|
+
UiTagsInputItemDelete,
|
|
350
|
+
UiPopover,
|
|
351
|
+
UiPopoverTrigger,
|
|
352
|
+
UiPopoverContent,
|
|
353
|
+
UiPopoverAnchor,
|
|
354
|
+
UiButton,
|
|
355
|
+
UiListbox,
|
|
356
|
+
UiListboxContent,
|
|
357
|
+
UiListboxFilter,
|
|
358
|
+
UiListboxItem,
|
|
359
|
+
UiListboxItemIndicator
|
|
360
|
+
} from '@aleph-alpha/ui-library'
|
|
361
|
+
|
|
362
|
+
const frameworks = [
|
|
363
|
+
{ value: 'next.js', label: 'Next.js' },
|
|
364
|
+
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
365
|
+
{ value: 'nuxt', label: 'Nuxt' },
|
|
366
|
+
{ value: 'remix', label: 'Remix' },
|
|
367
|
+
{ value: 'astro', label: 'Astro' },
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
const searchTerm = ref('')
|
|
371
|
+
const selectedFrameworks = ref(['Nuxt', 'Remix'])
|
|
372
|
+
const open = ref(false)
|
|
373
|
+
|
|
374
|
+
const { contains } = useFilter({ sensitivity: 'base' })
|
|
375
|
+
|
|
376
|
+
const filteredFrameworks = computed(() =>
|
|
377
|
+
searchTerm.value === ''
|
|
378
|
+
? frameworks
|
|
379
|
+
: frameworks.filter(option => contains(option.label, searchTerm.value))
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
watch(searchTerm, (value) => {
|
|
383
|
+
if (value) {
|
|
384
|
+
open.value = true
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
</script>
|
|
388
|
+
|
|
389
|
+
<template>
|
|
390
|
+
<UiPopover v-model:open="open">
|
|
391
|
+
<UiListbox
|
|
392
|
+
v-model="selectedFrameworks"
|
|
393
|
+
highlight-on-hover
|
|
394
|
+
multiple
|
|
395
|
+
>
|
|
396
|
+
<UiPopoverAnchor class="inline-flex w-[300px]">
|
|
397
|
+
<UiTagsInput v-slot="{ modelValue: tags }" v-model="selectedFrameworks" class="w-full">
|
|
398
|
+
<UiTagsInputItem v-for="item in tags" :key="item.toString()" :value="item.toString()">
|
|
399
|
+
<UiTagsInputItemText />
|
|
400
|
+
<UiTagsInputItemDelete />
|
|
401
|
+
</UiTagsInputItem>
|
|
402
|
+
|
|
403
|
+
<UiListboxFilter v-model="searchTerm" as-child>
|
|
404
|
+
<UiTagsInputInput placeholder="Frameworks..." @keydown.enter.prevent @keydown.down="open = true" />
|
|
405
|
+
</UiListboxFilter>
|
|
406
|
+
|
|
407
|
+
<UiPopoverTrigger as-child>
|
|
408
|
+
<UiButton size="icon-sm" variant="ghost" class="order-last self-start ml-auto">
|
|
409
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
410
|
+
<path d="m6 9 6 6 6-6"/>
|
|
411
|
+
</svg>
|
|
412
|
+
</UiButton>
|
|
413
|
+
</UiPopoverTrigger>
|
|
414
|
+
</UiTagsInput>
|
|
415
|
+
</UiPopoverAnchor>
|
|
416
|
+
|
|
417
|
+
<UiPopoverContent class="p-1" @open-auto-focus.prevent>
|
|
418
|
+
<UiListboxContent tabindex="0">
|
|
419
|
+
<UiListboxItem
|
|
420
|
+
v-for="item in filteredFrameworks"
|
|
421
|
+
:key="item.value"
|
|
422
|
+
:value="item.label"
|
|
423
|
+
@select="searchTerm = ''"
|
|
424
|
+
>
|
|
425
|
+
<span>{{ item.label }}</span>
|
|
426
|
+
<UiListboxItemIndicator />
|
|
427
|
+
</UiListboxItem>
|
|
428
|
+
</UiListboxContent>
|
|
429
|
+
</UiPopoverContent>
|
|
430
|
+
</UiListbox>
|
|
431
|
+
</UiPopover>
|
|
432
|
+
</template>`;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Tags input combined with a listbox in a popover for selecting from predefined options.
|
|
436
|
+
* This pattern allows users to either type custom tags or select from a dropdown.
|
|
437
|
+
*/
|
|
438
|
+
export const WithListbox: Story = {
|
|
439
|
+
render: () => ({
|
|
440
|
+
components: {
|
|
441
|
+
UiTagsInput,
|
|
442
|
+
UiTagsInputInput,
|
|
443
|
+
UiTagsInputItem,
|
|
444
|
+
UiTagsInputItemText,
|
|
445
|
+
UiTagsInputItemDelete,
|
|
446
|
+
UiPopover,
|
|
447
|
+
UiPopoverTrigger,
|
|
448
|
+
UiPopoverContent,
|
|
449
|
+
UiPopoverAnchor,
|
|
450
|
+
UiButton,
|
|
451
|
+
UiListbox,
|
|
452
|
+
UiListboxContent,
|
|
453
|
+
UiListboxFilter,
|
|
454
|
+
UiListboxItem,
|
|
455
|
+
UiListboxItemIndicator,
|
|
456
|
+
},
|
|
457
|
+
setup() {
|
|
458
|
+
const frameworks = [
|
|
459
|
+
{ value: 'next.js', label: 'Next.js' },
|
|
460
|
+
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
461
|
+
{ value: 'nuxt', label: 'Nuxt' },
|
|
462
|
+
{ value: 'remix', label: 'Remix' },
|
|
463
|
+
{ value: 'astro', label: 'Astro' },
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
const searchTerm = ref('');
|
|
467
|
+
const selectedFrameworks = ref(['Nuxt', 'Remix']);
|
|
468
|
+
const open = ref(false);
|
|
469
|
+
|
|
470
|
+
const { contains } = useFilter({ sensitivity: 'base' });
|
|
471
|
+
|
|
472
|
+
const filteredFrameworks = computed(() =>
|
|
473
|
+
searchTerm.value === ''
|
|
474
|
+
? frameworks
|
|
475
|
+
: frameworks.filter((option) => contains(option.label, searchTerm.value)),
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
watch(searchTerm, (value) => {
|
|
479
|
+
if (value) {
|
|
480
|
+
open.value = true;
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
return { frameworks, searchTerm, selectedFrameworks, open, filteredFrameworks };
|
|
485
|
+
},
|
|
486
|
+
template: `
|
|
487
|
+
<UiPopover v-model:open="open">
|
|
488
|
+
<UiListbox
|
|
489
|
+
v-model="selectedFrameworks"
|
|
490
|
+
highlight-on-hover
|
|
491
|
+
multiple
|
|
492
|
+
>
|
|
493
|
+
<UiPopoverAnchor class="inline-flex w-[300px]">
|
|
494
|
+
<UiTagsInput v-slot="{ modelValue: tags }" v-model="selectedFrameworks" class="w-full">
|
|
495
|
+
<UiTagsInputItem v-for="item in tags" :key="item.toString()" :value="item.toString()">
|
|
496
|
+
<UiTagsInputItemText />
|
|
497
|
+
<UiTagsInputItemDelete />
|
|
498
|
+
</UiTagsInputItem>
|
|
499
|
+
|
|
500
|
+
<UiListboxFilter v-model="searchTerm" as-child>
|
|
501
|
+
<UiTagsInputInput placeholder="Frameworks..." @keydown.enter.prevent @keydown.down="open = true" />
|
|
502
|
+
</UiListboxFilter>
|
|
503
|
+
|
|
504
|
+
<UiPopoverTrigger as-child>
|
|
505
|
+
<UiButton size="icon-sm" variant="ghost" class="order-last self-start ml-auto">
|
|
506
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
507
|
+
<path d="m6 9 6 6 6-6"/>
|
|
508
|
+
</svg>
|
|
509
|
+
</UiButton>
|
|
510
|
+
</UiPopoverTrigger>
|
|
511
|
+
</UiTagsInput>
|
|
512
|
+
</UiPopoverAnchor>
|
|
513
|
+
|
|
514
|
+
<UiPopoverContent class="p-1" @open-auto-focus.prevent>
|
|
515
|
+
<UiListboxContent tabindex="0">
|
|
516
|
+
<UiListboxItem
|
|
517
|
+
v-for="item in filteredFrameworks"
|
|
518
|
+
:key="item.value"
|
|
519
|
+
:value="item.label"
|
|
520
|
+
@select="searchTerm = ''"
|
|
521
|
+
>
|
|
522
|
+
<span>{{ item.label }}</span>
|
|
523
|
+
<UiListboxItemIndicator />
|
|
524
|
+
</UiListboxItem>
|
|
525
|
+
</UiListboxContent>
|
|
526
|
+
</UiPopoverContent>
|
|
527
|
+
</UiListbox>
|
|
528
|
+
</UiPopover>
|
|
529
|
+
`,
|
|
530
|
+
}),
|
|
531
|
+
parameters: {
|
|
532
|
+
docs: {
|
|
533
|
+
source: {
|
|
534
|
+
code: withListboxTemplateSource,
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { TagsInput as ShadcnTagsInput } from '@/primitives/shadcn/tags-input';
|
|
4
|
+
import { UiTagsInputProps } from './types';
|
|
5
|
+
|
|
6
|
+
defineOptions({
|
|
7
|
+
name: 'UiTagsInput',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<UiTagsInputProps>(), {
|
|
11
|
+
disabled: false,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const model = defineModel<string[]>({ default: [] });
|
|
15
|
+
|
|
16
|
+
const delegatedProps = computed(() => {
|
|
17
|
+
// eslint-disable-next-line sonarjs/no-unused-vars
|
|
18
|
+
const { modelValue: _modelValue, ...rest } = props;
|
|
19
|
+
return rest;
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<ShadcnTagsInput v-model="model" v-bind="delegatedProps" v-slot="slotProps">
|
|
25
|
+
<slot v-bind="slotProps" />
|
|
26
|
+
</ShadcnTagsInput>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TagsInputInput as ShadcnTagsInputInput } from '@/primitives/shadcn/tags-input';
|
|
3
|
+
import { UiTagsInputInputProps } from './types';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'UiTagsInputInput',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const props = defineProps<UiTagsInputInputProps>();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<ShadcnTagsInputInput v-bind="props" />
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TagsInputItem as ShadcnTagsInputItem } from '@/primitives/shadcn/tags-input';
|
|
3
|
+
import { UiTagsInputItemProps } from './types';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'UiTagsInputItem',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const props = defineProps<UiTagsInputItemProps>();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<ShadcnTagsInputItem v-bind="props">
|
|
14
|
+
<slot />
|
|
15
|
+
</ShadcnTagsInputItem>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TagsInputItemDelete as ShadcnTagsInputItemDelete } from '@/primitives/shadcn/tags-input';
|
|
3
|
+
import { UiTagsInputItemDeleteProps } from './types';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'UiTagsInputItemDelete',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const props = defineProps<UiTagsInputItemDeleteProps>();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<ShadcnTagsInputItemDelete v-bind="props">
|
|
14
|
+
<slot />
|
|
15
|
+
</ShadcnTagsInputItemDelete>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TagsInputItemText as ShadcnTagsInputItemText } from '@/primitives/shadcn/tags-input';
|
|
3
|
+
import { UiTagsInputItemTextProps } from './types';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'UiTagsInputItemText',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const props = defineProps<UiTagsInputItemTextProps>();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<ShadcnTagsInputItemText v-bind="props" />
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { render } from '@testing-library/vue';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import UiTagsInput from '../UiTagsInput.vue';
|
|
4
|
+
import UiTagsInputInput from '../UiTagsInputInput.vue';
|
|
5
|
+
import UiTagsInputItem from '../UiTagsInputItem.vue';
|
|
6
|
+
import UiTagsInputItemText from '../UiTagsInputItemText.vue';
|
|
7
|
+
import UiTagsInputItemDelete from '../UiTagsInputItemDelete.vue';
|
|
8
|
+
|
|
9
|
+
describe('UiTagsInput', () => {
|
|
10
|
+
test('renders tags input with items', () => {
|
|
11
|
+
render(UiTagsInput, {
|
|
12
|
+
props: {
|
|
13
|
+
modelValue: ['Apple', 'Banana'],
|
|
14
|
+
},
|
|
15
|
+
slots: {
|
|
16
|
+
default: `
|
|
17
|
+
<span>Apple</span>
|
|
18
|
+
<span>Banana</span>
|
|
19
|
+
`,
|
|
20
|
+
},
|
|
21
|
+
global: {
|
|
22
|
+
components: {
|
|
23
|
+
UiTagsInputInput,
|
|
24
|
+
UiTagsInputItem,
|
|
25
|
+
UiTagsInputItemText,
|
|
26
|
+
UiTagsInputItemDelete,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(document.querySelector('[data-slot="tags-input"]')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('renders disabled state', () => {
|
|
35
|
+
const { container } = render(UiTagsInput, {
|
|
36
|
+
props: {
|
|
37
|
+
modelValue: ['Test'],
|
|
38
|
+
disabled: true,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(container.querySelector('[data-slot="tags-input"]')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as UiTagsInput } from './UiTagsInput.vue';
|
|
2
|
+
export { default as UiTagsInputInput } from './UiTagsInputInput.vue';
|
|
3
|
+
export { default as UiTagsInputItem } from './UiTagsInputItem.vue';
|
|
4
|
+
export { default as UiTagsInputItemText } from './UiTagsInputItemText.vue';
|
|
5
|
+
export { default as UiTagsInputItemDelete } from './UiTagsInputItemDelete.vue';
|
|
6
|
+
export type * from './types';
|