@aleph-alpha/ui-library 1.13.0 → 1.14.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 (120) hide show
  1. package/README.md +1 -1
  2. package/config.js +34 -1
  3. package/dist/system/index.d.ts +1728 -234
  4. package/dist/system/lib.js +19804 -16600
  5. package/package.json +1 -1
  6. package/src/components/UiKbd/UiKbd.stories.ts +1 -1
  7. package/src/components/UiNavigationMenu/UiNavigationMenu.stories.ts +1196 -0
  8. package/src/components/UiNavigationMenu/UiNavigationMenu.vue +39 -0
  9. package/src/components/UiNavigationMenu/UiNavigationMenuContent.vue +25 -0
  10. package/src/components/UiNavigationMenu/UiNavigationMenuIndicator.vue +14 -0
  11. package/src/components/UiNavigationMenu/UiNavigationMenuItem.vue +16 -0
  12. package/src/components/UiNavigationMenu/UiNavigationMenuLink.vue +27 -0
  13. package/src/components/UiNavigationMenu/UiNavigationMenuList.vue +16 -0
  14. package/src/components/UiNavigationMenu/UiNavigationMenuTrigger.vue +16 -0
  15. package/src/components/UiNavigationMenu/__tests__/UiNavigationMenu.test.ts +428 -0
  16. package/src/components/UiNavigationMenu/index.ts +11 -0
  17. package/src/components/UiNavigationMenu/types.ts +185 -0
  18. package/src/components/UiSheet/UiSheet.stories.ts +715 -0
  19. package/src/components/UiSheet/__tests__/UiSheet.test.ts +229 -0
  20. package/src/components/UiSheet/index.ts +12 -0
  21. package/src/components/UiSheet/types.ts +83 -0
  22. package/src/components/UiSidebar/UiSidebar.stories.ts +1010 -0
  23. package/src/components/UiSidebar/UiSidebar.vue +20 -0
  24. package/src/components/UiSidebar/UiSidebarGroupAction.vue +18 -0
  25. package/src/components/UiSidebar/UiSidebarGroupLabel.vue +18 -0
  26. package/src/components/UiSidebar/UiSidebarHeaderTrigger.vue +53 -0
  27. package/src/components/UiSidebar/UiSidebarInput.vue +14 -0
  28. package/src/components/UiSidebar/UiSidebarMenuAction.vue +19 -0
  29. package/src/components/UiSidebar/UiSidebarMenuButton.vue +27 -0
  30. package/src/components/UiSidebar/UiSidebarMenuSkeleton.vue +16 -0
  31. package/src/components/UiSidebar/UiSidebarMenuSubButton.vue +24 -0
  32. package/src/components/UiSidebar/UiSidebarProvider.vue +18 -0
  33. package/src/components/UiSidebar/UiSidebarSeparator.vue +13 -0
  34. package/src/components/UiSidebar/__tests__/UiSidebar.test.ts +221 -0
  35. package/src/components/UiSidebar/index.ts +34 -0
  36. package/src/components/UiSidebar/types.ts +168 -0
  37. package/src/components/UiStepper/UiStepper.stories.ts +425 -0
  38. package/src/components/UiStepper/UiStepper.vue +27 -0
  39. package/src/components/UiStepper/UiStepperDescription.vue +20 -0
  40. package/src/components/UiStepper/UiStepperIndicator.vue +13 -0
  41. package/src/components/UiStepper/UiStepperItem.vue +25 -0
  42. package/src/components/UiStepper/UiStepperSeparator.vue +17 -0
  43. package/src/components/UiStepper/UiStepperTitle.vue +19 -0
  44. package/src/components/UiStepper/UiStepperTrigger.vue +18 -0
  45. package/src/components/UiStepper/__tests__/UiStepper.test.ts +167 -0
  46. package/src/components/UiStepper/index.ts +9 -0
  47. package/src/components/UiStepper/types.ts +65 -0
  48. package/src/components/core/alert/index.ts +2 -2
  49. package/src/components/core/alert-dialog/AlertDialogContent.vue +1 -1
  50. package/src/components/core/card/Card.vue +1 -1
  51. package/src/components/core/drawer/DrawerContent.vue +1 -1
  52. package/src/components/core/dropdown-menu/DropdownMenuContent.vue +1 -1
  53. package/src/components/core/dropdown-menu/DropdownMenuSubContent.vue +1 -1
  54. package/src/components/core/input/Input.vue +1 -1
  55. package/src/components/core/native-select/NativeSelect.vue +1 -1
  56. package/src/components/core/native-select/NativeSelectOptGroup.vue +1 -1
  57. package/src/components/core/native-select/NativeSelectOption.vue +1 -1
  58. package/src/components/core/navigation-menu/NavigationMenu.vue +40 -0
  59. package/src/components/core/navigation-menu/NavigationMenuContent.vue +28 -0
  60. package/src/components/core/navigation-menu/NavigationMenuIndicator.vue +26 -0
  61. package/src/components/core/navigation-menu/NavigationMenuItem.vue +19 -0
  62. package/src/components/core/navigation-menu/NavigationMenuLink.vue +27 -0
  63. package/src/components/core/navigation-menu/NavigationMenuList.vue +21 -0
  64. package/src/components/core/navigation-menu/NavigationMenuTrigger.vue +27 -0
  65. package/src/components/core/navigation-menu/NavigationMenuViewport.vue +26 -0
  66. package/src/components/core/navigation-menu/index.ts +14 -0
  67. package/src/components/core/popover/PopoverContent.vue +1 -1
  68. package/src/components/core/select/SelectContent.vue +1 -1
  69. package/src/components/core/select/SelectTrigger.vue +1 -1
  70. package/src/components/core/sheet/Sheet.vue +15 -0
  71. package/src/components/core/sheet/SheetClose.vue +12 -0
  72. package/src/components/core/sheet/SheetContent.vue +56 -0
  73. package/src/components/core/sheet/SheetDescription.vue +19 -0
  74. package/src/components/core/sheet/SheetFooter.vue +9 -0
  75. package/src/components/core/sheet/SheetHeader.vue +9 -0
  76. package/src/components/core/sheet/SheetOverlay.vue +24 -0
  77. package/src/components/core/sheet/SheetTitle.vue +19 -0
  78. package/src/components/core/sheet/SheetTrigger.vue +12 -0
  79. package/src/components/core/sheet/index.ts +8 -0
  80. package/src/components/core/sidebar/Sidebar.vue +105 -0
  81. package/src/components/core/sidebar/SidebarContent.vue +21 -0
  82. package/src/components/core/sidebar/SidebarFooter.vue +16 -0
  83. package/src/components/core/sidebar/SidebarGroup.vue +16 -0
  84. package/src/components/core/sidebar/SidebarGroupAction.vue +25 -0
  85. package/src/components/core/sidebar/SidebarGroupContent.vue +16 -0
  86. package/src/components/core/sidebar/SidebarGroupLabel.vue +23 -0
  87. package/src/components/core/sidebar/SidebarHeader.vue +16 -0
  88. package/src/components/core/sidebar/SidebarInput.vue +17 -0
  89. package/src/components/core/sidebar/SidebarInset.vue +21 -0
  90. package/src/components/core/sidebar/SidebarMenu.vue +16 -0
  91. package/src/components/core/sidebar/SidebarMenuAction.vue +33 -0
  92. package/src/components/core/sidebar/SidebarMenuBadge.vue +26 -0
  93. package/src/components/core/sidebar/SidebarMenuButton.vue +49 -0
  94. package/src/components/core/sidebar/SidebarMenuButtonChild.vue +36 -0
  95. package/src/components/core/sidebar/SidebarMenuItem.vue +16 -0
  96. package/src/components/core/sidebar/SidebarMenuSkeleton.vue +32 -0
  97. package/src/components/core/sidebar/SidebarMenuSub.vue +22 -0
  98. package/src/components/core/sidebar/SidebarMenuSubButton.vue +38 -0
  99. package/src/components/core/sidebar/SidebarMenuSubItem.vue +16 -0
  100. package/src/components/core/sidebar/SidebarProvider.vue +102 -0
  101. package/src/components/core/sidebar/SidebarRail.vue +33 -0
  102. package/src/components/core/sidebar/SidebarSeparator.vue +17 -0
  103. package/src/components/core/sidebar/SidebarTrigger.vue +25 -0
  104. package/src/components/core/sidebar/index.ts +58 -0
  105. package/src/components/core/sidebar/utils.ts +19 -0
  106. package/src/components/core/stepper/Stepper.vue +20 -0
  107. package/src/components/core/stepper/StepperDescription.vue +23 -0
  108. package/src/components/core/stepper/StepperIndicator.vue +34 -0
  109. package/src/components/core/stepper/StepperItem.vue +23 -0
  110. package/src/components/core/stepper/StepperSeparator.vue +29 -0
  111. package/src/components/core/stepper/StepperTitle.vue +24 -0
  112. package/src/components/core/stepper/StepperTrigger.vue +22 -0
  113. package/src/components/core/stepper/index.ts +7 -0
  114. package/src/components/core/tabs/TabsTrigger.vue +1 -1
  115. package/src/components/core/tags-input/TagsInput.vue +1 -1
  116. package/src/components/core/textarea/Textarea.vue +1 -1
  117. package/src/components/index.ts +4 -0
  118. package/src/theme/Background.stories.ts +84 -35
  119. package/src/theme/Extended.stories.ts +4 -4
  120. package/tokens.json +145 -8
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenu as ShadcnNavigationMenu } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuProps, UiNavigationMenuEmits } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenu',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiNavigationMenuProps>(), {
10
+ modelValue: undefined,
11
+ defaultValue: undefined,
12
+ orientation: 'horizontal',
13
+ dir: 'ltr',
14
+ delayDuration: 200,
15
+ skipDelayDuration: 300,
16
+ disableClickTrigger: false,
17
+ disableHoverTrigger: false,
18
+ viewport: true,
19
+ });
20
+
21
+ const emit = defineEmits<UiNavigationMenuEmits>();
22
+ </script>
23
+
24
+ <template>
25
+ <ShadcnNavigationMenu
26
+ :model-value="props.modelValue"
27
+ :default-value="props.defaultValue"
28
+ :orientation="props.orientation"
29
+ :dir="props.dir"
30
+ :delay-duration="props.delayDuration"
31
+ :skip-delay-duration="props.skipDelayDuration"
32
+ :disable-click-trigger="props.disableClickTrigger"
33
+ :disable-hover-trigger="props.disableHoverTrigger"
34
+ :viewport="props.viewport"
35
+ @update:model-value="emit('update:modelValue', $event)"
36
+ >
37
+ <slot />
38
+ </ShadcnNavigationMenu>
39
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenuContent as ShadcnNavigationMenuContent } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuContentProps, UiNavigationMenuContentEmits } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenuContent',
7
+ });
8
+
9
+ const props = defineProps<UiNavigationMenuContentProps>();
10
+
11
+ const emit = defineEmits<UiNavigationMenuContentEmits>();
12
+ </script>
13
+
14
+ <template>
15
+ <ShadcnNavigationMenuContent
16
+ :force-mount="props.forceMount ? true : undefined"
17
+ :disable-outside-pointer-events="props.disableOutsidePointerEvents"
18
+ @escape-key-down="emit('escapeKeyDown', $event)"
19
+ @pointer-down-outside="emit('outsideClick', $event)"
20
+ @focus-outside="emit('outsideFocus', $event)"
21
+ @interact-outside="emit('outsideInteract', $event)"
22
+ >
23
+ <slot />
24
+ </ShadcnNavigationMenuContent>
25
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenuIndicator as ShadcnNavigationMenuIndicator } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuIndicatorProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenuIndicator',
7
+ });
8
+
9
+ const props = defineProps<UiNavigationMenuIndicatorProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnNavigationMenuIndicator :force-mount="props.forceMount ? true : undefined" />
14
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenuItem as ShadcnNavigationMenuItem } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuItemProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenuItem',
7
+ });
8
+
9
+ const props = defineProps<UiNavigationMenuItemProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnNavigationMenuItem :value="props.value" :as-child="props.asChild">
14
+ <slot />
15
+ </ShadcnNavigationMenuItem>
16
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { NavigationMenuLink as ShadcnNavigationMenuLink } from '@/components/core/navigation-menu';
4
+ import type { UiNavigationMenuLinkProps, UiNavigationMenuLinkEmits } from './types';
5
+
6
+ defineOptions({
7
+ name: 'UiNavigationMenuLink',
8
+ });
9
+
10
+ const props = defineProps<UiNavigationMenuLinkProps>();
11
+
12
+ const emit = defineEmits<UiNavigationMenuLinkEmits>();
13
+
14
+ // Compute aria-current value: 'page' when active, undefined otherwise
15
+ const ariaCurrent = computed(() => (props.active ? 'page' : undefined));
16
+ </script>
17
+
18
+ <template>
19
+ <ShadcnNavigationMenuLink
20
+ :active="props.active"
21
+ :as-child="props.asChild"
22
+ :aria-current="ariaCurrent"
23
+ @select="emit('select', $event)"
24
+ >
25
+ <slot />
26
+ </ShadcnNavigationMenuLink>
27
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenuList as ShadcnNavigationMenuList } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuListProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenuList',
7
+ });
8
+
9
+ const props = defineProps<UiNavigationMenuListProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnNavigationMenuList :as-child="props.asChild">
14
+ <slot />
15
+ </ShadcnNavigationMenuList>
16
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { NavigationMenuTrigger as ShadcnNavigationMenuTrigger } from '@/components/core/navigation-menu';
3
+ import type { UiNavigationMenuTriggerProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiNavigationMenuTrigger',
7
+ });
8
+
9
+ const props = defineProps<UiNavigationMenuTriggerProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnNavigationMenuTrigger :disabled="props.disabled" :as-child="props.asChild">
14
+ <slot />
15
+ </ShadcnNavigationMenuTrigger>
16
+ </template>
@@ -0,0 +1,428 @@
1
+ import { render, waitFor } from '@testing-library/vue';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { describe, expect, test, vi } from 'vitest';
4
+ import { ref } from 'vue';
5
+ import {
6
+ UiNavigationMenu,
7
+ UiNavigationMenuList,
8
+ UiNavigationMenuItem,
9
+ UiNavigationMenuTrigger,
10
+ UiNavigationMenuContent,
11
+ UiNavigationMenuLink,
12
+ UiNavigationMenuIndicator,
13
+ } from '../index';
14
+
15
+ // Helper to render a basic navigation menu
16
+ function renderNavigationMenu(
17
+ options: {
18
+ triggerText?: string;
19
+ links?: string[];
20
+ modelValue?: string;
21
+ defaultValue?: string;
22
+ disableHoverTrigger?: boolean;
23
+ disableClickTrigger?: boolean;
24
+ itemValue?: string;
25
+ } = {},
26
+ ) {
27
+ const {
28
+ triggerText = 'Products',
29
+ links = ['Link 1', 'Link 2'],
30
+ modelValue,
31
+ defaultValue,
32
+ disableHoverTrigger = false,
33
+ disableClickTrigger = false,
34
+ itemValue = 'products',
35
+ } = options;
36
+
37
+ return render({
38
+ components: {
39
+ UiNavigationMenu,
40
+ UiNavigationMenuList,
41
+ UiNavigationMenuItem,
42
+ UiNavigationMenuTrigger,
43
+ UiNavigationMenuContent,
44
+ UiNavigationMenuLink,
45
+ },
46
+ template: `
47
+ <UiNavigationMenu
48
+ ${modelValue !== undefined ? ':model-value="modelValue"' : ''}
49
+ ${defaultValue !== undefined ? ':default-value="defaultValue"' : ''}
50
+ :disable-hover-trigger="disableHoverTrigger"
51
+ :disable-click-trigger="disableClickTrigger"
52
+ >
53
+ <UiNavigationMenuList>
54
+ <UiNavigationMenuItem :value="itemValue">
55
+ <UiNavigationMenuTrigger>
56
+ {{ triggerText }}
57
+ </UiNavigationMenuTrigger>
58
+ <UiNavigationMenuContent>
59
+ <UiNavigationMenuLink v-for="link in links" :key="link">
60
+ {{ link }}
61
+ </UiNavigationMenuLink>
62
+ </UiNavigationMenuContent>
63
+ </UiNavigationMenuItem>
64
+ </UiNavigationMenuList>
65
+ </UiNavigationMenu>
66
+ `,
67
+ setup() {
68
+ return {
69
+ triggerText,
70
+ links,
71
+ modelValue,
72
+ defaultValue,
73
+ disableHoverTrigger,
74
+ disableClickTrigger,
75
+ itemValue,
76
+ };
77
+ },
78
+ });
79
+ }
80
+
81
+ describe('UiNavigationMenu', () => {
82
+ test('supports controlled modelValue', async () => {
83
+ const { getByText } = render({
84
+ components: {
85
+ UiNavigationMenu,
86
+ UiNavigationMenuList,
87
+ UiNavigationMenuItem,
88
+ UiNavigationMenuTrigger,
89
+ UiNavigationMenuContent,
90
+ UiNavigationMenuLink,
91
+ },
92
+ template: `
93
+ <UiNavigationMenu :model-value="modelValue">
94
+ <UiNavigationMenuList>
95
+ <UiNavigationMenuItem value="products">
96
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
97
+ <UiNavigationMenuContent>
98
+ <UiNavigationMenuLink>Product Link</UiNavigationMenuLink>
99
+ </UiNavigationMenuContent>
100
+ </UiNavigationMenuItem>
101
+ </UiNavigationMenuList>
102
+ </UiNavigationMenu>
103
+ `,
104
+ setup() {
105
+ const modelValue = ref('products');
106
+ return { modelValue };
107
+ },
108
+ });
109
+
110
+ await waitFor(() => {
111
+ expect(getByText('Product Link')).toBeVisible();
112
+ });
113
+ });
114
+
115
+ test('suppresses viewport rendering when viewport is false', () => {
116
+ const { container } = render({
117
+ components: {
118
+ UiNavigationMenu,
119
+ UiNavigationMenuList,
120
+ UiNavigationMenuItem,
121
+ UiNavigationMenuTrigger,
122
+ UiNavigationMenuContent,
123
+ },
124
+ template: `
125
+ <UiNavigationMenu :viewport="false">
126
+ <UiNavigationMenuList>
127
+ <UiNavigationMenuItem>
128
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
129
+ <UiNavigationMenuContent>Content</UiNavigationMenuContent>
130
+ </UiNavigationMenuItem>
131
+ </UiNavigationMenuList>
132
+ </UiNavigationMenu>
133
+ `,
134
+ });
135
+
136
+ expect(
137
+ container.querySelector('[data-slot="navigation-menu-viewport"]'),
138
+ ).not.toBeInTheDocument();
139
+ });
140
+
141
+ test('emits update:modelValue when item changes', async () => {
142
+ const user = userEvent.setup();
143
+ const onUpdateModelValue = vi.fn();
144
+
145
+ const { getByRole } = render({
146
+ components: {
147
+ UiNavigationMenu,
148
+ UiNavigationMenuList,
149
+ UiNavigationMenuItem,
150
+ UiNavigationMenuTrigger,
151
+ UiNavigationMenuContent,
152
+ UiNavigationMenuLink,
153
+ },
154
+ template: `
155
+ <UiNavigationMenu @update:model-value="onUpdateModelValue" :disable-hover-trigger="true">
156
+ <UiNavigationMenuList>
157
+ <UiNavigationMenuItem value="products">
158
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
159
+ <UiNavigationMenuContent>
160
+ <UiNavigationMenuLink>Link</UiNavigationMenuLink>
161
+ </UiNavigationMenuContent>
162
+ </UiNavigationMenuItem>
163
+ </UiNavigationMenuList>
164
+ </UiNavigationMenu>
165
+ `,
166
+ setup() {
167
+ return { onUpdateModelValue };
168
+ },
169
+ });
170
+
171
+ await user.click(getByRole('button', { name: 'Products' }));
172
+
173
+ await waitFor(() => {
174
+ expect(onUpdateModelValue).toHaveBeenCalledWith('products');
175
+ });
176
+ });
177
+ });
178
+
179
+ describe('UiNavigationMenuTrigger', () => {
180
+ test('forwards disableHoverTrigger — click opens when hover is disabled', async () => {
181
+ const user = userEvent.setup();
182
+ const { getByRole, getByText, queryByText } = renderNavigationMenu({
183
+ disableHoverTrigger: true,
184
+ });
185
+
186
+ // Hover should not open the menu
187
+ await user.hover(getByRole('button', { name: 'Products' }));
188
+ expect(queryByText('Link 1')).not.toBeInTheDocument();
189
+
190
+ // Click should open the menu
191
+ await user.click(getByRole('button', { name: 'Products' }));
192
+
193
+ await waitFor(() => {
194
+ expect(getByText('Link 1')).toBeVisible();
195
+ });
196
+ });
197
+
198
+ test('forwards disabled prop', () => {
199
+ const { getByRole } = render({
200
+ components: {
201
+ UiNavigationMenu,
202
+ UiNavigationMenuList,
203
+ UiNavigationMenuItem,
204
+ UiNavigationMenuTrigger,
205
+ UiNavigationMenuContent,
206
+ },
207
+ template: `
208
+ <UiNavigationMenu>
209
+ <UiNavigationMenuList>
210
+ <UiNavigationMenuItem>
211
+ <UiNavigationMenuTrigger :disabled="true">Disabled</UiNavigationMenuTrigger>
212
+ <UiNavigationMenuContent>Content</UiNavigationMenuContent>
213
+ </UiNavigationMenuItem>
214
+ </UiNavigationMenuList>
215
+ </UiNavigationMenu>
216
+ `,
217
+ });
218
+
219
+ expect(getByRole('button', { name: 'Disabled' })).toBeDisabled();
220
+ });
221
+ });
222
+
223
+ describe('UiNavigationMenuContent', () => {
224
+ test('content is present in DOM without interaction when forceMount is true', () => {
225
+ const { getByText } = render({
226
+ components: {
227
+ UiNavigationMenu,
228
+ UiNavigationMenuList,
229
+ UiNavigationMenuItem,
230
+ UiNavigationMenuTrigger,
231
+ UiNavigationMenuContent,
232
+ UiNavigationMenuLink,
233
+ },
234
+ template: `
235
+ <UiNavigationMenu>
236
+ <UiNavigationMenuList>
237
+ <UiNavigationMenuItem value="products">
238
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
239
+ <UiNavigationMenuContent :force-mount="true">
240
+ <UiNavigationMenuLink>Force Mounted Link</UiNavigationMenuLink>
241
+ </UiNavigationMenuContent>
242
+ </UiNavigationMenuItem>
243
+ </UiNavigationMenuList>
244
+ </UiNavigationMenu>
245
+ `,
246
+ });
247
+
248
+ expect(getByText('Force Mounted Link')).toBeInTheDocument();
249
+ });
250
+
251
+ test('forwards escapeKeyDown event', async () => {
252
+ const user = userEvent.setup();
253
+ const onEscapeKeyDown = vi.fn();
254
+
255
+ const { getByRole, getByText } = render({
256
+ components: {
257
+ UiNavigationMenu,
258
+ UiNavigationMenuList,
259
+ UiNavigationMenuItem,
260
+ UiNavigationMenuTrigger,
261
+ UiNavigationMenuContent,
262
+ UiNavigationMenuLink,
263
+ },
264
+ template: `
265
+ <UiNavigationMenu>
266
+ <UiNavigationMenuList>
267
+ <UiNavigationMenuItem value="products">
268
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
269
+ <UiNavigationMenuContent @escape-key-down="onEscapeKeyDown">
270
+ <UiNavigationMenuLink>Link</UiNavigationMenuLink>
271
+ </UiNavigationMenuContent>
272
+ </UiNavigationMenuItem>
273
+ </UiNavigationMenuList>
274
+ </UiNavigationMenu>
275
+ `,
276
+ setup() {
277
+ return { onEscapeKeyDown };
278
+ },
279
+ });
280
+
281
+ await user.hover(getByRole('button', { name: 'Products' }));
282
+ await waitFor(() => expect(getByText('Link')).toBeVisible());
283
+
284
+ await user.keyboard('{Escape}');
285
+
286
+ expect(onEscapeKeyDown).toHaveBeenCalled();
287
+ });
288
+ });
289
+
290
+ describe('UiNavigationMenuLink', () => {
291
+ test('forwards active prop as data-active attribute', async () => {
292
+ const user = userEvent.setup();
293
+ const { getByRole, getByText } = render({
294
+ components: {
295
+ UiNavigationMenu,
296
+ UiNavigationMenuList,
297
+ UiNavigationMenuItem,
298
+ UiNavigationMenuTrigger,
299
+ UiNavigationMenuContent,
300
+ UiNavigationMenuLink,
301
+ },
302
+ template: `
303
+ <UiNavigationMenu>
304
+ <UiNavigationMenuList>
305
+ <UiNavigationMenuItem value="products">
306
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
307
+ <UiNavigationMenuContent>
308
+ <UiNavigationMenuLink :active="true">Active Link</UiNavigationMenuLink>
309
+ <UiNavigationMenuLink :active="false">Inactive Link</UiNavigationMenuLink>
310
+ </UiNavigationMenuContent>
311
+ </UiNavigationMenuItem>
312
+ </UiNavigationMenuList>
313
+ </UiNavigationMenu>
314
+ `,
315
+ });
316
+
317
+ await user.hover(getByRole('button', { name: 'Products' }));
318
+
319
+ await waitFor(() => {
320
+ expect(getByText('Active Link')).toHaveAttribute('data-active');
321
+ expect(getByText('Inactive Link')).not.toHaveAttribute('data-active');
322
+ });
323
+ });
324
+
325
+ test('forwards select event', async () => {
326
+ const user = userEvent.setup();
327
+ const onSelect = vi.fn();
328
+
329
+ const { getByRole, getByText } = render({
330
+ components: {
331
+ UiNavigationMenu,
332
+ UiNavigationMenuList,
333
+ UiNavigationMenuItem,
334
+ UiNavigationMenuTrigger,
335
+ UiNavigationMenuContent,
336
+ UiNavigationMenuLink,
337
+ },
338
+ template: `
339
+ <UiNavigationMenu>
340
+ <UiNavigationMenuList>
341
+ <UiNavigationMenuItem value="products">
342
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
343
+ <UiNavigationMenuContent>
344
+ <UiNavigationMenuLink @select="onSelect">Click Me</UiNavigationMenuLink>
345
+ </UiNavigationMenuContent>
346
+ </UiNavigationMenuItem>
347
+ </UiNavigationMenuList>
348
+ </UiNavigationMenu>
349
+ `,
350
+ setup() {
351
+ return { onSelect };
352
+ },
353
+ });
354
+
355
+ await user.hover(getByRole('button', { name: 'Products' }));
356
+ await waitFor(() => expect(getByText('Click Me')).toBeVisible());
357
+
358
+ await user.click(getByText('Click Me'));
359
+
360
+ expect(onSelect).toHaveBeenCalled();
361
+ });
362
+
363
+ test('sets aria-current="page" when active', async () => {
364
+ const user = userEvent.setup();
365
+ const { getByRole, getByText } = render({
366
+ components: {
367
+ UiNavigationMenu,
368
+ UiNavigationMenuList,
369
+ UiNavigationMenuItem,
370
+ UiNavigationMenuTrigger,
371
+ UiNavigationMenuContent,
372
+ UiNavigationMenuLink,
373
+ },
374
+ template: `
375
+ <UiNavigationMenu>
376
+ <UiNavigationMenuList>
377
+ <UiNavigationMenuItem value="products">
378
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
379
+ <UiNavigationMenuContent>
380
+ <UiNavigationMenuLink :active="true">Active Link</UiNavigationMenuLink>
381
+ <UiNavigationMenuLink :active="false">Inactive Link</UiNavigationMenuLink>
382
+ </UiNavigationMenuContent>
383
+ </UiNavigationMenuItem>
384
+ </UiNavigationMenuList>
385
+ </UiNavigationMenu>
386
+ `,
387
+ });
388
+
389
+ await user.hover(getByRole('button', { name: 'Products' }));
390
+
391
+ await waitFor(() => {
392
+ expect(getByText('Active Link')).toHaveAttribute('aria-current', 'page');
393
+ expect(getByText('Inactive Link')).not.toHaveAttribute('aria-current');
394
+ });
395
+ });
396
+ });
397
+
398
+ describe('UiNavigationMenuIndicator', () => {
399
+ test('indicator is present in DOM without interaction when forceMount is true', async () => {
400
+ const { container } = render({
401
+ components: {
402
+ UiNavigationMenu,
403
+ UiNavigationMenuList,
404
+ UiNavigationMenuItem,
405
+ UiNavigationMenuTrigger,
406
+ UiNavigationMenuContent,
407
+ UiNavigationMenuIndicator,
408
+ },
409
+ template: `
410
+ <UiNavigationMenu>
411
+ <UiNavigationMenuList>
412
+ <UiNavigationMenuItem>
413
+ <UiNavigationMenuTrigger>Products</UiNavigationMenuTrigger>
414
+ <UiNavigationMenuContent>Content</UiNavigationMenuContent>
415
+ </UiNavigationMenuItem>
416
+ <UiNavigationMenuIndicator :force-mount="true" />
417
+ </UiNavigationMenuList>
418
+ </UiNavigationMenu>
419
+ `,
420
+ });
421
+
422
+ await waitFor(() => {
423
+ expect(
424
+ container.querySelector('[data-slot="navigation-menu-indicator"]'),
425
+ ).toBeInTheDocument();
426
+ });
427
+ });
428
+ });
@@ -0,0 +1,11 @@
1
+ export { default as UiNavigationMenu } from './UiNavigationMenu.vue';
2
+ export { default as UiNavigationMenuList } from './UiNavigationMenuList.vue';
3
+ export { default as UiNavigationMenuItem } from './UiNavigationMenuItem.vue';
4
+ export { default as UiNavigationMenuTrigger } from './UiNavigationMenuTrigger.vue';
5
+ export { default as UiNavigationMenuContent } from './UiNavigationMenuContent.vue';
6
+ export { default as UiNavigationMenuLink } from './UiNavigationMenuLink.vue';
7
+ export { default as UiNavigationMenuIndicator } from './UiNavigationMenuIndicator.vue';
8
+
9
+ export { navigationMenuTriggerStyle } from '@/components/core/navigation-menu';
10
+
11
+ export * from './types';