@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.
- package/README.md +1 -1
- package/config.js +34 -1
- package/dist/system/index.d.ts +1728 -234
- package/dist/system/lib.js +19804 -16600
- package/package.json +1 -1
- package/src/components/UiKbd/UiKbd.stories.ts +1 -1
- package/src/components/UiNavigationMenu/UiNavigationMenu.stories.ts +1196 -0
- package/src/components/UiNavigationMenu/UiNavigationMenu.vue +39 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuContent.vue +25 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuIndicator.vue +14 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuItem.vue +16 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuLink.vue +27 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuList.vue +16 -0
- package/src/components/UiNavigationMenu/UiNavigationMenuTrigger.vue +16 -0
- package/src/components/UiNavigationMenu/__tests__/UiNavigationMenu.test.ts +428 -0
- package/src/components/UiNavigationMenu/index.ts +11 -0
- package/src/components/UiNavigationMenu/types.ts +185 -0
- package/src/components/UiSheet/UiSheet.stories.ts +715 -0
- package/src/components/UiSheet/__tests__/UiSheet.test.ts +229 -0
- package/src/components/UiSheet/index.ts +12 -0
- package/src/components/UiSheet/types.ts +83 -0
- package/src/components/UiSidebar/UiSidebar.stories.ts +1010 -0
- package/src/components/UiSidebar/UiSidebar.vue +20 -0
- package/src/components/UiSidebar/UiSidebarGroupAction.vue +18 -0
- package/src/components/UiSidebar/UiSidebarGroupLabel.vue +18 -0
- package/src/components/UiSidebar/UiSidebarHeaderTrigger.vue +53 -0
- package/src/components/UiSidebar/UiSidebarInput.vue +14 -0
- package/src/components/UiSidebar/UiSidebarMenuAction.vue +19 -0
- package/src/components/UiSidebar/UiSidebarMenuButton.vue +27 -0
- package/src/components/UiSidebar/UiSidebarMenuSkeleton.vue +16 -0
- package/src/components/UiSidebar/UiSidebarMenuSubButton.vue +24 -0
- package/src/components/UiSidebar/UiSidebarProvider.vue +18 -0
- package/src/components/UiSidebar/UiSidebarSeparator.vue +13 -0
- package/src/components/UiSidebar/__tests__/UiSidebar.test.ts +221 -0
- package/src/components/UiSidebar/index.ts +34 -0
- package/src/components/UiSidebar/types.ts +168 -0
- package/src/components/UiStepper/UiStepper.stories.ts +425 -0
- package/src/components/UiStepper/UiStepper.vue +27 -0
- package/src/components/UiStepper/UiStepperDescription.vue +20 -0
- package/src/components/UiStepper/UiStepperIndicator.vue +13 -0
- package/src/components/UiStepper/UiStepperItem.vue +25 -0
- package/src/components/UiStepper/UiStepperSeparator.vue +17 -0
- package/src/components/UiStepper/UiStepperTitle.vue +19 -0
- package/src/components/UiStepper/UiStepperTrigger.vue +18 -0
- package/src/components/UiStepper/__tests__/UiStepper.test.ts +167 -0
- package/src/components/UiStepper/index.ts +9 -0
- package/src/components/UiStepper/types.ts +65 -0
- package/src/components/core/alert/index.ts +2 -2
- package/src/components/core/alert-dialog/AlertDialogContent.vue +1 -1
- package/src/components/core/card/Card.vue +1 -1
- package/src/components/core/drawer/DrawerContent.vue +1 -1
- package/src/components/core/dropdown-menu/DropdownMenuContent.vue +1 -1
- package/src/components/core/dropdown-menu/DropdownMenuSubContent.vue +1 -1
- package/src/components/core/input/Input.vue +1 -1
- package/src/components/core/native-select/NativeSelect.vue +1 -1
- package/src/components/core/native-select/NativeSelectOptGroup.vue +1 -1
- package/src/components/core/native-select/NativeSelectOption.vue +1 -1
- package/src/components/core/navigation-menu/NavigationMenu.vue +40 -0
- package/src/components/core/navigation-menu/NavigationMenuContent.vue +28 -0
- package/src/components/core/navigation-menu/NavigationMenuIndicator.vue +26 -0
- package/src/components/core/navigation-menu/NavigationMenuItem.vue +19 -0
- package/src/components/core/navigation-menu/NavigationMenuLink.vue +27 -0
- package/src/components/core/navigation-menu/NavigationMenuList.vue +21 -0
- package/src/components/core/navigation-menu/NavigationMenuTrigger.vue +27 -0
- package/src/components/core/navigation-menu/NavigationMenuViewport.vue +26 -0
- package/src/components/core/navigation-menu/index.ts +14 -0
- package/src/components/core/popover/PopoverContent.vue +1 -1
- package/src/components/core/select/SelectContent.vue +1 -1
- package/src/components/core/select/SelectTrigger.vue +1 -1
- package/src/components/core/sheet/Sheet.vue +15 -0
- package/src/components/core/sheet/SheetClose.vue +12 -0
- package/src/components/core/sheet/SheetContent.vue +56 -0
- package/src/components/core/sheet/SheetDescription.vue +19 -0
- package/src/components/core/sheet/SheetFooter.vue +9 -0
- package/src/components/core/sheet/SheetHeader.vue +9 -0
- package/src/components/core/sheet/SheetOverlay.vue +24 -0
- package/src/components/core/sheet/SheetTitle.vue +19 -0
- package/src/components/core/sheet/SheetTrigger.vue +12 -0
- package/src/components/core/sheet/index.ts +8 -0
- package/src/components/core/sidebar/Sidebar.vue +105 -0
- package/src/components/core/sidebar/SidebarContent.vue +21 -0
- package/src/components/core/sidebar/SidebarFooter.vue +16 -0
- package/src/components/core/sidebar/SidebarGroup.vue +16 -0
- package/src/components/core/sidebar/SidebarGroupAction.vue +25 -0
- package/src/components/core/sidebar/SidebarGroupContent.vue +16 -0
- package/src/components/core/sidebar/SidebarGroupLabel.vue +23 -0
- package/src/components/core/sidebar/SidebarHeader.vue +16 -0
- package/src/components/core/sidebar/SidebarInput.vue +17 -0
- package/src/components/core/sidebar/SidebarInset.vue +21 -0
- package/src/components/core/sidebar/SidebarMenu.vue +16 -0
- package/src/components/core/sidebar/SidebarMenuAction.vue +33 -0
- package/src/components/core/sidebar/SidebarMenuBadge.vue +26 -0
- package/src/components/core/sidebar/SidebarMenuButton.vue +49 -0
- package/src/components/core/sidebar/SidebarMenuButtonChild.vue +36 -0
- package/src/components/core/sidebar/SidebarMenuItem.vue +16 -0
- package/src/components/core/sidebar/SidebarMenuSkeleton.vue +32 -0
- package/src/components/core/sidebar/SidebarMenuSub.vue +22 -0
- package/src/components/core/sidebar/SidebarMenuSubButton.vue +38 -0
- package/src/components/core/sidebar/SidebarMenuSubItem.vue +16 -0
- package/src/components/core/sidebar/SidebarProvider.vue +102 -0
- package/src/components/core/sidebar/SidebarRail.vue +33 -0
- package/src/components/core/sidebar/SidebarSeparator.vue +17 -0
- package/src/components/core/sidebar/SidebarTrigger.vue +25 -0
- package/src/components/core/sidebar/index.ts +58 -0
- package/src/components/core/sidebar/utils.ts +19 -0
- package/src/components/core/stepper/Stepper.vue +20 -0
- package/src/components/core/stepper/StepperDescription.vue +23 -0
- package/src/components/core/stepper/StepperIndicator.vue +34 -0
- package/src/components/core/stepper/StepperItem.vue +23 -0
- package/src/components/core/stepper/StepperSeparator.vue +29 -0
- package/src/components/core/stepper/StepperTitle.vue +24 -0
- package/src/components/core/stepper/StepperTrigger.vue +22 -0
- package/src/components/core/stepper/index.ts +7 -0
- package/src/components/core/tabs/TabsTrigger.vue +1 -1
- package/src/components/core/tags-input/TagsInput.vue +1 -1
- package/src/components/core/textarea/Textarea.vue +1 -1
- package/src/components/index.ts +4 -0
- package/src/theme/Background.stories.ts +84 -35
- package/src/theme/Extended.stories.ts +4 -4
- 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
|
+
}
|