@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.
Files changed (104) hide show
  1. package/README.md +0 -2
  2. package/dist/system/index.d.ts +860 -179
  3. package/dist/system/lib.js +33936 -9729
  4. package/package.json +1 -2
  5. package/src/compositions/UiDataTable/UiDataTable.stories.ts +6 -7
  6. package/src/compositions/UiDataTable/UiDataTableColumnHeader.vue +22 -8
  7. package/src/compositions/UiDataTable/UiDataTablePagination.vue +5 -5
  8. package/src/compositions/UiDatePicker/UiDatePicker.vue +2 -2
  9. package/src/primitives/UiAlert/UiAlert.stories.ts +8 -8
  10. package/src/primitives/UiBadge/UiBadge.stories.ts +9 -9
  11. package/src/primitives/UiButton/UiButton.stories.ts +12 -12
  12. package/src/primitives/UiCalendar/UiCalendar.stories.ts +4 -4
  13. package/src/primitives/UiDropdownMenu/UiDropdownMenu.stories.ts +2 -2
  14. package/src/primitives/UiIcon/UiIcon.stories.ts +59 -13
  15. package/src/primitives/UiIcon/UiIcon.vue +41 -3
  16. package/src/primitives/UiIcon/__tests__/UiIcon.test.ts +33 -4
  17. package/src/primitives/UiIcon/index.ts +1 -0
  18. package/src/primitives/UiIcon/types.ts +24 -5
  19. package/src/primitives/UiIconButton/UiIconButton.stories.ts +36 -36
  20. package/src/primitives/UiKbd/UiKbd.stories.ts +551 -0
  21. package/src/primitives/UiKbd/UiKbd.vue +62 -0
  22. package/src/primitives/UiKbd/UiKbdGroup.vue +16 -0
  23. package/src/primitives/UiKbd/__tests__/UiKbd.test.ts +46 -0
  24. package/src/primitives/UiKbd/index.ts +3 -0
  25. package/src/primitives/UiKbd/types.ts +32 -0
  26. package/src/primitives/UiLabel/UiLabel.stories.ts +192 -0
  27. package/src/primitives/UiLabel/UiLabel.vue +16 -0
  28. package/src/primitives/UiLabel/__tests__/UiLabel.test.ts +43 -0
  29. package/src/primitives/UiLabel/index.ts +2 -0
  30. package/src/primitives/UiLabel/types.ts +16 -0
  31. package/src/primitives/UiListbox/UiListbox.stories.ts +607 -0
  32. package/src/primitives/UiListbox/UiListbox.vue +30 -0
  33. package/src/primitives/UiListbox/UiListboxContent.vue +16 -0
  34. package/src/primitives/UiListbox/UiListboxFilter.vue +16 -0
  35. package/src/primitives/UiListbox/UiListboxGroup.vue +16 -0
  36. package/src/primitives/UiListbox/UiListboxGroupLabel.vue +16 -0
  37. package/src/primitives/UiListbox/UiListboxItem.vue +20 -0
  38. package/src/primitives/UiListbox/UiListboxItemIndicator.vue +16 -0
  39. package/src/primitives/UiListbox/__tests__/UiListbox.test.ts +42 -0
  40. package/src/primitives/UiListbox/index.ts +8 -0
  41. package/src/primitives/UiListbox/types.ts +119 -0
  42. package/src/primitives/UiPopover/index.ts +1 -0
  43. package/src/primitives/UiSeparator/UiSeparator.stories.ts +177 -0
  44. package/src/primitives/UiSeparator/UiSeparator.vue +17 -0
  45. package/src/primitives/UiSeparator/__tests__/UiSeparator.test.ts +34 -0
  46. package/src/primitives/UiSeparator/index.ts +2 -0
  47. package/src/primitives/UiSeparator/types.ts +23 -0
  48. package/src/primitives/UiSkeleton/UiSkeleton.stories.ts +247 -0
  49. package/src/primitives/UiSkeleton/UiSkeleton.vue +24 -0
  50. package/src/primitives/UiSkeleton/__tests__/UiSkeleton.test.ts +47 -0
  51. package/src/primitives/UiSkeleton/index.ts +2 -0
  52. package/src/primitives/UiSkeleton/types.ts +26 -0
  53. package/src/primitives/UiTable/UiTable.stories.ts +2 -2
  54. package/src/primitives/UiTagsInput/UiTagsInput.stories.ts +538 -0
  55. package/src/primitives/UiTagsInput/UiTagsInput.vue +27 -0
  56. package/src/primitives/UiTagsInput/UiTagsInputInput.vue +14 -0
  57. package/src/primitives/UiTagsInput/UiTagsInputItem.vue +16 -0
  58. package/src/primitives/UiTagsInput/UiTagsInputItemDelete.vue +16 -0
  59. package/src/primitives/UiTagsInput/UiTagsInputItemText.vue +14 -0
  60. package/src/primitives/UiTagsInput/__tests__/UiTagsInput.test.ts +44 -0
  61. package/src/primitives/UiTagsInput/index.ts +6 -0
  62. package/src/primitives/UiTagsInput/types.ts +60 -0
  63. package/src/primitives/UiToggle/UiToggle.stories.ts +370 -0
  64. package/src/primitives/UiToggle/UiToggle.vue +28 -0
  65. package/src/primitives/UiToggle/__tests__/UiToggle.test.ts +62 -0
  66. package/src/primitives/UiToggle/index.ts +2 -0
  67. package/src/primitives/UiToggle/types.ts +35 -0
  68. package/src/primitives/UiTooltip/UiTooltip.stories.ts +8 -8
  69. package/src/primitives/index.ts +7 -0
  70. package/src/primitives/shadcn/accordion/AccordionTrigger.vue +5 -4
  71. package/src/primitives/shadcn/calendar/CalendarNextButton.vue +2 -2
  72. package/src/primitives/shadcn/calendar/CalendarPrevButton.vue +2 -2
  73. package/src/primitives/shadcn/checkbox/Checkbox.vue +2 -2
  74. package/src/primitives/shadcn/dropdown-menu/DropdownMenuCheckboxItem.vue +2 -2
  75. package/src/primitives/shadcn/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
  76. package/src/primitives/shadcn/kbd/Kbd.vue +20 -0
  77. package/src/primitives/shadcn/kbd/KbdGroup.vue +12 -0
  78. package/src/primitives/shadcn/kbd/index.ts +2 -0
  79. package/src/primitives/shadcn/listbox/Listbox.vue +23 -0
  80. package/src/primitives/shadcn/listbox/ListboxContent.vue +26 -0
  81. package/src/primitives/shadcn/listbox/ListboxFilter.vue +30 -0
  82. package/src/primitives/shadcn/listbox/ListboxGroup.vue +26 -0
  83. package/src/primitives/shadcn/listbox/ListboxGroupLabel.vue +26 -0
  84. package/src/primitives/shadcn/listbox/ListboxItem.vue +32 -0
  85. package/src/primitives/shadcn/listbox/ListboxItemIndicator.vue +40 -0
  86. package/src/primitives/shadcn/listbox/index.ts +7 -0
  87. package/src/primitives/shadcn/native-select/NativeSelect.vue +5 -4
  88. package/src/primitives/shadcn/range-calendar/RangeCalendarNextButton.vue +2 -2
  89. package/src/primitives/shadcn/range-calendar/RangeCalendarPrevButton.vue +2 -2
  90. package/src/primitives/shadcn/select/SelectItem.vue +2 -2
  91. package/src/primitives/shadcn/select/SelectScrollDownButton.vue +2 -2
  92. package/src/primitives/shadcn/select/SelectScrollUpButton.vue +2 -2
  93. package/src/primitives/shadcn/select/SelectTrigger.vue +2 -2
  94. package/src/primitives/shadcn/skeleton/Skeleton.vue +10 -0
  95. package/src/primitives/shadcn/skeleton/index.ts +1 -0
  96. package/src/primitives/shadcn/spinner/Spinner.vue +5 -4
  97. package/src/primitives/shadcn/tags-input/TagsInput.vue +33 -0
  98. package/src/primitives/shadcn/tags-input/TagsInputInput.vue +24 -0
  99. package/src/primitives/shadcn/tags-input/TagsInputItem.vue +31 -0
  100. package/src/primitives/shadcn/tags-input/TagsInputItemDelete.vue +46 -0
  101. package/src/primitives/shadcn/tags-input/TagsInputItemText.vue +24 -0
  102. package/src/primitives/shadcn/tags-input/index.ts +5 -0
  103. package/src/primitives/shadcn/toggle/Toggle.vue +34 -0
  104. package/src/primitives/shadcn/toggle/index.ts +27 -0
@@ -0,0 +1,551 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { ref, onMounted, onUnmounted } from 'vue';
3
+ import UiKbd from './UiKbd.vue';
4
+ import UiKbdGroup from './UiKbdGroup.vue';
5
+ import { UiButton } from '@/primitives/UiButton';
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipProvider,
10
+ TooltipTrigger,
11
+ } from '@/primitives/shadcn/tooltip';
12
+ import {
13
+ UiDrawer,
14
+ UiDrawerClose,
15
+ UiDrawerContent,
16
+ UiDrawerDescription,
17
+ UiDrawerFooter,
18
+ UiDrawerHeader,
19
+ UiDrawerTitle,
20
+ } from '@/primitives/UiDrawer';
21
+
22
+ const meta: Meta<typeof UiKbd> = {
23
+ title: 'Primitives/UiKbd',
24
+ component: UiKbd,
25
+ tags: ['autodocs'],
26
+ argTypes: {
27
+ size: {
28
+ control: 'select',
29
+ options: ['sm', 'default', 'lg'],
30
+ description: 'Size variant for the kbd element',
31
+ },
32
+ },
33
+ args: {
34
+ size: 'default',
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+
40
+ type Story = StoryObj<typeof UiKbd>;
41
+
42
+ const defaultTemplateSource = `<script setup lang="ts">
43
+ import { UiKbd, UiKbdGroup } from '@aleph-alpha/ui-library'
44
+ </script>
45
+
46
+ <template>
47
+ <div class="flex flex-col items-center gap-4">
48
+ <UiKbdGroup>
49
+ <UiKbd>⌘</UiKbd>
50
+ <UiKbd>⇧</UiKbd>
51
+ <UiKbd>⌥</UiKbd>
52
+ <UiKbd>⌃</UiKbd>
53
+ </UiKbdGroup>
54
+
55
+ <UiKbdGroup>
56
+ <UiKbd>Ctrl</UiKbd>
57
+ <span>+</span>
58
+ <UiKbd>B</UiKbd>
59
+ </UiKbdGroup>
60
+ </div>
61
+ </template>`;
62
+
63
+ /**
64
+ * Default keyboard shortcut display with modifier symbols and key combinations.
65
+ * Use the size control to change the kbd size.
66
+ */
67
+ export const Default: Story = {
68
+ render: (args) => ({
69
+ components: { UiKbd, UiKbdGroup },
70
+ setup() {
71
+ return { args };
72
+ },
73
+ template: `
74
+ <div class="flex flex-col items-center gap-4">
75
+ <UiKbdGroup>
76
+ <UiKbd v-bind="args">⌘</UiKbd>
77
+ <UiKbd v-bind="args">⇧</UiKbd>
78
+ <UiKbd v-bind="args">⌥</UiKbd>
79
+ <UiKbd v-bind="args">⌃</UiKbd>
80
+ </UiKbdGroup>
81
+
82
+ <UiKbdGroup>
83
+ <UiKbd v-bind="args">Ctrl</UiKbd>
84
+ <span>+</span>
85
+ <UiKbd v-bind="args">B</UiKbd>
86
+ </UiKbdGroup>
87
+ </div>
88
+ `,
89
+ }),
90
+ parameters: {
91
+ docs: {
92
+ source: {
93
+ code: defaultTemplateSource,
94
+ },
95
+ },
96
+ },
97
+ };
98
+
99
+ const interactiveTemplateSource = `<script setup lang="ts">
100
+ import { ref, onMounted, onUnmounted } from 'vue'
101
+ import { UiKbd, UiKbdGroup, UiButton, UiDrawer, UiDrawerContent, UiDrawerHeader, UiDrawerTitle, UiDrawerDescription, UiDrawerFooter, UiDrawerClose } from '@aleph-alpha/ui-library'
102
+
103
+ const isDrawerOpen = ref(false)
104
+ const showHelp = ref(false)
105
+
106
+ const handleKeydown = (e: KeyboardEvent) => {
107
+ if ((e.ctrlKey || e.metaKey) && e.key === '.') {
108
+ e.preventDefault()
109
+ isDrawerOpen.value = true
110
+ }
111
+ if ((e.ctrlKey || e.metaKey) && e.key === '/') {
112
+ e.preventDefault()
113
+ showHelp.value = !showHelp.value
114
+ }
115
+ if (e.key === 'Escape') {
116
+ isDrawerOpen.value = false
117
+ showHelp.value = false
118
+ }
119
+ }
120
+
121
+ onMounted(() => window.addEventListener('keydown', handleKeydown))
122
+ onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
123
+ </script>
124
+
125
+ <template>
126
+ <div class="flex flex-col gap-3 rounded-lg border p-4">
127
+ <div class="flex items-center justify-between gap-8">
128
+ <span class="text-sm">Open Command Palette</span>
129
+ <UiKbdGroup>
130
+ <UiKbd>Ctrl</UiKbd>
131
+ <span class="text-muted-foreground">+</span>
132
+ <UiKbd>.</UiKbd>
133
+ </UiKbdGroup>
134
+ </div>
135
+ <div class="flex items-center justify-between gap-8">
136
+ <span class="text-sm">Toggle Help</span>
137
+ <UiKbdGroup>
138
+ <UiKbd>Ctrl</UiKbd>
139
+ <span class="text-muted-foreground">+</span>
140
+ <UiKbd>/</UiKbd>
141
+ </UiKbdGroup>
142
+ </div>
143
+ </div>
144
+
145
+ <UiDrawer v-model:open="isDrawerOpen">
146
+ <UiDrawerContent>
147
+ <UiDrawerHeader>
148
+ <UiDrawerTitle>Command Palette</UiDrawerTitle>
149
+ <UiDrawerDescription>
150
+ Press <UiKbd size="sm">Esc</UiKbd> to close.
151
+ </UiDrawerDescription>
152
+ </UiDrawerHeader>
153
+ </UiDrawerContent>
154
+ </UiDrawer>
155
+ </template>`;
156
+
157
+ /**
158
+ * Interactive keyboard shortcut demo. Press the keyboard shortcuts to trigger actions!
159
+ * - Press `Ctrl/⌘ + .` to open the command palette drawer
160
+ * - Press `Ctrl/⌘ + /` to toggle help
161
+ */
162
+ export const Interactive: Story = {
163
+ parameters: {
164
+ docs: {
165
+ source: {
166
+ code: interactiveTemplateSource,
167
+ },
168
+ },
169
+ },
170
+ render: () => ({
171
+ components: {
172
+ UiKbd,
173
+ UiKbdGroup,
174
+ UiButton,
175
+ UiDrawer,
176
+ UiDrawerClose,
177
+ UiDrawerContent,
178
+ UiDrawerDescription,
179
+ UiDrawerFooter,
180
+ UiDrawerHeader,
181
+ UiDrawerTitle,
182
+ },
183
+ setup() {
184
+ const isDrawerOpen = ref(false);
185
+ const showHelp = ref(false);
186
+ const lastPressed = ref('');
187
+
188
+ const handleKeydown = (e: KeyboardEvent) => {
189
+ if ((e.ctrlKey || e.metaKey) && e.key === '.') {
190
+ e.preventDefault();
191
+ isDrawerOpen.value = true;
192
+ lastPressed.value = 'Ctrl + .';
193
+ }
194
+ if ((e.ctrlKey || e.metaKey) && e.key === '/') {
195
+ e.preventDefault();
196
+ showHelp.value = !showHelp.value;
197
+ lastPressed.value = 'Ctrl + /';
198
+ }
199
+ if (e.key === 'Escape') {
200
+ isDrawerOpen.value = false;
201
+ showHelp.value = false;
202
+ lastPressed.value = 'Esc';
203
+ }
204
+ };
205
+
206
+ onMounted(() => {
207
+ window.addEventListener('keydown', handleKeydown);
208
+ });
209
+
210
+ onUnmounted(() => {
211
+ window.removeEventListener('keydown', handleKeydown);
212
+ });
213
+
214
+ return { isDrawerOpen, showHelp, lastPressed };
215
+ },
216
+ template: `
217
+ <div class="flex flex-col items-center gap-6 p-4">
218
+ <div class="text-center space-y-2">
219
+ <h3 class="text-lg font-semibold">Interactive Keyboard Shortcuts</h3>
220
+ <p class="text-sm text-muted-foreground">Try pressing these keyboard shortcuts:</p>
221
+ </div>
222
+
223
+ <div class="flex flex-col gap-3 rounded-lg border p-4 bg-muted/30">
224
+ <div class="flex items-center justify-between gap-8">
225
+ <span class="text-sm">Open Command Palette</span>
226
+ <UiKbdGroup>
227
+ <UiKbd>Ctrl</UiKbd>
228
+ <span class="text-muted-foreground">+</span>
229
+ <UiKbd>.</UiKbd>
230
+ </UiKbdGroup>
231
+ </div>
232
+ <div class="flex items-center justify-between gap-8">
233
+ <span class="text-sm">Toggle Help</span>
234
+ <UiKbdGroup>
235
+ <UiKbd>Ctrl</UiKbd>
236
+ <span class="text-muted-foreground">+</span>
237
+ <UiKbd>/</UiKbd>
238
+ </UiKbdGroup>
239
+ </div>
240
+ <div class="flex items-center justify-between gap-8">
241
+ <span class="text-sm">Close / Dismiss</span>
242
+ <UiKbd>Esc</UiKbd>
243
+ </div>
244
+ </div>
245
+
246
+ <div v-if="lastPressed" class="text-sm text-muted-foreground">
247
+ Last pressed: <UiKbd>{{ lastPressed }}</UiKbd>
248
+ </div>
249
+
250
+ <div v-if="showHelp" class="w-full max-w-md rounded-lg border bg-card p-4 shadow-lg">
251
+ <div class="flex items-center justify-between mb-3">
252
+ <h4 class="font-medium">Keyboard Shortcuts Help</h4>
253
+ <UiKbd>Esc</UiKbd>
254
+ </div>
255
+ <div class="space-y-2 text-sm text-muted-foreground">
256
+ <p>Use keyboard shortcuts to navigate quickly through the application.</p>
257
+ <p>Press <UiKbd>Esc</UiKbd> to close this help panel.</p>
258
+ </div>
259
+ </div>
260
+
261
+ <UiDrawer v-model:open="isDrawerOpen">
262
+ <UiDrawerContent>
263
+ <UiDrawerHeader>
264
+ <UiDrawerTitle>Command Palette</UiDrawerTitle>
265
+ <UiDrawerDescription>
266
+ Quick access to common actions. Press <UiKbd size="sm">Esc</UiKbd> to close.
267
+ </UiDrawerDescription>
268
+ </UiDrawerHeader>
269
+ <div class="p-4 space-y-2">
270
+ <div class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-accent cursor-pointer">
271
+ <span>New Document</span>
272
+ <UiKbdGroup>
273
+ <UiKbd size="sm">Ctrl</UiKbd>
274
+ <UiKbd size="sm">N</UiKbd>
275
+ </UiKbdGroup>
276
+ </div>
277
+ <div class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-accent cursor-pointer">
278
+ <span>Search Files</span>
279
+ <UiKbdGroup>
280
+ <UiKbd size="sm">Ctrl</UiKbd>
281
+ <UiKbd size="sm">Shift</UiKbd>
282
+ <UiKbd size="sm">F</UiKbd>
283
+ </UiKbdGroup>
284
+ </div>
285
+ <div class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-accent cursor-pointer">
286
+ <span>Go to Line</span>
287
+ <UiKbdGroup>
288
+ <UiKbd size="sm">Ctrl</UiKbd>
289
+ <UiKbd size="sm">G</UiKbd>
290
+ </UiKbdGroup>
291
+ </div>
292
+ </div>
293
+ <UiDrawerFooter>
294
+ <UiDrawerClose as-child>
295
+ <UiButton variant="outline">Close</UiButton>
296
+ </UiDrawerClose>
297
+ </UiDrawerFooter>
298
+ </UiDrawerContent>
299
+ </UiDrawer>
300
+ </div>
301
+ `,
302
+ }),
303
+ };
304
+
305
+ const groupTemplateSource = `<script setup lang="ts">
306
+ import { UiKbd, UiKbdGroup } from '@aleph-alpha/ui-library'
307
+ </script>
308
+
309
+ <template>
310
+ <p class="text-muted-foreground text-sm">
311
+ Use
312
+ <UiKbdGroup>
313
+ <UiKbd>Ctrl</UiKbd>
314
+ <UiKbd>Shift</UiKbd>
315
+ <UiKbd>P</UiKbd>
316
+ </UiKbdGroup>
317
+ to open the command palette
318
+ </p>
319
+ </template>`;
320
+
321
+ /**
322
+ * Use the `UiKbdGroup` component to group keyboard keys together.
323
+ */
324
+ export const Group: Story = {
325
+ render: (args) => ({
326
+ components: { UiKbd, UiKbdGroup },
327
+ setup() {
328
+ return { args };
329
+ },
330
+ template: `
331
+ <div class="flex flex-col items-center gap-4">
332
+ <p class="text-muted-foreground text-sm">
333
+ Use
334
+ <UiKbdGroup>
335
+ <UiKbd v-bind="args">Ctrl + Shift + P</UiKbd>
336
+ </UiKbdGroup>
337
+ to open the command palette
338
+ </p>
339
+
340
+ <div class="mt-8 w-full max-w-md rounded-lg border bg-popover p-4 shadow-lg">
341
+ <div class="flex items-center gap-2 border-b pb-3">
342
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
343
+ <span class="flex-1 text-sm text-muted-foreground">Type a command or search...</span>
344
+ <UiKbd v-bind="args">Esc</UiKbd>
345
+ </div>
346
+ <div class="pt-3 space-y-1">
347
+ <div class="flex items-center justify-between rounded-md px-2 py-1.5 text-sm hover:bg-accent cursor-pointer">
348
+ <span>Toggle Sidebar</span>
349
+ <UiKbdGroup>
350
+ <UiKbd v-bind="args">Ctrl</UiKbd>
351
+ <UiKbd v-bind="args">\\</UiKbd>
352
+ </UiKbdGroup>
353
+ </div>
354
+ <div class="flex items-center justify-between rounded-md px-2 py-1.5 text-sm hover:bg-accent cursor-pointer">
355
+ <span>Quick Search</span>
356
+ <UiKbdGroup>
357
+ <UiKbd v-bind="args">Ctrl</UiKbd>
358
+ <UiKbd v-bind="args">Shift</UiKbd>
359
+ <UiKbd v-bind="args">F</UiKbd>
360
+ </UiKbdGroup>
361
+ </div>
362
+ <div class="flex items-center justify-between rounded-md px-2 py-1.5 text-sm hover:bg-accent cursor-pointer">
363
+ <span>Go to Line</span>
364
+ <UiKbdGroup>
365
+ <UiKbd v-bind="args">Ctrl</UiKbd>
366
+ <UiKbd v-bind="args">G</UiKbd>
367
+ </UiKbdGroup>
368
+ </div>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ `,
373
+ }),
374
+ parameters: {
375
+ docs: {
376
+ source: {
377
+ code: groupTemplateSource,
378
+ },
379
+ },
380
+ },
381
+ };
382
+
383
+ const buttonTemplateSource = `<script setup lang="ts">
384
+ import { UiKbd, UiButton } from '@aleph-alpha/ui-library'
385
+ </script>
386
+
387
+ <template>
388
+ <div class="flex flex-wrap items-center gap-4">
389
+ <UiButton variant="outline" size="sm" class="pr-2">
390
+ Accept <UiKbd>⏎</UiKbd>
391
+ </UiButton>
392
+ <UiButton variant="outline" size="sm" class="pr-2">
393
+ Cancel <UiKbd>Esc</UiKbd>
394
+ </UiButton>
395
+ </div>
396
+ </template>`;
397
+
398
+ /**
399
+ * Use the `UiKbd` component inside a button.
400
+ */
401
+ export const Button: Story = {
402
+ render: (args) => ({
403
+ components: { UiKbd, UiButton },
404
+ setup() {
405
+ return { args };
406
+ },
407
+ template: `
408
+ <div class="flex flex-wrap items-center gap-4">
409
+ <UiButton variant="outline" size="sm" class="pr-2">
410
+ Accept <UiKbd v-bind="args">⏎</UiKbd>
411
+ </UiButton>
412
+ <UiButton variant="outline" size="sm" class="pr-2">
413
+ Cancel <UiKbd v-bind="args">Esc</UiKbd>
414
+ </UiButton>
415
+ </div>
416
+ `,
417
+ }),
418
+ parameters: {
419
+ docs: {
420
+ source: {
421
+ code: buttonTemplateSource,
422
+ },
423
+ },
424
+ },
425
+ };
426
+
427
+ const withTooltipTemplateSource = `<script setup lang="ts">
428
+ import { UiKbd, UiKbdGroup, UiButton, UiTooltip, UiTooltipContent, UiTooltipProvider, UiTooltipTrigger } from '@aleph-alpha/ui-library'
429
+ </script>
430
+
431
+ <template>
432
+ <UiTooltipProvider>
433
+ <UiTooltip>
434
+ <UiTooltipTrigger as-child>
435
+ <UiButton size="sm" variant="outline">Save</UiButton>
436
+ </UiTooltipTrigger>
437
+ <UiTooltipContent>
438
+ <div class="flex items-center gap-2">
439
+ Save Changes <UiKbd>S</UiKbd>
440
+ </div>
441
+ </UiTooltipContent>
442
+ </UiTooltip>
443
+ </UiTooltipProvider>
444
+ </template>`;
445
+
446
+ /**
447
+ * Use the `UiKbd` component inside a tooltip.
448
+ */
449
+ export const WithTooltip: Story = {
450
+ render: (args) => ({
451
+ components: {
452
+ UiKbd,
453
+ UiKbdGroup,
454
+ UiButton,
455
+ Tooltip,
456
+ TooltipContent,
457
+ TooltipProvider,
458
+ TooltipTrigger,
459
+ },
460
+ setup() {
461
+ return { args };
462
+ },
463
+ template: `
464
+ <div class="flex flex-wrap gap-4">
465
+ <TooltipProvider>
466
+ <Tooltip>
467
+ <TooltipTrigger as-child>
468
+ <UiButton size="sm" variant="outline">Save</UiButton>
469
+ </TooltipTrigger>
470
+ <TooltipContent>
471
+ <div class="flex items-center gap-2">
472
+ Save Changes <UiKbd v-bind="args">S</UiKbd>
473
+ </div>
474
+ </TooltipContent>
475
+ </Tooltip>
476
+ </TooltipProvider>
477
+
478
+ <TooltipProvider>
479
+ <Tooltip>
480
+ <TooltipTrigger as-child>
481
+ <UiButton size="sm" variant="outline">Print</UiButton>
482
+ </TooltipTrigger>
483
+ <TooltipContent>
484
+ <div class="flex items-center gap-2">
485
+ Print Document
486
+ <UiKbdGroup>
487
+ <UiKbd v-bind="args">Ctrl</UiKbd>
488
+ <UiKbd v-bind="args">P</UiKbd>
489
+ </UiKbdGroup>
490
+ </div>
491
+ </TooltipContent>
492
+ </Tooltip>
493
+ </TooltipProvider>
494
+ </div>
495
+ `,
496
+ }),
497
+ parameters: {
498
+ docs: {
499
+ source: {
500
+ code: withTooltipTemplateSource,
501
+ },
502
+ },
503
+ },
504
+ };
505
+
506
+ const sizesTemplateSource = `<script setup lang="ts">
507
+ import { UiKbd } from '@aleph-alpha/ui-library'
508
+ </script>
509
+
510
+ <template>
511
+ <div class="flex items-center gap-4">
512
+ <UiKbd size="sm">Ctrl</UiKbd>
513
+ <UiKbd>Ctrl</UiKbd>
514
+ <UiKbd size="lg">Ctrl</UiKbd>
515
+ </div>
516
+ </template>`;
517
+
518
+ /**
519
+ * Different size variants.
520
+ */
521
+ export const Sizes: Story = {
522
+ render: () => ({
523
+ components: { UiKbd },
524
+ template: `
525
+ <div class="flex items-center gap-4">
526
+ <div class="flex items-center gap-1">
527
+ <span class="text-xs text-muted-foreground">sm:</span>
528
+ <UiKbd size="sm">Ctrl</UiKbd>
529
+ <UiKbd size="sm">S</UiKbd>
530
+ </div>
531
+ <div class="flex items-center gap-1">
532
+ <span class="text-xs text-muted-foreground">default:</span>
533
+ <UiKbd>Ctrl</UiKbd>
534
+ <UiKbd>S</UiKbd>
535
+ </div>
536
+ <div class="flex items-center gap-1">
537
+ <span class="text-xs text-muted-foreground">lg:</span>
538
+ <UiKbd size="lg">Ctrl</UiKbd>
539
+ <UiKbd size="lg">S</UiKbd>
540
+ </div>
541
+ </div>
542
+ `,
543
+ }),
544
+ parameters: {
545
+ docs: {
546
+ source: {
547
+ code: sizesTemplateSource,
548
+ },
549
+ },
550
+ },
551
+ };
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * UiKbd renders keyboard key labels in a monospace, pill-shaped container.
4
+ *
5
+ * This is a thin wrapper around the shadcn-vue {@link Kbd} primitive that adds
6
+ * Aleph Alpha design system defaults and size variants.
7
+ *
8
+ * @component UiKbd
9
+ *
10
+ * @prop {'sm' | 'default' | 'lg'} [size='default'] -
11
+ * Controls the visual size of the keyboard key. `sm` renders a compact key,
12
+ * `default` uses the base size, and `lg` renders a larger key.
13
+ *
14
+ * @slot default
15
+ * Content to display inside the keyboard key, typically the key label
16
+ * (for example, ⌘, Enter, or shortcut combinations).
17
+ *
18
+ * @example Basic usage
19
+ * ```vue
20
+ * <template>
21
+ * <UiKbd>⌘</UiKbd>
22
+ * </template>
23
+ * ```
24
+ *
25
+ * @example Size variants
26
+ * ```vue
27
+ * <template>
28
+ * <UiKbd size="sm">Esc</UiKbd>
29
+ * <UiKbd>Enter</UiKbd>
30
+ * <UiKbd size="lg">Shift</UiKbd>
31
+ * </template>
32
+ * ```
33
+ */
34
+ import { Kbd as ShadcnKbd } from '@/primitives/shadcn/kbd';
35
+ import { computed } from 'vue';
36
+ import type { UiKbdProps } from './types';
37
+
38
+ defineOptions({
39
+ name: 'UiKbd',
40
+ });
41
+
42
+ const props = withDefaults(defineProps<UiKbdProps>(), {
43
+ size: 'default',
44
+ });
45
+
46
+ const sizeClasses = computed(() => {
47
+ switch (props.size) {
48
+ case 'sm':
49
+ return 'h-4 px-1 text-[9px]';
50
+ case 'lg':
51
+ return 'h-6 px-2 text-xs';
52
+ default:
53
+ return '';
54
+ }
55
+ });
56
+ </script>
57
+
58
+ <template>
59
+ <ShadcnKbd :class="sizeClasses">
60
+ <slot />
61
+ </ShadcnKbd>
62
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { KbdGroup as ShadcnKbdGroup } from '@/primitives/shadcn/kbd';
3
+ import type { UiKbdGroupProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiKbdGroup',
7
+ });
8
+
9
+ const props = defineProps<UiKbdGroupProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnKbdGroup :class="props.class">
14
+ <slot />
15
+ </ShadcnKbdGroup>
16
+ </template>
@@ -0,0 +1,46 @@
1
+ import { render } from '@testing-library/vue';
2
+ import { describe, expect, test } from 'vitest';
3
+ import UiKbd from '../UiKbd.vue';
4
+ import UiKbdGroup from '../UiKbdGroup.vue';
5
+
6
+ describe('UiKbd', () => {
7
+ test('renders kbd element with slot content', () => {
8
+ const { getByText } = render(UiKbd, {
9
+ slots: { default: 'Ctrl' },
10
+ });
11
+ expect(getByText('Ctrl')).toBeInTheDocument();
12
+ });
13
+
14
+ test('renders as kbd HTML element', () => {
15
+ const { container } = render(UiKbd, {
16
+ slots: { default: 'K' },
17
+ });
18
+ const kbd = container.querySelector('kbd');
19
+ expect(kbd).toBeInTheDocument();
20
+ });
21
+
22
+ test('has data-slot attribute', () => {
23
+ const { container } = render(UiKbd, {
24
+ slots: { default: 'Enter' },
25
+ });
26
+ const kbd = container.querySelector('[data-slot="kbd"]');
27
+ expect(kbd).toBeInTheDocument();
28
+ });
29
+ });
30
+
31
+ describe('UiKbdGroup', () => {
32
+ test('renders span element with slot content', () => {
33
+ const { getByText } = render(UiKbdGroup, {
34
+ slots: { default: 'Content' },
35
+ });
36
+ expect(getByText('Content')).toBeInTheDocument();
37
+ });
38
+
39
+ test('has data-slot attribute', () => {
40
+ const { container } = render(UiKbdGroup, {
41
+ slots: { default: 'Group' },
42
+ });
43
+ const group = container.querySelector('[data-slot="kbd-group"]');
44
+ expect(group).toBeInTheDocument();
45
+ });
46
+ });
@@ -0,0 +1,3 @@
1
+ export { default as UiKbd } from './UiKbd.vue';
2
+ export { default as UiKbdGroup } from './UiKbdGroup.vue';
3
+ export type { UiKbdProps, UiKbdGroupProps } from './types';