@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,20 @@
1
+ <script setup lang="ts">
2
+ import { Sidebar as ShadcnSidebar } from '@/components/core/sidebar';
3
+ import type { UiSidebarProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebar',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarProps>(), {
10
+ side: 'left',
11
+ variant: 'sidebar',
12
+ collapsible: 'offcanvas',
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <ShadcnSidebar :side="props.side" :variant="props.variant" :collapsible="props.collapsible">
18
+ <slot />
19
+ </ShadcnSidebar>
20
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { SidebarGroupAction as ShadcnSidebarGroupAction } from '@/components/core/sidebar';
3
+ import type { UiSidebarGroupActionProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarGroupAction',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarGroupActionProps>(), {
10
+ asChild: false,
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <ShadcnSidebarGroupAction :as-child="props.asChild">
16
+ <slot />
17
+ </ShadcnSidebarGroupAction>
18
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { SidebarGroupLabel as ShadcnSidebarGroupLabel } from '@/components/core/sidebar';
3
+ import type { UiSidebarGroupLabelProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarGroupLabel',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarGroupLabelProps>(), {
10
+ asChild: false,
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <ShadcnSidebarGroupLabel :as-child="props.asChild">
16
+ <slot />
17
+ </ShadcnSidebarGroupLabel>
18
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import { PanelLeft } from 'lucide-vue-next';
3
+ import { useSidebar } from '@/components/core/sidebar';
4
+ import {
5
+ SidebarHeader,
6
+ SidebarMenu,
7
+ SidebarMenuAction,
8
+ SidebarMenuItem,
9
+ } from '@/components/core/sidebar';
10
+ import UiSidebarMenuButton from './UiSidebarMenuButton.vue';
11
+
12
+ defineOptions({ name: 'UiSidebarHeaderTrigger' });
13
+
14
+ const { toggleSidebar } = useSidebar();
15
+ </script>
16
+
17
+ <template>
18
+ <SidebarHeader>
19
+ <SidebarMenu>
20
+ <SidebarMenuItem>
21
+ <!-- Expanded: full header with logo + title + toggle button -->
22
+ <UiSidebarMenuButton size="lg" class="group-data-[collapsible=icon]:hidden!">
23
+ <slot name="logo" />
24
+ <div class="grid flex-1 text-left text-sm leading-tight">
25
+ <slot />
26
+ </div>
27
+ </UiSidebarMenuButton>
28
+ <SidebarMenuAction
29
+ @click="toggleSidebar"
30
+ aria-label="Toggle sidebar"
31
+ class="top-1/2! -translate-y-1/2 group-data-[collapsible=icon]:hidden!"
32
+ >
33
+ <PanelLeft />
34
+ <span class="sr-only">Toggle sidebar</span>
35
+ </SidebarMenuAction>
36
+
37
+ <!-- Collapsed: logo that morphs to toggle on hover -->
38
+ <UiSidebarMenuButton
39
+ size="lg"
40
+ aria-label="Toggle sidebar"
41
+ class="hidden group-data-[collapsible=icon]:flex! group-data-[collapsible=icon]:justify-center! group/logo-trigger"
42
+ @click="toggleSidebar"
43
+ >
44
+ <span class="group-hover/logo-trigger:hidden">
45
+ <slot name="logo" />
46
+ </span>
47
+ <PanelLeft class="hidden group-hover/logo-trigger:block size-4" />
48
+ <span class="sr-only">Toggle sidebar</span>
49
+ </UiSidebarMenuButton>
50
+ </SidebarMenuItem>
51
+ </SidebarMenu>
52
+ </SidebarHeader>
53
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import { SidebarInput as ShadcnSidebarInput } from '@/components/core/sidebar';
3
+ import type { UiSidebarInputProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarInput',
7
+ });
8
+
9
+ const props = defineProps<UiSidebarInputProps>();
10
+ </script>
11
+
12
+ <template>
13
+ <ShadcnSidebarInput :placeholder="props.placeholder" />
14
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { SidebarMenuAction as ShadcnSidebarMenuAction } from '@/components/core/sidebar';
3
+ import type { UiSidebarMenuActionProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarMenuAction',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarMenuActionProps>(), {
10
+ showOnHover: false,
11
+ asChild: false,
12
+ });
13
+ </script>
14
+
15
+ <template>
16
+ <ShadcnSidebarMenuAction :show-on-hover="props.showOnHover" :as-child="props.asChild">
17
+ <slot />
18
+ </ShadcnSidebarMenuAction>
19
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { SidebarMenuButton as ShadcnSidebarMenuButton } from '@/components/core/sidebar';
3
+ import type { UiSidebarMenuButtonProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarMenuButton',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarMenuButtonProps>(), {
10
+ variant: 'default',
11
+ size: 'default',
12
+ isActive: false,
13
+ asChild: false,
14
+ });
15
+ </script>
16
+
17
+ <template>
18
+ <ShadcnSidebarMenuButton
19
+ :variant="props.variant"
20
+ :size="props.size"
21
+ :is-active="props.isActive"
22
+ :tooltip="props.tooltip"
23
+ :as-child="props.asChild"
24
+ >
25
+ <slot />
26
+ </ShadcnSidebarMenuButton>
27
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { SidebarMenuSkeleton as ShadcnSidebarMenuSkeleton } from '@/components/core/sidebar';
3
+ import type { UiSidebarMenuSkeletonProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarMenuSkeleton',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarMenuSkeletonProps>(), {
10
+ showIcon: false,
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <ShadcnSidebarMenuSkeleton :show-icon="props.showIcon" />
16
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { SidebarMenuSubButton as ShadcnSidebarMenuSubButton } from '@/components/core/sidebar';
3
+ import type { UiSidebarMenuSubButtonProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarMenuSubButton',
7
+ });
8
+
9
+ const props = withDefaults(defineProps<UiSidebarMenuSubButtonProps>(), {
10
+ size: 'md',
11
+ isActive: false,
12
+ asChild: false,
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <ShadcnSidebarMenuSubButton
18
+ :size="props.size"
19
+ :is-active="props.isActive"
20
+ :as-child="props.asChild"
21
+ >
22
+ <slot />
23
+ </ShadcnSidebarMenuSubButton>
24
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { SidebarProvider as ShadcnSidebarProvider } from '@/components/core/sidebar';
3
+ import type { UiSidebarProviderProps } from './types';
4
+
5
+ defineOptions({
6
+ name: 'UiSidebarProvider',
7
+ });
8
+
9
+ const props = defineProps<UiSidebarProviderProps>();
10
+
11
+ const open = defineModel<boolean | undefined>('open');
12
+ </script>
13
+
14
+ <template>
15
+ <ShadcnSidebarProvider v-model:open="open" :default-open="props.defaultOpen">
16
+ <slot />
17
+ </ShadcnSidebarProvider>
18
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { SidebarSeparator as ShadcnSidebarSeparator } from '@/components/core/sidebar';
3
+
4
+ defineOptions({
5
+ name: 'UiSidebarSeparator',
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <!-- !w-auto overrides base Separator's data-[orientation=horizontal]:w-full which has higher
11
+ CSS specificity in UnoCSS, causing width:100% + mx-2 margins to overflow the sidebar -->
12
+ <ShadcnSidebarSeparator class="!w-auto" />
13
+ </template>
@@ -0,0 +1,221 @@
1
+ import { fireEvent, render } from '@testing-library/vue';
2
+ import { describe, expect, test, vi } from 'vitest';
3
+ import {
4
+ UiSidebar,
5
+ UiSidebarContent,
6
+ UiSidebarGroup,
7
+ UiSidebarGroupContent,
8
+ UiSidebarInset,
9
+ UiSidebarMenu,
10
+ UiSidebarMenuButton,
11
+ UiSidebarMenuItem,
12
+ UiSidebarMenuSkeleton,
13
+ UiSidebarProvider,
14
+ UiSidebarTrigger,
15
+ } from '../index';
16
+ import { ref } from 'vue';
17
+
18
+ describe('UiSidebar', () => {
19
+ test('trigger button has accessible label', () => {
20
+ const { getByRole } = render({
21
+ components: {
22
+ UiSidebar,
23
+ UiSidebarContent,
24
+ UiSidebarInset,
25
+ UiSidebarProvider,
26
+ UiSidebarTrigger,
27
+ },
28
+ template: `
29
+ <UiSidebarProvider>
30
+ <UiSidebar>
31
+ <UiSidebarContent>Content</UiSidebarContent>
32
+ </UiSidebar>
33
+ <UiSidebarInset>
34
+ <UiSidebarTrigger />
35
+ </UiSidebarInset>
36
+ </UiSidebarProvider>
37
+ `,
38
+ });
39
+
40
+ const trigger = getByRole('button', { name: /toggle sidebar/i });
41
+ expect(trigger).toBeInTheDocument();
42
+ });
43
+
44
+ test('menu button applies active state via data attribute', () => {
45
+ const { getByRole } = render({
46
+ components: {
47
+ UiSidebar,
48
+ UiSidebarContent,
49
+ UiSidebarGroup,
50
+ UiSidebarGroupContent,
51
+ UiSidebarMenu,
52
+ UiSidebarMenuButton,
53
+ UiSidebarMenuItem,
54
+ UiSidebarProvider,
55
+ },
56
+ template: `
57
+ <UiSidebarProvider>
58
+ <UiSidebar>
59
+ <UiSidebarContent>
60
+ <UiSidebarGroup>
61
+ <UiSidebarGroupContent>
62
+ <UiSidebarMenu>
63
+ <UiSidebarMenuItem>
64
+ <UiSidebarMenuButton is-active>Active Item</UiSidebarMenuButton>
65
+ </UiSidebarMenuItem>
66
+ </UiSidebarMenu>
67
+ </UiSidebarGroupContent>
68
+ </UiSidebarGroup>
69
+ </UiSidebarContent>
70
+ </UiSidebar>
71
+ </UiSidebarProvider>
72
+ `,
73
+ });
74
+
75
+ const button = getByRole('button', { name: /active item/i });
76
+ expect(button).toHaveAttribute('data-active', 'true');
77
+ });
78
+
79
+ test('supports controlled mode via v-model:open', async () => {
80
+ const onUpdateOpen = vi.fn();
81
+ const { getByRole } = render({
82
+ components: {
83
+ UiSidebar,
84
+ UiSidebarContent,
85
+ UiSidebarInset,
86
+ UiSidebarProvider,
87
+ UiSidebarTrigger,
88
+ },
89
+ setup() {
90
+ const open = ref(true);
91
+ return { open, onUpdateOpen };
92
+ },
93
+ template: `
94
+ <UiSidebarProvider v-model:open="open" @update:open="onUpdateOpen">
95
+ <UiSidebar>
96
+ <UiSidebarContent>Content</UiSidebarContent>
97
+ </UiSidebar>
98
+ <UiSidebarInset>
99
+ <UiSidebarTrigger />
100
+ </UiSidebarInset>
101
+ </UiSidebarProvider>
102
+ `,
103
+ });
104
+
105
+ const trigger = getByRole('button', { name: /toggle sidebar/i });
106
+ await fireEvent.click(trigger);
107
+
108
+ expect(onUpdateOpen).toHaveBeenCalled();
109
+ });
110
+
111
+ test('provider respects defaultOpen prop', () => {
112
+ const { container } = render({
113
+ components: {
114
+ UiSidebar,
115
+ UiSidebarContent,
116
+ UiSidebarProvider,
117
+ },
118
+ template: `
119
+ <UiSidebarProvider :default-open="false">
120
+ <UiSidebar>
121
+ <UiSidebarContent>Content</UiSidebarContent>
122
+ </UiSidebar>
123
+ </UiSidebarProvider>
124
+ `,
125
+ });
126
+
127
+ const sidebar = container.querySelector('[data-slot="sidebar"]');
128
+ expect(sidebar).toHaveAttribute('data-state', 'collapsed');
129
+ });
130
+
131
+ test('menu button applies size variant via data attribute', () => {
132
+ const { getByRole } = render({
133
+ components: {
134
+ UiSidebar,
135
+ UiSidebarContent,
136
+ UiSidebarGroup,
137
+ UiSidebarGroupContent,
138
+ UiSidebarMenu,
139
+ UiSidebarMenuButton,
140
+ UiSidebarMenuItem,
141
+ UiSidebarProvider,
142
+ },
143
+ template: `
144
+ <UiSidebarProvider>
145
+ <UiSidebar>
146
+ <UiSidebarContent>
147
+ <UiSidebarGroup>
148
+ <UiSidebarGroupContent>
149
+ <UiSidebarMenu>
150
+ <UiSidebarMenuItem>
151
+ <UiSidebarMenuButton size="sm">Small Item</UiSidebarMenuButton>
152
+ </UiSidebarMenuItem>
153
+ </UiSidebarMenu>
154
+ </UiSidebarGroupContent>
155
+ </UiSidebarGroup>
156
+ </UiSidebarContent>
157
+ </UiSidebar>
158
+ </UiSidebarProvider>
159
+ `,
160
+ });
161
+
162
+ const button = getByRole('button', { name: /small item/i });
163
+ expect(button).toHaveAttribute('data-size', 'sm');
164
+ });
165
+
166
+ test('menu skeleton shows icon placeholder when showIcon is true', () => {
167
+ const { container } = render({
168
+ components: {
169
+ UiSidebar,
170
+ UiSidebarContent,
171
+ UiSidebarMenu,
172
+ UiSidebarMenuItem,
173
+ UiSidebarMenuSkeleton,
174
+ UiSidebarProvider,
175
+ },
176
+ template: `
177
+ <UiSidebarProvider>
178
+ <UiSidebar>
179
+ <UiSidebarContent>
180
+ <UiSidebarMenu>
181
+ <UiSidebarMenuItem>
182
+ <UiSidebarMenuSkeleton show-icon />
183
+ </UiSidebarMenuItem>
184
+ </UiSidebarMenu>
185
+ </UiSidebarContent>
186
+ </UiSidebar>
187
+ </UiSidebarProvider>
188
+ `,
189
+ });
190
+
191
+ expect(container.querySelector('[data-sidebar="menu-skeleton-icon"]')).toBeInTheDocument();
192
+ });
193
+
194
+ test('menu skeleton hides icon placeholder by default', () => {
195
+ const { container } = render({
196
+ components: {
197
+ UiSidebar,
198
+ UiSidebarContent,
199
+ UiSidebarMenu,
200
+ UiSidebarMenuItem,
201
+ UiSidebarMenuSkeleton,
202
+ UiSidebarProvider,
203
+ },
204
+ template: `
205
+ <UiSidebarProvider>
206
+ <UiSidebar>
207
+ <UiSidebarContent>
208
+ <UiSidebarMenu>
209
+ <UiSidebarMenuItem>
210
+ <UiSidebarMenuSkeleton />
211
+ </UiSidebarMenuItem>
212
+ </UiSidebarMenu>
213
+ </UiSidebarContent>
214
+ </UiSidebar>
215
+ </UiSidebarProvider>
216
+ `,
217
+ });
218
+
219
+ expect(container.querySelector('[data-sidebar="menu-skeleton-icon"]')).not.toBeInTheDocument();
220
+ });
221
+ });
@@ -0,0 +1,34 @@
1
+ // Pure reexports — no wrapper logic needed
2
+ export {
3
+ SidebarContent as UiSidebarContent,
4
+ SidebarFooter as UiSidebarFooter,
5
+ SidebarGroup as UiSidebarGroup,
6
+ SidebarGroupContent as UiSidebarGroupContent,
7
+ SidebarHeader as UiSidebarHeader,
8
+ SidebarInset as UiSidebarInset,
9
+ SidebarMenu as UiSidebarMenu,
10
+ SidebarMenuBadge as UiSidebarMenuBadge,
11
+ SidebarMenuItem as UiSidebarMenuItem,
12
+ SidebarMenuSub as UiSidebarMenuSub,
13
+ SidebarMenuSubItem as UiSidebarMenuSubItem,
14
+ SidebarRail as UiSidebarRail,
15
+ SidebarTrigger as UiSidebarTrigger,
16
+ } from '@/components/core/sidebar';
17
+
18
+ // Enhanced wrappers — add defaults, v-model, or custom props
19
+ export { default as UiSidebar } from './UiSidebar.vue';
20
+ export { default as UiSidebarGroupAction } from './UiSidebarGroupAction.vue';
21
+ export { default as UiSidebarGroupLabel } from './UiSidebarGroupLabel.vue';
22
+ export { default as UiSidebarHeaderTrigger } from './UiSidebarHeaderTrigger.vue';
23
+ export { default as UiSidebarInput } from './UiSidebarInput.vue';
24
+ export { default as UiSidebarMenuAction } from './UiSidebarMenuAction.vue';
25
+ export { default as UiSidebarMenuButton } from './UiSidebarMenuButton.vue';
26
+ export { default as UiSidebarMenuSkeleton } from './UiSidebarMenuSkeleton.vue';
27
+ export { default as UiSidebarMenuSubButton } from './UiSidebarMenuSubButton.vue';
28
+ export { default as UiSidebarProvider } from './UiSidebarProvider.vue';
29
+ export { default as UiSidebarSeparator } from './UiSidebarSeparator.vue';
30
+
31
+ // Re-export the composable for programmatic sidebar control
32
+ export { useSidebar } from '@/components/core/sidebar';
33
+
34
+ export type * from './types';
@@ -0,0 +1,168 @@
1
+ import type { Component } from 'vue';
2
+
3
+ export type UiSidebarSide = 'left' | 'right';
4
+
5
+ export type UiSidebarVariant = 'sidebar' | 'floating' | 'inset';
6
+
7
+ export type UiSidebarCollapsible = 'offcanvas' | 'icon' | 'none';
8
+
9
+ export type UiSidebarMenuButtonVariant = 'default' | 'outline';
10
+
11
+ export type UiSidebarMenuButtonSize = 'default' | 'sm' | 'lg';
12
+
13
+ export type UiSidebarMenuSubButtonSize = 'sm' | 'md';
14
+
15
+ export interface UiSidebarProviderProps {
16
+ /**
17
+ * The default open state when uncontrolled.
18
+ * If not provided, the core provider uses cookie-based persistence
19
+ * (falls back to expanded if no cookie exists).
20
+ */
21
+ defaultOpen?: boolean;
22
+
23
+ /**
24
+ * The controlled open state of the sidebar.
25
+ * - Omit for uncontrolled mode (component manages state internally)
26
+ * - Use `v-model:open` for controlled mode
27
+ */
28
+ open?: boolean;
29
+ }
30
+
31
+ /**
32
+ * A collapsible sidebar navigation component for application layouts.
33
+ * Use for primary navigation, page hierarchy, and secondary actions in dashboard-style applications.
34
+ * @category Layout
35
+ * @useCases navigation, dashboard layout, app shell, admin panel, settings menu
36
+ * @keywords sidebar, navigation, menu, drawer, collapsible, layout
37
+ * @related UiDrawer, UiSheet
38
+ */
39
+ export interface UiSidebarProps {
40
+ /**
41
+ * Which side the sidebar appears on.
42
+ * @default 'left'
43
+ */
44
+ side?: UiSidebarSide;
45
+
46
+ /**
47
+ * The visual variant of the sidebar.
48
+ * - `sidebar`: Standard fixed sidebar with border
49
+ * - `floating`: Floating sidebar with shadow and rounded corners
50
+ * - `inset`: Sidebar that shares background with main content
51
+ * @default 'sidebar'
52
+ */
53
+ variant?: UiSidebarVariant;
54
+
55
+ /**
56
+ * How the sidebar collapses.
57
+ * - `offcanvas`: Slides completely off screen
58
+ * - `icon`: Collapses to icon-only width
59
+ * - `none`: Never collapses (always expanded)
60
+ * @default 'offcanvas'
61
+ */
62
+ collapsible?: UiSidebarCollapsible;
63
+ }
64
+
65
+ export interface UiSidebarGroupLabelProps {
66
+ /**
67
+ * Render as child element (merges props into slotted element instead of wrapping).
68
+ * @default false
69
+ */
70
+ asChild?: boolean;
71
+ }
72
+
73
+ export interface UiSidebarGroupActionProps {
74
+ /**
75
+ * Render as child element (merges props into slotted element instead of wrapping).
76
+ * @default false
77
+ */
78
+ asChild?: boolean;
79
+ }
80
+
81
+ export interface UiSidebarMenuButtonProps {
82
+ /**
83
+ * The visual variant of the button.
84
+ * @default 'default'
85
+ */
86
+ variant?: UiSidebarMenuButtonVariant;
87
+
88
+ /**
89
+ * The size of the button.
90
+ * @default 'default'
91
+ */
92
+ size?: UiSidebarMenuButtonSize;
93
+
94
+ /**
95
+ * Whether the button represents the currently active item.
96
+ * @default false
97
+ */
98
+ isActive?: boolean;
99
+
100
+ /**
101
+ * Tooltip text or component to show when sidebar is collapsed.
102
+ * Only visible in icon-collapsed mode.
103
+ */
104
+ tooltip?: string | Component;
105
+
106
+ /**
107
+ * Render as child element (merges props into slotted element instead of wrapping).
108
+ * @default false
109
+ */
110
+ asChild?: boolean;
111
+ }
112
+
113
+ export interface UiSidebarMenuActionProps {
114
+ /**
115
+ * Whether to show the action only on hover of the parent menu item.
116
+ * @default false
117
+ */
118
+ showOnHover?: boolean;
119
+
120
+ /**
121
+ * Render as child element (merges props into slotted element instead of wrapping).
122
+ * @default false
123
+ */
124
+ asChild?: boolean;
125
+ }
126
+
127
+ export interface UiSidebarMenuSubButtonProps {
128
+ /**
129
+ * The size of the sub-button.
130
+ * @default 'md'
131
+ */
132
+ size?: UiSidebarMenuSubButtonSize;
133
+
134
+ /**
135
+ * Whether the button represents the currently active item.
136
+ * @default false
137
+ */
138
+ isActive?: boolean;
139
+
140
+ /**
141
+ * Render as child element (merges props into slotted element instead of wrapping).
142
+ * @default false
143
+ */
144
+ asChild?: boolean;
145
+ }
146
+
147
+ export interface UiSidebarMenuSkeletonProps {
148
+ /**
149
+ * Whether to show an icon placeholder in the skeleton.
150
+ * @default false
151
+ */
152
+ showIcon?: boolean;
153
+ }
154
+
155
+ export interface UiSidebarInputProps {
156
+ /**
157
+ * Placeholder text for the input.
158
+ */
159
+ placeholder?: string;
160
+ }
161
+
162
+ /**
163
+ * Sidebar header with integrated toggle trigger (OpenAI-style).
164
+ * Shows logo + title when expanded; logo morphs to toggle icon on hover when collapsed.
165
+ */
166
+ export interface UiSidebarHeaderTriggerProps {
167
+ // No props needed — behavior is slot-driven
168
+ }