@titan-design/react-ui 0.1.0 → 0.2.5
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 +47 -1
- package/dist/{chunk-MGW37MPO.mjs → chunk-UXVRPOME.mjs} +173 -3
- package/dist/chunk-UXVRPOME.mjs.map +1 -0
- package/dist/index.d.mts +101 -12
- package/dist/index.d.ts +101 -12
- package/dist/index.js +1267 -468
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +734 -112
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +86 -1
- package/dist/theme/index.d.ts +86 -1
- package/dist/theme/index.js +166 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -1
- package/package.json +22 -6
- package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
- package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
- package/src/components/custom/DateTime/DateTime.tsx +235 -0
- package/src/components/custom/DateTime/index.ts +2 -0
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
- package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
- package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
- package/src/components/custom/EmptyState/index.ts +2 -0
- package/src/components/custom/Metric/Metric.stories.tsx +133 -0
- package/src/components/custom/Metric/Metric.test.tsx +120 -0
- package/src/components/custom/Metric/Metric.tsx +92 -0
- package/src/components/custom/Metric/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
- package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
- package/src/components/custom/Sidebar/index.ts +18 -0
- package/src/components/custom/Table/Table.stories.tsx +481 -0
- package/src/components/custom/Table/Table.test.tsx +531 -0
- package/src/components/custom/Table/Table.tsx +696 -0
- package/src/components/custom/Table/index.ts +22 -0
- package/src/components/custom/Typography/Typography.stories.tsx +153 -0
- package/src/components/custom/Typography/Typography.test.tsx +107 -0
- package/src/components/custom/Typography/Typography.tsx +218 -0
- package/src/components/custom/Typography/index.ts +19 -0
- package/src/components/custom/Workout/RestTimer.stories.tsx +71 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +145 -0
- package/src/components/custom/Workout/RestTimer.tsx +172 -0
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +144 -0
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +130 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +63 -0
- package/src/components/custom/Workout/index.ts +2 -0
- package/src/components/custom/index.ts +9 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
- package/src/components/custom/stepper/Stepper.test.tsx +230 -0
- package/src/components/custom/stepper/Stepper.tsx +250 -0
- package/src/components/custom/stepper/index.ts +10 -0
- package/src/components/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +154 -0
- package/src/components/ui/alert/Alert.test.tsx +202 -0
- package/src/components/ui/alert/Alert.tsx +170 -0
- package/src/components/ui/alert/index.ts +8 -0
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
- package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
- package/src/components/ui/autocomplete/index.ts +2 -0
- package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
- package/src/components/ui/avatar/Avatar.test.tsx +152 -0
- package/src/components/ui/avatar/Avatar.tsx +176 -0
- package/src/components/ui/avatar/index.ts +2 -0
- package/src/components/ui/badge/Badge.stories.tsx +100 -0
- package/src/components/ui/badge/Badge.test.tsx +103 -0
- package/src/components/ui/badge/Badge.tsx +119 -0
- package/src/components/ui/badge/index.ts +3 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
- package/src/components/ui/breadcrumbs/index.ts +2 -0
- package/src/components/ui/button/Button.stories.tsx +216 -0
- package/src/components/ui/button/Button.test.tsx +132 -0
- package/src/components/ui/button/Button.tsx +297 -0
- package/src/components/ui/button/index.ts +10 -0
- package/src/components/ui/card/Card.stories.tsx +443 -0
- package/src/components/ui/card/Card.test.tsx +294 -0
- package/src/components/ui/card/Card.tsx +401 -0
- package/src/components/ui/card/index.ts +20 -0
- package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
- package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
- package/src/components/ui/checkbox/Checkbox.tsx +163 -0
- package/src/components/ui/checkbox/index.ts +2 -0
- package/src/components/ui/chip/Chip.stories.tsx +112 -0
- package/src/components/ui/chip/Chip.test.tsx +119 -0
- package/src/components/ui/chip/Chip.tsx +151 -0
- package/src/components/ui/chip/index.ts +2 -0
- package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
- package/src/components/ui/collapse/Collapse.test.tsx +298 -0
- package/src/components/ui/collapse/Collapse.tsx +275 -0
- package/src/components/ui/collapse/index.ts +18 -0
- package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
- package/src/components/ui/data-row/DataRow.test.tsx +78 -0
- package/src/components/ui/data-row/DataRow.tsx +29 -0
- package/src/components/ui/data-row/index.ts +2 -0
- package/src/components/ui/divider/Divider.stories.tsx +71 -0
- package/src/components/ui/divider/Divider.test.tsx +55 -0
- package/src/components/ui/divider/Divider.tsx +37 -0
- package/src/components/ui/divider/index.ts +2 -0
- package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
- package/src/components/ui/drawer/Drawer.test.tsx +142 -0
- package/src/components/ui/drawer/Drawer.tsx +228 -0
- package/src/components/ui/drawer/index.ts +9 -0
- package/src/components/ui/form-field/FormField.stories.tsx +260 -0
- package/src/components/ui/form-field/FormField.test.tsx +280 -0
- package/src/components/ui/form-field/FormField.tsx +308 -0
- package/src/components/ui/form-field/index.ts +2 -0
- package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
- package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
- package/src/components/ui/help-tip/HelpTip.tsx +229 -0
- package/src/components/ui/help-tip/index.ts +2 -0
- package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
- package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
- package/src/components/ui/icon-box/IconBox.tsx +57 -0
- package/src/components/ui/icon-box/index.ts +1 -0
- package/src/components/ui/index.ts +38 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +74 -0
- package/src/components/ui/indicator/Indicator.test.tsx +55 -0
- package/src/components/ui/indicator/Indicator.tsx +73 -0
- package/src/components/ui/indicator/index.ts +2 -0
- package/src/components/ui/input/Input.stories.tsx +192 -0
- package/src/components/ui/input/Input.test.tsx +86 -0
- package/src/components/ui/input/Input.tsx +273 -0
- package/src/components/ui/input/index.ts +2 -0
- package/src/components/ui/link/Link.stories.tsx +78 -0
- package/src/components/ui/link/Link.test.tsx +94 -0
- package/src/components/ui/link/Link.tsx +86 -0
- package/src/components/ui/link/index.ts +2 -0
- package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
- package/src/components/ui/list-item/ListItem.test.tsx +128 -0
- package/src/components/ui/list-item/ListItem.tsx +89 -0
- package/src/components/ui/list-item/index.ts +14 -0
- package/src/components/ui/menu/Menu.stories.tsx +180 -0
- package/src/components/ui/menu/Menu.test.tsx +353 -0
- package/src/components/ui/menu/Menu.tsx +222 -0
- package/src/components/ui/menu/index.ts +2 -0
- package/src/components/ui/modal/Modal.stories.tsx +350 -0
- package/src/components/ui/modal/Modal.test.tsx +159 -0
- package/src/components/ui/modal/Modal.tsx +254 -0
- package/src/components/ui/modal/index.ts +19 -0
- package/src/components/ui/pill/Pill.stories.tsx +71 -0
- package/src/components/ui/pill/Pill.test.tsx +69 -0
- package/src/components/ui/pill/Pill.tsx +104 -0
- package/src/components/ui/pill/index.ts +2 -0
- package/src/components/ui/popover/Popover.stories.tsx +190 -0
- package/src/components/ui/popover/Popover.test.tsx +419 -0
- package/src/components/ui/popover/Popover.tsx +237 -0
- package/src/components/ui/popover/index.ts +2 -0
- package/src/components/ui/progress/Progress.stories.tsx +190 -0
- package/src/components/ui/progress/Progress.test.tsx +210 -0
- package/src/components/ui/progress/Progress.tsx +339 -0
- package/src/components/ui/progress/index.ts +9 -0
- package/src/components/ui/radio/Radio.stories.tsx +199 -0
- package/src/components/ui/radio/Radio.test.tsx +166 -0
- package/src/components/ui/radio/Radio.tsx +200 -0
- package/src/components/ui/radio/index.ts +2 -0
- package/src/components/ui/section/Section.stories.tsx +111 -0
- package/src/components/ui/section/Section.test.tsx +121 -0
- package/src/components/ui/section/Section.tsx +52 -0
- package/src/components/ui/section/index.ts +6 -0
- package/src/components/ui/select/Select.stories.tsx +286 -0
- package/src/components/ui/select/Select.test.tsx +200 -0
- package/src/components/ui/select/Select.tsx +243 -0
- package/src/components/ui/select/index.ts +2 -0
- package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
- package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
- package/src/components/ui/skeleton/Skeleton.tsx +229 -0
- package/src/components/ui/skeleton/index.ts +16 -0
- package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
- package/src/components/ui/spinner/Spinner.test.tsx +67 -0
- package/src/components/ui/spinner/Spinner.tsx +72 -0
- package/src/components/ui/spinner/index.ts +2 -0
- package/src/components/ui/stack/Stack.stories.tsx +127 -0
- package/src/components/ui/stack/Stack.test.tsx +184 -0
- package/src/components/ui/stack/Stack.tsx +76 -0
- package/src/components/ui/stack/index.ts +2 -0
- package/src/components/ui/surface/Surface.stories.tsx +91 -0
- package/src/components/ui/surface/Surface.test.tsx +63 -0
- package/src/components/ui/surface/Surface.tsx +51 -0
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/switch/Switch.stories.tsx +111 -0
- package/src/components/ui/switch/Switch.test.tsx +112 -0
- package/src/components/ui/switch/Switch.tsx +103 -0
- package/src/components/ui/switch/index.ts +2 -0
- package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
- package/src/components/ui/tabs/Tabs.test.tsx +192 -0
- package/src/components/ui/tabs/Tabs.tsx +253 -0
- package/src/components/ui/tabs/index.ts +16 -0
- package/src/components/ui/toast/Toast.stories.tsx +255 -0
- package/src/components/ui/toast/Toast.test.tsx +207 -0
- package/src/components/ui/toast/Toast.tsx +327 -0
- package/src/components/ui/toast/index.ts +9 -0
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
- package/src/components/ui/toolbar-button/index.ts +7 -0
- package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +262 -0
- package/src/components/ui/tooltip/Tooltip.tsx +234 -0
- package/src/components/ui/tooltip/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -0
- package/src/test/setup.ts +19 -0
- package/src/theme/Colors.stories.tsx +258 -0
- package/src/theme/config.ts +193 -0
- package/src/theme/elevation.stories.tsx +346 -0
- package/src/theme/elevation.test.ts +27 -0
- package/src/theme/elevation.ts +567 -0
- package/src/theme/global.css +129 -1
- package/src/theme/index.ts +40 -0
- package/src/theme/presets/apply.ts +97 -0
- package/src/theme/presets/audiobook.ts +93 -0
- package/src/theme/presets/default.ts +6 -0
- package/src/theme/presets/index.ts +4 -0
- package/src/theme/presets/presets.test.ts +51 -0
- package/src/theme/presets/types.ts +76 -0
- package/src/theme/shadows.stories.tsx +523 -0
- package/src/theme/shadows.ts +439 -0
- package/src/theme/tokens/index.ts +2 -0
- package/src/theme/tokens/primitives.ts +354 -0
- package/src/theme/tokens/semantic.ts +381 -0
- package/src/types/jest-axe.d.ts +16 -0
- package/src/types/nativewind.d.ts +54 -0
- package/src/utils/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -0
- package/src/utils/cn.ts +14 -0
- package/src/utils/colors.ts +140 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/useTheme.ts +72 -0
- package/src/web-jsx/jsx-dev-runtime.ts +28 -0
- package/src/web-jsx/jsx-runtime.ts +41 -0
- package/tailwind.config.js +53 -3
- package/dist/chunk-MGW37MPO.mjs.map +0 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import {
|
|
4
|
+
Sidebar,
|
|
5
|
+
SidebarHeader,
|
|
6
|
+
SidebarContent,
|
|
7
|
+
SidebarFooter,
|
|
8
|
+
SidebarSection,
|
|
9
|
+
SidebarItem,
|
|
10
|
+
SidebarDivider,
|
|
11
|
+
} from './Sidebar'
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof Sidebar> = {
|
|
14
|
+
title: 'Custom/Sidebar',
|
|
15
|
+
component: Sidebar,
|
|
16
|
+
tags: ['autodocs'],
|
|
17
|
+
argTypes: {
|
|
18
|
+
isCollapsed: {
|
|
19
|
+
control: 'boolean',
|
|
20
|
+
description: 'Whether sidebar is collapsed (icon-only mode)',
|
|
21
|
+
},
|
|
22
|
+
activeItem: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'ID of the currently active item',
|
|
25
|
+
},
|
|
26
|
+
width: {
|
|
27
|
+
control: 'number',
|
|
28
|
+
description: 'Width when expanded',
|
|
29
|
+
},
|
|
30
|
+
collapsedWidth: {
|
|
31
|
+
control: 'number',
|
|
32
|
+
description: 'Width when collapsed',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
decorators: [
|
|
36
|
+
(Story) => (
|
|
37
|
+
<View style={{ height: 500 }}>
|
|
38
|
+
<Story />
|
|
39
|
+
</View>
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default meta
|
|
45
|
+
type Story = StoryObj<typeof Sidebar>
|
|
46
|
+
|
|
47
|
+
// Simple icon components for demos
|
|
48
|
+
function HomeIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
49
|
+
return <Text style={{ fontSize: size }} className={className}>⌂</Text>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ChartIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
53
|
+
return <Text style={{ fontSize: size }} className={className}>■</Text>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function UserIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
57
|
+
return <Text style={{ fontSize: size }} className={className}>☺</Text>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function GearIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
61
|
+
return <Text style={{ fontSize: size }} className={className}>⚙</Text>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function MailIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
65
|
+
return <Text style={{ fontSize: size }} className={className}>✉</Text>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function FileIcon({ size = 20, className }: { size?: number; className?: string }) {
|
|
69
|
+
return <Text style={{ fontSize: size }} className={className}>📄</Text>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Default: Story = {
|
|
73
|
+
args: {
|
|
74
|
+
activeItem: 'home',
|
|
75
|
+
},
|
|
76
|
+
render: (args) => (
|
|
77
|
+
<Sidebar {...args}>
|
|
78
|
+
<SidebarHeader>
|
|
79
|
+
<Text className="text-text-primary text-lg font-bold">Titan</Text>
|
|
80
|
+
</SidebarHeader>
|
|
81
|
+
<SidebarContent>
|
|
82
|
+
<SidebarSection>
|
|
83
|
+
<SidebarItem id="home" label="Home" />
|
|
84
|
+
<SidebarItem id="dashboard" label="Dashboard" />
|
|
85
|
+
<SidebarItem id="projects" label="Projects" />
|
|
86
|
+
<SidebarItem id="settings" label="Settings" />
|
|
87
|
+
</SidebarSection>
|
|
88
|
+
</SidebarContent>
|
|
89
|
+
</Sidebar>
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const WithIcons: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
activeItem: 'dashboard',
|
|
96
|
+
},
|
|
97
|
+
render: (args) => (
|
|
98
|
+
<Sidebar {...args}>
|
|
99
|
+
<SidebarHeader>
|
|
100
|
+
<Text className="text-text-primary text-lg font-bold">Titan</Text>
|
|
101
|
+
</SidebarHeader>
|
|
102
|
+
<SidebarContent>
|
|
103
|
+
<SidebarSection>
|
|
104
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
105
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
106
|
+
<SidebarItem id="users" icon={UserIcon} label="Users" />
|
|
107
|
+
<SidebarItem id="messages" icon={MailIcon} label="Messages" />
|
|
108
|
+
</SidebarSection>
|
|
109
|
+
</SidebarContent>
|
|
110
|
+
<SidebarFooter>
|
|
111
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
112
|
+
</SidebarFooter>
|
|
113
|
+
</Sidebar>
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const Collapsed: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
isCollapsed: true,
|
|
120
|
+
activeItem: 'home',
|
|
121
|
+
},
|
|
122
|
+
render: (args) => (
|
|
123
|
+
<Sidebar {...args}>
|
|
124
|
+
<SidebarHeader>
|
|
125
|
+
<Text className="text-text-primary text-lg font-bold text-center">T</Text>
|
|
126
|
+
</SidebarHeader>
|
|
127
|
+
<SidebarContent>
|
|
128
|
+
<SidebarSection>
|
|
129
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
130
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
131
|
+
<SidebarItem id="users" icon={UserIcon} label="Users" />
|
|
132
|
+
<SidebarItem id="messages" icon={MailIcon} label="Messages" />
|
|
133
|
+
</SidebarSection>
|
|
134
|
+
</SidebarContent>
|
|
135
|
+
<SidebarFooter>
|
|
136
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
137
|
+
</SidebarFooter>
|
|
138
|
+
</Sidebar>
|
|
139
|
+
),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const WithSections: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
activeItem: 'dashboard',
|
|
145
|
+
},
|
|
146
|
+
render: (args) => (
|
|
147
|
+
<Sidebar {...args}>
|
|
148
|
+
<SidebarHeader>
|
|
149
|
+
<Text className="text-text-primary text-lg font-bold">Titan</Text>
|
|
150
|
+
</SidebarHeader>
|
|
151
|
+
<SidebarContent>
|
|
152
|
+
<SidebarSection title="Main">
|
|
153
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
154
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
155
|
+
</SidebarSection>
|
|
156
|
+
<SidebarDivider />
|
|
157
|
+
<SidebarSection title="Content">
|
|
158
|
+
<SidebarItem id="files" icon={FileIcon} label="Files" />
|
|
159
|
+
<SidebarItem id="messages" icon={MailIcon} label="Messages" />
|
|
160
|
+
</SidebarSection>
|
|
161
|
+
<SidebarDivider />
|
|
162
|
+
<SidebarSection title="Account">
|
|
163
|
+
<SidebarItem id="profile" icon={UserIcon} label="Profile" />
|
|
164
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
165
|
+
</SidebarSection>
|
|
166
|
+
</SidebarContent>
|
|
167
|
+
</Sidebar>
|
|
168
|
+
),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const WithBadge: Story = {
|
|
172
|
+
args: {
|
|
173
|
+
activeItem: 'home',
|
|
174
|
+
},
|
|
175
|
+
render: (args) => (
|
|
176
|
+
<Sidebar {...args}>
|
|
177
|
+
<SidebarHeader>
|
|
178
|
+
<Text className="text-text-primary text-lg font-bold">Titan</Text>
|
|
179
|
+
</SidebarHeader>
|
|
180
|
+
<SidebarContent>
|
|
181
|
+
<SidebarSection>
|
|
182
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
183
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
184
|
+
<SidebarItem
|
|
185
|
+
id="messages"
|
|
186
|
+
icon={MailIcon}
|
|
187
|
+
label="Messages"
|
|
188
|
+
badge={
|
|
189
|
+
<View className="bg-status-error rounded-full px-1.5 py-0.5">
|
|
190
|
+
<Text className="text-white text-xs font-bold">5</Text>
|
|
191
|
+
</View>
|
|
192
|
+
}
|
|
193
|
+
/>
|
|
194
|
+
<SidebarItem id="users" icon={UserIcon} label="Users" />
|
|
195
|
+
</SidebarSection>
|
|
196
|
+
</SidebarContent>
|
|
197
|
+
<SidebarFooter>
|
|
198
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
199
|
+
</SidebarFooter>
|
|
200
|
+
</Sidebar>
|
|
201
|
+
),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const WithActiveItem: Story = {
|
|
205
|
+
render: () => (
|
|
206
|
+
<View style={{ flexDirection: 'row', gap: 16, height: 400 }}>
|
|
207
|
+
<Sidebar activeItem="home" width={200}>
|
|
208
|
+
<SidebarContent>
|
|
209
|
+
<SidebarSection>
|
|
210
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
211
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
212
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
213
|
+
</SidebarSection>
|
|
214
|
+
</SidebarContent>
|
|
215
|
+
</Sidebar>
|
|
216
|
+
<Sidebar activeItem="dashboard" width={200}>
|
|
217
|
+
<SidebarContent>
|
|
218
|
+
<SidebarSection>
|
|
219
|
+
<SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
220
|
+
<SidebarItem id="dashboard" icon={ChartIcon} label="Dashboard" />
|
|
221
|
+
<SidebarItem id="settings" icon={GearIcon} label="Settings" />
|
|
222
|
+
</SidebarSection>
|
|
223
|
+
</SidebarContent>
|
|
224
|
+
</Sidebar>
|
|
225
|
+
</View>
|
|
226
|
+
),
|
|
227
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import {
|
|
5
|
+
Sidebar,
|
|
6
|
+
SidebarHeader,
|
|
7
|
+
SidebarContent,
|
|
8
|
+
SidebarFooter,
|
|
9
|
+
SidebarSection,
|
|
10
|
+
SidebarItem,
|
|
11
|
+
SidebarDivider,
|
|
12
|
+
} from './Sidebar'
|
|
13
|
+
|
|
14
|
+
function MockIcon({ size, className }: { size?: number; className?: string }) {
|
|
15
|
+
return <span data-testid="mock-icon" data-size={size} className={className}>I</span>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderSidebar(props: Partial<React.ComponentProps<typeof Sidebar>> = {}) {
|
|
19
|
+
return render(
|
|
20
|
+
<Sidebar activeItem="home" onItemSelect={vi.fn()} {...props}>
|
|
21
|
+
<SidebarHeader>
|
|
22
|
+
<span>Logo</span>
|
|
23
|
+
</SidebarHeader>
|
|
24
|
+
<SidebarContent>
|
|
25
|
+
<SidebarSection title="Main">
|
|
26
|
+
<SidebarItem id="home" icon={MockIcon} label="Home" />
|
|
27
|
+
<SidebarItem id="dashboard" icon={MockIcon} label="Dashboard" />
|
|
28
|
+
</SidebarSection>
|
|
29
|
+
<SidebarDivider />
|
|
30
|
+
<SidebarSection title="Settings">
|
|
31
|
+
<SidebarItem id="settings" icon={MockIcon} label="Settings" />
|
|
32
|
+
</SidebarSection>
|
|
33
|
+
</SidebarContent>
|
|
34
|
+
<SidebarFooter>
|
|
35
|
+
<SidebarItem id="logout" label="Logout" />
|
|
36
|
+
</SidebarFooter>
|
|
37
|
+
</Sidebar>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('Sidebar', () => {
|
|
42
|
+
it('renders with all sections', () => {
|
|
43
|
+
renderSidebar()
|
|
44
|
+
expect(screen.getByText('Logo')).toBeInTheDocument()
|
|
45
|
+
expect(screen.getByText('Home')).toBeInTheDocument()
|
|
46
|
+
expect(screen.getByText('Dashboard')).toBeInTheDocument()
|
|
47
|
+
expect(screen.getAllByText('Settings').length).toBeGreaterThanOrEqual(1)
|
|
48
|
+
expect(screen.getByText('Logout')).toBeInTheDocument()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('renders section titles', () => {
|
|
52
|
+
renderSidebar()
|
|
53
|
+
expect(screen.getByText('Main')).toBeInTheDocument()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('hides section titles when collapsed', () => {
|
|
57
|
+
renderSidebar({ isCollapsed: true })
|
|
58
|
+
expect(screen.queryByText('Main')).not.toBeInTheDocument()
|
|
59
|
+
expect(screen.queryByText('Settings')).not.toBeInTheDocument()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('hides item labels when collapsed', () => {
|
|
63
|
+
renderSidebar({ isCollapsed: true })
|
|
64
|
+
expect(screen.queryByText('Home')).not.toBeInTheDocument()
|
|
65
|
+
expect(screen.queryByText('Dashboard')).not.toBeInTheDocument()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('shows item labels when expanded', () => {
|
|
69
|
+
renderSidebar({ isCollapsed: false })
|
|
70
|
+
expect(screen.getByText('Home')).toBeInTheDocument()
|
|
71
|
+
expect(screen.getByText('Dashboard')).toBeInTheDocument()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('SidebarItem', () => {
|
|
75
|
+
it('calls onItemSelect when item is pressed', () => {
|
|
76
|
+
const onItemSelect = vi.fn()
|
|
77
|
+
renderSidebar({ onItemSelect })
|
|
78
|
+
|
|
79
|
+
fireEvent.click(screen.getByLabelText('Dashboard'))
|
|
80
|
+
expect(onItemSelect).toHaveBeenCalledWith('dashboard')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('highlights active item', () => {
|
|
84
|
+
renderSidebar({ activeItem: 'home' })
|
|
85
|
+
const homeItem = screen.getByLabelText('Home')
|
|
86
|
+
expect(homeItem).toBeInTheDocument()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('renders icon', () => {
|
|
90
|
+
renderSidebar()
|
|
91
|
+
const icons = screen.getAllByTestId('mock-icon')
|
|
92
|
+
expect(icons.length).toBeGreaterThan(0)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('renders badge content', () => {
|
|
96
|
+
render(
|
|
97
|
+
<Sidebar>
|
|
98
|
+
<SidebarContent>
|
|
99
|
+
<SidebarItem id="inbox" label="Inbox" badge={<span>5</span>} />
|
|
100
|
+
</SidebarContent>
|
|
101
|
+
</Sidebar>
|
|
102
|
+
)
|
|
103
|
+
expect(screen.getByText('5')).toBeInTheDocument()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('renders expand indicator for items with children', () => {
|
|
107
|
+
render(
|
|
108
|
+
<Sidebar>
|
|
109
|
+
<SidebarContent>
|
|
110
|
+
<SidebarItem id="nav" label="Navigation" hasChildren isExpanded={false} />
|
|
111
|
+
</SidebarContent>
|
|
112
|
+
</Sidebar>
|
|
113
|
+
)
|
|
114
|
+
expect(screen.getByLabelText('Navigation')).toBeInTheDocument()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('calls custom onPress handler alongside onItemSelect', () => {
|
|
118
|
+
const onItemSelect = vi.fn()
|
|
119
|
+
const onPress = vi.fn()
|
|
120
|
+
render(
|
|
121
|
+
<Sidebar onItemSelect={onItemSelect}>
|
|
122
|
+
<SidebarContent>
|
|
123
|
+
<SidebarItem id="custom" label="Custom" onPress={onPress} />
|
|
124
|
+
</SidebarContent>
|
|
125
|
+
</Sidebar>
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
fireEvent.click(screen.getByLabelText('Custom'))
|
|
129
|
+
expect(onItemSelect).toHaveBeenCalledWith('custom')
|
|
130
|
+
expect(onPress).toHaveBeenCalled()
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('SidebarDivider', () => {
|
|
135
|
+
it('renders divider', () => {
|
|
136
|
+
renderSidebar()
|
|
137
|
+
// The divider renders as a View with a separator style, just verify the sidebar renders completely
|
|
138
|
+
expect(screen.getByText('Logout')).toBeInTheDocument()
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('width props', () => {
|
|
143
|
+
it('uses default width', () => {
|
|
144
|
+
renderSidebar()
|
|
145
|
+
expect(screen.getByText('Logo')).toBeInTheDocument()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('uses custom width', () => {
|
|
149
|
+
renderSidebar({ width: 300 })
|
|
150
|
+
expect(screen.getByText('Logo')).toBeInTheDocument()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('uses collapsed width when collapsed', () => {
|
|
154
|
+
renderSidebar({ isCollapsed: true, collapsedWidth: 80 })
|
|
155
|
+
// Items still render but labels are hidden
|
|
156
|
+
expect(screen.queryByText('Home')).not.toBeInTheDocument()
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('accessibility', () => {
|
|
161
|
+
it('has no accessibility violations', async () => {
|
|
162
|
+
const { container } = renderSidebar()
|
|
163
|
+
const results = await axe(container)
|
|
164
|
+
expect(results).toHaveNoViolations()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('items have button role', () => {
|
|
168
|
+
renderSidebar()
|
|
169
|
+
const buttons = screen.getAllByRole('button')
|
|
170
|
+
expect(buttons.length).toBeGreaterThan(0)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('items have accessible labels', () => {
|
|
174
|
+
renderSidebar()
|
|
175
|
+
expect(screen.getByLabelText('Home')).toBeInTheDocument()
|
|
176
|
+
expect(screen.getByLabelText('Dashboard')).toBeInTheDocument()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('communicates selected state', () => {
|
|
180
|
+
renderSidebar({ activeItem: 'home' })
|
|
181
|
+
const homeButton = screen.getByLabelText('Home')
|
|
182
|
+
expect(homeButton).toBeInTheDocument()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
})
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react'
|
|
2
|
+
import { View, Text, Pressable, ScrollView, type ViewProps, type PressableProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
|
|
5
|
+
interface SidebarContextType {
|
|
6
|
+
isCollapsed: boolean
|
|
7
|
+
activeItem?: string
|
|
8
|
+
onItemSelect?: (id: string) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SidebarContext = createContext<SidebarContextType>({
|
|
12
|
+
isCollapsed: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export interface SidebarProps extends ViewProps {
|
|
16
|
+
/** Whether sidebar is collapsed (icon-only mode) */
|
|
17
|
+
isCollapsed?: boolean
|
|
18
|
+
/** Currently active item ID */
|
|
19
|
+
activeItem?: string
|
|
20
|
+
/** Callback when an item is selected */
|
|
21
|
+
onItemSelect?: (id: string) => void
|
|
22
|
+
/** Width when expanded */
|
|
23
|
+
width?: number
|
|
24
|
+
/** Width when collapsed */
|
|
25
|
+
collapsedWidth?: number
|
|
26
|
+
/** Additional className */
|
|
27
|
+
className?: string
|
|
28
|
+
children?: React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Sidebar navigation component.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <Sidebar activeItem={activeNav} onItemSelect={setActiveNav}>
|
|
36
|
+
* <SidebarHeader>
|
|
37
|
+
* <Logo />
|
|
38
|
+
* </SidebarHeader>
|
|
39
|
+
* <SidebarContent>
|
|
40
|
+
* <SidebarSection title="Main">
|
|
41
|
+
* <SidebarItem id="home" icon={HomeIcon} label="Home" />
|
|
42
|
+
* <SidebarItem id="dashboard" icon={DashboardIcon} label="Dashboard" />
|
|
43
|
+
* </SidebarSection>
|
|
44
|
+
* </SidebarContent>
|
|
45
|
+
* <SidebarFooter>
|
|
46
|
+
* <SidebarItem id="settings" icon={SettingsIcon} label="Settings" />
|
|
47
|
+
* </SidebarFooter>
|
|
48
|
+
* </Sidebar>
|
|
49
|
+
*/
|
|
50
|
+
export function Sidebar({
|
|
51
|
+
isCollapsed = false,
|
|
52
|
+
activeItem,
|
|
53
|
+
onItemSelect,
|
|
54
|
+
width = 256,
|
|
55
|
+
collapsedWidth = 64,
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
...props
|
|
59
|
+
}: SidebarProps) {
|
|
60
|
+
const currentWidth = isCollapsed ? collapsedWidth : width
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<SidebarContext.Provider value={{ isCollapsed, activeItem, onItemSelect }}>
|
|
64
|
+
<View
|
|
65
|
+
style={{ width: currentWidth }}
|
|
66
|
+
className={cn(
|
|
67
|
+
'h-full flex-col bg-surface-base border-r border-border-default transition-all duration-200',
|
|
68
|
+
className
|
|
69
|
+
)}
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</View>
|
|
74
|
+
</SidebarContext.Provider>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SidebarHeaderProps {
|
|
79
|
+
children?: React.ReactNode
|
|
80
|
+
className?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Header section of sidebar (typically contains logo).
|
|
85
|
+
*/
|
|
86
|
+
export function SidebarHeader({ children, className }: SidebarHeaderProps) {
|
|
87
|
+
return (
|
|
88
|
+
<View className={cn('px-4 py-4 border-b border-border-subtle', className)}>
|
|
89
|
+
{children}
|
|
90
|
+
</View>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SidebarContentProps {
|
|
95
|
+
children?: React.ReactNode
|
|
96
|
+
className?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Scrollable content area of sidebar.
|
|
101
|
+
*/
|
|
102
|
+
export function SidebarContent({ children, className }: SidebarContentProps) {
|
|
103
|
+
return (
|
|
104
|
+
<ScrollView
|
|
105
|
+
className={cn('flex-1', className)}
|
|
106
|
+
showsVerticalScrollIndicator={false}
|
|
107
|
+
>
|
|
108
|
+
{children}
|
|
109
|
+
</ScrollView>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface SidebarFooterProps {
|
|
114
|
+
children?: React.ReactNode
|
|
115
|
+
className?: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Footer section of sidebar.
|
|
120
|
+
*/
|
|
121
|
+
export function SidebarFooter({ children, className }: SidebarFooterProps) {
|
|
122
|
+
return (
|
|
123
|
+
<View className={cn('px-2 py-4 border-t border-border-subtle', className)}>
|
|
124
|
+
{children}
|
|
125
|
+
</View>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface SidebarSectionProps {
|
|
130
|
+
/** Section title */
|
|
131
|
+
title?: string
|
|
132
|
+
children?: React.ReactNode
|
|
133
|
+
className?: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Section grouping for sidebar items.
|
|
138
|
+
*/
|
|
139
|
+
export function SidebarSection({ title, children, className }: SidebarSectionProps) {
|
|
140
|
+
const { isCollapsed } = useContext(SidebarContext)
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<View className={cn('px-2 py-2', className)}>
|
|
144
|
+
{title && !isCollapsed && (
|
|
145
|
+
<Text className="px-3 py-2 text-xs font-semibold uppercase tracking-wider text-text-tertiary">
|
|
146
|
+
{title}
|
|
147
|
+
</Text>
|
|
148
|
+
)}
|
|
149
|
+
<View className="gap-0.5">
|
|
150
|
+
{children}
|
|
151
|
+
</View>
|
|
152
|
+
</View>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SidebarItemProps extends Omit<PressableProps, 'children'> {
|
|
157
|
+
/** Unique identifier for the item */
|
|
158
|
+
id: string
|
|
159
|
+
/** Icon component */
|
|
160
|
+
icon?: React.ComponentType<{ size?: number; className?: string }>
|
|
161
|
+
/** Label text */
|
|
162
|
+
label: string
|
|
163
|
+
/** Badge content (e.g., notification count) */
|
|
164
|
+
badge?: React.ReactNode
|
|
165
|
+
/** Whether this item has nested items */
|
|
166
|
+
hasChildren?: boolean
|
|
167
|
+
/** Whether nested items are expanded */
|
|
168
|
+
isExpanded?: boolean
|
|
169
|
+
/** Additional className */
|
|
170
|
+
className?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Individual navigation item in sidebar.
|
|
175
|
+
*/
|
|
176
|
+
export function SidebarItem({
|
|
177
|
+
id,
|
|
178
|
+
icon: Icon,
|
|
179
|
+
label,
|
|
180
|
+
badge,
|
|
181
|
+
hasChildren,
|
|
182
|
+
isExpanded,
|
|
183
|
+
className,
|
|
184
|
+
onPress,
|
|
185
|
+
...props
|
|
186
|
+
}: SidebarItemProps) {
|
|
187
|
+
const { isCollapsed, activeItem, onItemSelect } = useContext(SidebarContext)
|
|
188
|
+
const isActive = activeItem === id
|
|
189
|
+
|
|
190
|
+
const handlePress = (e: any) => {
|
|
191
|
+
onItemSelect?.(id)
|
|
192
|
+
onPress?.(e)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Pressable
|
|
197
|
+
accessibilityRole="button"
|
|
198
|
+
accessibilityState={{ selected: isActive }}
|
|
199
|
+
accessibilityLabel={label}
|
|
200
|
+
onPress={handlePress}
|
|
201
|
+
className={cn(
|
|
202
|
+
'flex-row items-center gap-3 px-3 py-2.5 rounded-md transition-colors',
|
|
203
|
+
isActive
|
|
204
|
+
? 'bg-brand-primary-subtle text-brand-primary'
|
|
205
|
+
: 'text-text-secondary web:hover:bg-interactive-hover active:bg-interactive-active',
|
|
206
|
+
isCollapsed && 'justify-center px-2',
|
|
207
|
+
className
|
|
208
|
+
)}
|
|
209
|
+
{...props}
|
|
210
|
+
>
|
|
211
|
+
{Icon && (
|
|
212
|
+
<Icon
|
|
213
|
+
size={20}
|
|
214
|
+
className={cn(
|
|
215
|
+
isActive ? 'text-brand-primary' : 'text-text-secondary'
|
|
216
|
+
)}
|
|
217
|
+
/>
|
|
218
|
+
)}
|
|
219
|
+
{!isCollapsed && (
|
|
220
|
+
<>
|
|
221
|
+
<Text
|
|
222
|
+
numberOfLines={1}
|
|
223
|
+
className={cn(
|
|
224
|
+
'flex-1 text-sm font-medium',
|
|
225
|
+
isActive ? 'text-brand-primary' : 'text-text-primary'
|
|
226
|
+
)}
|
|
227
|
+
>
|
|
228
|
+
{label}
|
|
229
|
+
</Text>
|
|
230
|
+
{badge && (
|
|
231
|
+
<View className="ml-auto">
|
|
232
|
+
{badge}
|
|
233
|
+
</View>
|
|
234
|
+
)}
|
|
235
|
+
{hasChildren && (
|
|
236
|
+
<Text className="text-text-tertiary ml-1">
|
|
237
|
+
{isExpanded ? '▼' : '▶'}
|
|
238
|
+
</Text>
|
|
239
|
+
)}
|
|
240
|
+
</>
|
|
241
|
+
)}
|
|
242
|
+
</Pressable>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface SidebarDividerProps {
|
|
247
|
+
className?: string
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Divider between sidebar sections.
|
|
252
|
+
*/
|
|
253
|
+
export function SidebarDivider({ className }: SidebarDividerProps) {
|
|
254
|
+
return (
|
|
255
|
+
<View className={cn('h-px mx-4 my-2 bg-border-subtle', className)} />
|
|
256
|
+
)
|
|
257
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Sidebar,
|
|
3
|
+
SidebarHeader,
|
|
4
|
+
SidebarContent,
|
|
5
|
+
SidebarFooter,
|
|
6
|
+
SidebarSection,
|
|
7
|
+
SidebarItem,
|
|
8
|
+
SidebarDivider,
|
|
9
|
+
} from './Sidebar'
|
|
10
|
+
export type {
|
|
11
|
+
SidebarProps,
|
|
12
|
+
SidebarHeaderProps,
|
|
13
|
+
SidebarContentProps,
|
|
14
|
+
SidebarFooterProps,
|
|
15
|
+
SidebarSectionProps,
|
|
16
|
+
SidebarItemProps,
|
|
17
|
+
SidebarDividerProps,
|
|
18
|
+
} from './Sidebar'
|