ai-design-system 0.1.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 +307 -0
- package/components/ai-elements/actions.tsx +65 -0
- package/components/ai-elements/artifact.tsx +147 -0
- package/components/ai-elements/branch.tsx +212 -0
- package/components/ai-elements/canvas.tsx +24 -0
- package/components/ai-elements/chain-of-thought.tsx +228 -0
- package/components/ai-elements/code-block.tsx +179 -0
- package/components/ai-elements/confirmation.tsx +169 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +408 -0
- package/components/ai-elements/controls.tsx +18 -0
- package/components/ai-elements/conversation.tsx +97 -0
- package/components/ai-elements/edge.tsx +140 -0
- package/components/ai-elements/image.tsx +24 -0
- package/components/ai-elements/inline-citation.tsx +287 -0
- package/components/ai-elements/loader.tsx +96 -0
- package/components/ai-elements/message.tsx +80 -0
- package/components/ai-elements/node.tsx +71 -0
- package/components/ai-elements/open-in-chat.tsx +363 -0
- package/components/ai-elements/panel.tsx +15 -0
- package/components/ai-elements/plan.tsx +142 -0
- package/components/ai-elements/prompt-input.tsx +1352 -0
- package/components/ai-elements/queue.tsx +274 -0
- package/components/ai-elements/reasoning.tsx +178 -0
- package/components/ai-elements/response.tsx +22 -0
- package/components/ai-elements/shimmer.tsx +64 -0
- package/components/ai-elements/sources.tsx +77 -0
- package/components/ai-elements/suggestion.tsx +56 -0
- package/components/ai-elements/task.tsx +87 -0
- package/components/ai-elements/tool.tsx +179 -0
- package/components/ai-elements/toolbar.tsx +16 -0
- package/components/ai-elements/web-preview.tsx +263 -0
- package/components/blocks/AIConversation/AIConversation.stories.tsx +164 -0
- package/components/blocks/AIConversation/AIConversation.tsx +186 -0
- package/components/blocks/AIConversation/index.ts +8 -0
- package/components/blocks/AppSidebar/AppSidebar.stories.tsx +63 -0
- package/components/blocks/AppSidebar/AppSidebar.tsx +87 -0
- package/components/blocks/AppSidebar/index.ts +2 -0
- package/components/blocks/DocumentEditorWithComments/DocumentEditorWithComments.stories.tsx +341 -0
- package/components/blocks/DocumentEditorWithComments/DocumentEditorWithComments.tsx +255 -0
- package/components/blocks/DocumentEditorWithComments/index.ts +9 -0
- package/components/blocks/FileChangeQueue/FileChangeQueue.stories.tsx +207 -0
- package/components/blocks/FileChangeQueue/FileChangeQueue.tsx +143 -0
- package/components/blocks/FileChangeQueue/index.ts +7 -0
- package/components/blocks/LayoutProvider/LayoutProvider.tsx +34 -0
- package/components/blocks/LayoutProvider/index.ts +1 -0
- package/components/blocks/index.ts +2 -0
- package/components/composites/AgentIndicator/AgentIndicator.stories.tsx +154 -0
- package/components/composites/AgentIndicator/AgentIndicator.tsx +102 -0
- package/components/composites/AgentIndicator/index.ts +8 -0
- package/components/composites/AppHeader/AppHeader.stories.tsx +46 -0
- package/components/composites/AppHeader/AppHeader.tsx +24 -0
- package/components/composites/AppHeader/index.ts +2 -0
- package/components/composites/CommentBox/CommentBox.stories.tsx +192 -0
- package/components/composites/CommentBox/CommentBox.tsx +364 -0
- package/components/composites/CommentBox/index.ts +8 -0
- package/components/composites/Confirmation/Confirmation.stories.tsx +151 -0
- package/components/composites/Confirmation/Confirmation.tsx +93 -0
- package/components/composites/Confirmation/index.ts +7 -0
- package/components/composites/DataTable/DataTable.stories.tsx +35 -0
- package/components/composites/DataTable/DataTable.tsx +95 -0
- package/components/composites/DataTable/index.ts +2 -0
- package/components/composites/DocumentEditor/DocumentEditor.css +106 -0
- package/components/composites/DocumentEditor/DocumentEditor.stories.tsx +927 -0
- package/components/composites/DocumentEditor/DocumentEditor.tsx +279 -0
- package/components/composites/DocumentEditor/index.ts +8 -0
- package/components/composites/FileQueue/FileQueue.stories.tsx +175 -0
- package/components/composites/FileQueue/FileQueue.tsx +161 -0
- package/components/composites/FileQueue/FileStatusBadge.tsx +74 -0
- package/components/composites/FileQueue/index.ts +24 -0
- package/components/composites/InteractiveChart/InteractiveChart.stories.tsx +49 -0
- package/components/composites/InteractiveChart/InteractiveChart.tsx +69 -0
- package/components/composites/InteractiveChart/index.ts +2 -0
- package/components/composites/ModeToggle/ModeToggle.stories.tsx +212 -0
- package/components/composites/ModeToggle/ModeToggle.tsx +100 -0
- package/components/composites/ModeToggle/index.ts +7 -0
- package/components/composites/NavUser/NavUser.stories.tsx +50 -0
- package/components/composites/NavUser/NavUser.tsx +60 -0
- package/components/composites/NavUser/index.ts +2 -0
- package/components/composites/NavigationList/NavigationList.stories.tsx +46 -0
- package/components/composites/NavigationList/NavigationList.tsx +46 -0
- package/components/composites/NavigationList/index.ts +2 -0
- package/components/composites/OrchestratorMessage/OrchestratorMessage.stories.tsx +188 -0
- package/components/composites/OrchestratorMessage/OrchestratorMessage.tsx +72 -0
- package/components/composites/OrchestratorMessage/index.ts +8 -0
- package/components/composites/PageContainer/PageContainer.stories.tsx +41 -0
- package/components/composites/PageContainer/PageContainer.tsx +24 -0
- package/components/composites/PageContainer/index.ts +2 -0
- package/components/composites/PromptInput/PromptInput.stories.tsx +200 -0
- package/components/composites/PromptInput/PromptInput.tsx +129 -0
- package/components/composites/PromptInput/index.ts +8 -0
- package/components/composites/SpecialistMessage/SpecialistMessage.stories.tsx +286 -0
- package/components/composites/SpecialistMessage/SpecialistMessage.tsx +107 -0
- package/components/composites/SpecialistMessage/index.ts +8 -0
- package/components/composites/StatsCard/StatsCard.stories.tsx +76 -0
- package/components/composites/StatsCard/StatsCard.tsx +81 -0
- package/components/composites/StatsCard/index.ts +2 -0
- package/components/composites/TablePagination/TablePagination.stories.tsx +38 -0
- package/components/composites/TablePagination/TablePagination.tsx +119 -0
- package/components/composites/TablePagination/index.ts +2 -0
- package/components/composites/TableToolbar/TableToolbar.stories.tsx +60 -0
- package/components/composites/TableToolbar/TableToolbar.tsx +66 -0
- package/components/composites/TableToolbar/index.ts +2 -0
- package/components/composites/ThemeSelector/ThemeSelector.stories.tsx +48 -0
- package/components/composites/ThemeSelector/ThemeSelector.tsx +79 -0
- package/components/composites/ThemeSelector/index.ts +2 -0
- package/components/composites/ToolCallDisplay/ToolCallDisplay.stories.tsx +49 -0
- package/components/composites/ToolCallDisplay/ToolCallDisplay.tsx +108 -0
- package/components/composites/ToolCallDisplay/index.ts +8 -0
- package/components/composites/UserMessage/UserMessage.stories.tsx +59 -0
- package/components/composites/UserMessage/UserMessage.tsx +52 -0
- package/components/composites/UserMessage/index.ts +8 -0
- package/components/composites/index.ts +90 -0
- package/components/features/AIDocEditor/AIDocEditor.behaviors.stories.tsx +451 -0
- package/components/features/AIDocEditor/AIDocEditor.mocks.ts +96 -0
- package/components/features/AIDocEditor/AIDocEditor.stories.tsx +140 -0
- package/components/features/AIDocEditor/AIDocEditor.tsx +130 -0
- package/components/features/AIDocEditor/index.ts +8 -0
- package/components/features/AIDocEditor/useAIDocEditor.d.ts +97 -0
- package/components/features/AIDocEditor/useAIDocEditor.mock.ts +83 -0
- package/components/features/PageLayout/PageLayout.behaviors.stories.tsx +119 -0
- package/components/features/PageLayout/PageLayout.mocks.ts +27 -0
- package/components/features/PageLayout/PageLayout.stories.tsx +142 -0
- package/components/features/PageLayout/PageLayout.tsx +94 -0
- package/components/features/PageLayout/index.ts +4 -0
- package/components/features/PageLayout/usePageLayout.d.ts +24 -0
- package/components/features/PageLayout/usePageLayout.mock.ts +19 -0
- package/components/features/RefinementPanel/README.md +189 -0
- package/components/features/RefinementPanel/RefinementPanel.behaviors.stories.tsx +475 -0
- package/components/features/RefinementPanel/RefinementPanel.mocks.ts +131 -0
- package/components/features/RefinementPanel/RefinementPanel.stories.tsx +141 -0
- package/components/features/RefinementPanel/RefinementPanel.tsx +160 -0
- package/components/features/RefinementPanel/index.ts +25 -0
- package/components/features/RefinementPanel/useRefinementPanel.d.ts +74 -0
- package/components/features/RefinementPanel/useRefinementPanel.mock.ts +121 -0
- package/components/features/SpecNavigator/SpecNavigator.behaviors.stories.tsx +379 -0
- package/components/features/SpecNavigator/SpecNavigator.mocks.ts +131 -0
- package/components/features/SpecNavigator/SpecNavigator.stories.tsx +122 -0
- package/components/features/SpecNavigator/SpecNavigator.tsx +43 -0
- package/components/features/SpecNavigator/index.ts +2 -0
- package/components/features/SpecNavigator/useSpecNavigator.d.ts +122 -0
- package/components/features/SpecNavigator/useSpecNavigator.mock.ts +93 -0
- package/components/features/index.ts +18 -0
- package/components/index.ts +14 -0
- package/components/primitives/Accordion/Accordion.stories.tsx +87 -0
- package/components/primitives/Accordion/Accordion.tsx +66 -0
- package/components/primitives/Accordion/index.ts +13 -0
- package/components/primitives/Alert/Alert.stories.tsx +422 -0
- package/components/primitives/Alert/Alert.tsx +61 -0
- package/components/primitives/Alert/index.ts +8 -0
- package/components/primitives/AlertDialog/AlertDialog.stories.tsx +367 -0
- package/components/primitives/AlertDialog/AlertDialog.tsx +182 -0
- package/components/primitives/AlertDialog/index.ts +25 -0
- package/components/primitives/Avatar/Avatar.stories.tsx +321 -0
- package/components/primitives/Avatar/Avatar.tsx +63 -0
- package/components/primitives/Avatar/index.ts +8 -0
- package/components/primitives/Badge/Badge.stories.tsx +74 -0
- package/components/primitives/Badge/Badge.tsx +49 -0
- package/components/primitives/Badge/index.ts +2 -0
- package/components/primitives/Button/Button.stories.tsx +445 -0
- package/components/primitives/Button/Button.tsx +89 -0
- package/components/primitives/Button/index.ts +7 -0
- package/components/primitives/Card/Card.stories.tsx +831 -0
- package/components/primitives/Card/Card.tsx +242 -0
- package/components/primitives/Card/index.ts +30 -0
- package/components/primitives/Carousel/Carousel.stories.tsx +32 -0
- package/components/primitives/Carousel/Carousel.tsx +63 -0
- package/components/primitives/Carousel/index.ts +13 -0
- package/components/primitives/Chart/Chart.stories.tsx +346 -0
- package/components/primitives/Chart/Chart.tsx +117 -0
- package/components/primitives/Chart/index.ts +20 -0
- package/components/primitives/Checkbox/Checkbox.stories.tsx +87 -0
- package/components/primitives/Checkbox/Checkbox.tsx +38 -0
- package/components/primitives/Checkbox/index.ts +2 -0
- package/components/primitives/Collapsible/Collapsible.stories.tsx +38 -0
- package/components/primitives/Collapsible/Collapsible.tsx +39 -0
- package/components/primitives/Collapsible/index.ts +8 -0
- package/components/primitives/Command/Command.stories.tsx +150 -0
- package/components/primitives/Command/Command.tsx +147 -0
- package/components/primitives/Command/index.ts +20 -0
- package/components/primitives/Dialog/Dialog.stories.tsx +390 -0
- package/components/primitives/Dialog/Dialog.tsx +140 -0
- package/components/primitives/Dialog/index.ts +22 -0
- package/components/primitives/Drawer/Drawer.stories.tsx +327 -0
- package/components/primitives/Drawer/Drawer.tsx +208 -0
- package/components/primitives/Drawer/index.ts +27 -0
- package/components/primitives/DropdownMenu/DropdownMenu.stories.tsx +150 -0
- package/components/primitives/DropdownMenu/DropdownMenu.tsx +73 -0
- package/components/primitives/DropdownMenu/index.ts +1 -0
- package/components/primitives/HoverCard/HoverCard.stories.tsx +26 -0
- package/components/primitives/HoverCard/HoverCard.tsx +39 -0
- package/components/primitives/HoverCard/index.ts +8 -0
- package/components/primitives/Icon/Icon.stories.tsx +281 -0
- package/components/primitives/Icon/Icon.tsx +87 -0
- package/components/primitives/Icon/index.ts +8 -0
- package/components/primitives/Input/Input.stories.tsx +370 -0
- package/components/primitives/Input/Input.tsx +88 -0
- package/components/primitives/Input/index.ts +7 -0
- package/components/primitives/InputGroup/InputGroup.stories.tsx +40 -0
- package/components/primitives/InputGroup/InputGroup.tsx +72 -0
- package/components/primitives/InputGroup/index.ts +14 -0
- package/components/primitives/Label/Label.stories.tsx +227 -0
- package/components/primitives/Label/Label.tsx +53 -0
- package/components/primitives/Label/index.ts +7 -0
- package/components/primitives/Popover/Popover.stories.tsx +42 -0
- package/components/primitives/Popover/Popover.tsx +107 -0
- package/components/primitives/Popover/index.ts +2 -0
- package/components/primitives/Progress/Progress.stories.tsx +340 -0
- package/components/primitives/Progress/Progress.tsx +31 -0
- package/components/primitives/Progress/index.ts +1 -0
- package/components/primitives/ScrollArea/ScrollArea.stories.tsx +26 -0
- package/components/primitives/ScrollArea/ScrollArea.tsx +28 -0
- package/components/primitives/ScrollArea/index.ts +6 -0
- package/components/primitives/Select/Select.stories.tsx +288 -0
- package/components/primitives/Select/Select.tsx +162 -0
- package/components/primitives/Select/index.ts +22 -0
- package/components/primitives/Separator/Separator.stories.tsx +264 -0
- package/components/primitives/Separator/Separator.tsx +48 -0
- package/components/primitives/Separator/index.ts +7 -0
- package/components/primitives/Sidebar/Sidebar.stories.tsx +358 -0
- package/components/primitives/Sidebar/Sidebar.tsx +317 -0
- package/components/primitives/Sidebar/index.ts +41 -0
- package/components/primitives/Table/Table.stories.tsx +389 -0
- package/components/primitives/Table/Table.tsx +191 -0
- package/components/primitives/Table/index.ts +26 -0
- package/components/primitives/Tabs/Tabs.stories.tsx +129 -0
- package/components/primitives/Tabs/Tabs.tsx +70 -0
- package/components/primitives/Tabs/index.ts +13 -0
- package/components/primitives/Textarea/Textarea.stories.tsx +358 -0
- package/components/primitives/Textarea/Textarea.tsx +91 -0
- package/components/primitives/Textarea/index.ts +7 -0
- package/components/primitives/ToggleGroup/ToggleGroup.stories.tsx +87 -0
- package/components/primitives/ToggleGroup/ToggleGroup.tsx +52 -0
- package/components/primitives/ToggleGroup/index.ts +6 -0
- package/components/primitives/Tooltip/Tooltip.stories.tsx +336 -0
- package/components/primitives/Tooltip/Tooltip.tsx +78 -0
- package/components/primitives/Tooltip/index.ts +10 -0
- package/components/primitives/index.ts +34 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +157 -0
- package/components/ui/alert.tsx +66 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +117 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +334 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +184 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/drawer.tsx +118 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +48 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +33 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/select.tsx +187 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/sidebar.tsx +577 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +46 -0
- package/components/ui/toggle-group.tsx +83 -0
- package/components/ui/toggle.tsx +47 -0
- package/components/ui/tooltip.tsx +61 -0
- package/dist/index.cjs +7389 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +75 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +7160 -0
- package/dist/index.js.map +1 -0
- package/hooks/useAIDocReviewer.d.ts +0 -0
- package/lib/utils.ts +6 -0
- package/package.json +140 -0
- package/tokens/color/base.json +14 -0
- package/tokens/color/dark.json +40 -0
- package/tokens/color/green.json +21 -0
- package/tokens/color/light.json +52 -0
- package/tokens/color/neutral.json +20 -0
- package/tokens/color/violet.json +21 -0
- package/tokens/spacing.json +22 -0
- package/utils/ai-editor/format-date.ts +41 -0
- package/utils/ai-editor/index.ts +22 -0
- package/utils/ai-editor/type-guards.ts +72 -0
- package/utils/ai-editor/validation.ts +130 -0
- package/utils/editor-annotations.ts +122 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileQueue Block
|
|
3
|
+
*
|
|
4
|
+
* Central export point for the FileQueue block component and its related types.
|
|
5
|
+
*/
|
|
6
|
+
export { FileQueue } from "./FileQueue";
|
|
7
|
+
export type { FileQueueProps, FileGroup, FileItem } from "./FileQueue";
|
|
8
|
+
|
|
9
|
+
// Legacy exports for backward compatibility (used by RefinementPanel)
|
|
10
|
+
export { FileStatusBadge } from "./FileStatusBadge";
|
|
11
|
+
export type { FileStatusBadgeProps, FileStatus } from "./FileStatusBadge";
|
|
12
|
+
|
|
13
|
+
import type { FileStatus } from "./FileStatusBadge";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Legacy FileChangeData type for backward compatibility
|
|
17
|
+
* @deprecated Use FileGroup and FileItem instead
|
|
18
|
+
*/
|
|
19
|
+
export interface FileChangeData {
|
|
20
|
+
id: string;
|
|
21
|
+
filename: string;
|
|
22
|
+
status: FileStatus;
|
|
23
|
+
path: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { InteractiveChart } from './InteractiveChart'
|
|
3
|
+
import { AreaChart, Area, XAxis, CartesianGrid } from 'recharts'
|
|
4
|
+
import type { ChartConfig } from '@/components/primitives/Chart'
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Composites/InteractiveChart',
|
|
8
|
+
component: InteractiveChart,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: { layout: 'padded' },
|
|
11
|
+
} satisfies Meta<typeof InteractiveChart>
|
|
12
|
+
|
|
13
|
+
export default meta
|
|
14
|
+
type Story = StoryObj<typeof meta>
|
|
15
|
+
|
|
16
|
+
const chartData = [
|
|
17
|
+
{ month: 'Jan', desktop: 186 },
|
|
18
|
+
{ month: 'Feb', desktop: 305 },
|
|
19
|
+
{ month: 'Mar', desktop: 237 },
|
|
20
|
+
{ month: 'Apr', desktop: 73 },
|
|
21
|
+
{ month: 'May', desktop: 209 },
|
|
22
|
+
{ month: 'Jun', desktop: 214 },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const chartConfig = {
|
|
26
|
+
desktop: { label: 'Desktop', color: 'hsl(var(--chart-1))' },
|
|
27
|
+
} satisfies ChartConfig
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
title: 'Area Chart',
|
|
32
|
+
description: 'Showing total visitors for the last 6 months',
|
|
33
|
+
data: chartData,
|
|
34
|
+
config: chartConfig,
|
|
35
|
+
timeRanges: [
|
|
36
|
+
{ label: '7D', value: '7d' },
|
|
37
|
+
{ label: '30D', value: '30d' },
|
|
38
|
+
{ label: '90D', value: '90d' },
|
|
39
|
+
],
|
|
40
|
+
defaultTimeRange: '30d',
|
|
41
|
+
children: (
|
|
42
|
+
<AreaChart data={chartData}>
|
|
43
|
+
<CartesianGrid vertical={false} />
|
|
44
|
+
<XAxis dataKey="month" tickLine={false} axisLine={false} tickMargin={8} />
|
|
45
|
+
<Area dataKey="desktop" type="monotone" fill="var(--color-desktop)" fillOpacity={0.4} stroke="var(--color-desktop)" />
|
|
46
|
+
</AreaChart>
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChartContainer, ChartConfig } from "@/components/primitives/Chart"
|
|
3
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/primitives/Card"
|
|
4
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/primitives/Select"
|
|
5
|
+
import { ToggleGroup, ToggleGroupItem } from "@/components/primitives/ToggleGroup"
|
|
6
|
+
|
|
7
|
+
export interface InteractiveChartProps {
|
|
8
|
+
title: string
|
|
9
|
+
description?: string
|
|
10
|
+
data: any[]
|
|
11
|
+
config: ChartConfig
|
|
12
|
+
timeRanges?: { label: string; value: string }[]
|
|
13
|
+
defaultTimeRange?: string
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const InteractiveChart = React.memo<InteractiveChartProps>(
|
|
19
|
+
({ title, description, data, config, timeRanges, defaultTimeRange, children, className }) => {
|
|
20
|
+
const [timeRange, setTimeRange] = React.useState(defaultTimeRange || timeRanges?.[0]?.value)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Card className={className}>
|
|
24
|
+
<CardHeader>
|
|
25
|
+
<div className="flex items-center justify-between">
|
|
26
|
+
<div>
|
|
27
|
+
<CardTitle>{title}</CardTitle>
|
|
28
|
+
{description && <CardDescription>{description}</CardDescription>}
|
|
29
|
+
</div>
|
|
30
|
+
{timeRanges && timeRanges.length > 0 && (
|
|
31
|
+
<div className="flex items-center gap-2">
|
|
32
|
+
<div className="hidden sm:block">
|
|
33
|
+
<ToggleGroup type="single" value={timeRange} onValueChange={setTimeRange}>
|
|
34
|
+
{timeRanges.map((range) => (
|
|
35
|
+
<ToggleGroupItem key={range.value} value={range.value} size="sm">
|
|
36
|
+
{range.label}
|
|
37
|
+
</ToggleGroupItem>
|
|
38
|
+
))}
|
|
39
|
+
</ToggleGroup>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="sm:hidden">
|
|
42
|
+
<Select value={timeRange} onValueChange={setTimeRange}>
|
|
43
|
+
<SelectTrigger className="w-[120px]">
|
|
44
|
+
<SelectValue />
|
|
45
|
+
</SelectTrigger>
|
|
46
|
+
<SelectContent>
|
|
47
|
+
{timeRanges.map((range) => (
|
|
48
|
+
<SelectItem key={range.value} value={range.value}>
|
|
49
|
+
{range.label}
|
|
50
|
+
</SelectItem>
|
|
51
|
+
))}
|
|
52
|
+
</SelectContent>
|
|
53
|
+
</Select>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<CardContent>
|
|
60
|
+
<ChartContainer config={config} className="h-[300px] w-full">
|
|
61
|
+
{children}
|
|
62
|
+
</ChartContainer>
|
|
63
|
+
</CardContent>
|
|
64
|
+
</Card>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
InteractiveChart.displayName = "InteractiveChart"
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { ModeToggle } from './ModeToggle'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ModeToggle Composite Stories
|
|
6
|
+
*
|
|
7
|
+
* The ModeToggle component is a composite that allows users to switch between light, dark, and system themes.
|
|
8
|
+
* It composes Button and DropdownMenu primitives.
|
|
9
|
+
*
|
|
10
|
+
* ## Features
|
|
11
|
+
* - Light, dark, and system theme modes
|
|
12
|
+
* - Persistent theme preference via localStorage
|
|
13
|
+
* - Automatic system theme detection
|
|
14
|
+
* - Accessible dropdown menu
|
|
15
|
+
* - Icon-based toggle button
|
|
16
|
+
*
|
|
17
|
+
* ## Accessibility
|
|
18
|
+
* - Keyboard navigation support
|
|
19
|
+
* - Screen reader labels
|
|
20
|
+
* - Focus management
|
|
21
|
+
* - ARIA attributes
|
|
22
|
+
*
|
|
23
|
+
* ## Usage Guidelines
|
|
24
|
+
*
|
|
25
|
+
* ### Do's
|
|
26
|
+
* - Place in application header or settings
|
|
27
|
+
* - Use consistent placement across pages
|
|
28
|
+
* - Provide visual feedback for current theme
|
|
29
|
+
*
|
|
30
|
+
* ### Don'ts
|
|
31
|
+
* - Don't place multiple theme toggles
|
|
32
|
+
* - Don't override user's system preference without consent
|
|
33
|
+
* - Don't hide the toggle in nested menus
|
|
34
|
+
*/
|
|
35
|
+
const meta = {
|
|
36
|
+
title: 'Composites/ModeToggle',
|
|
37
|
+
component: ModeToggle,
|
|
38
|
+
tags: ['autodocs'],
|
|
39
|
+
parameters: {
|
|
40
|
+
layout: 'centered',
|
|
41
|
+
docs: {
|
|
42
|
+
description: {
|
|
43
|
+
component: 'A theme mode switcher component that composes Button and DropdownMenu primitives.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof ModeToggle>
|
|
48
|
+
|
|
49
|
+
export default meta
|
|
50
|
+
type Story = StoryObj<typeof meta>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default mode toggle
|
|
54
|
+
*
|
|
55
|
+
* Basic theme switcher with light, dark, and system options.
|
|
56
|
+
*/
|
|
57
|
+
export const Default: Story = {
|
|
58
|
+
render: () => <ModeToggle />,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* In Header
|
|
63
|
+
*
|
|
64
|
+
* Mode toggle as it would appear in an application header.
|
|
65
|
+
*/
|
|
66
|
+
export const InHeader: Story = {
|
|
67
|
+
render: () => (
|
|
68
|
+
<div
|
|
69
|
+
style={{
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
justifyContent: 'space-between',
|
|
73
|
+
padding: '16px',
|
|
74
|
+
borderBottom: '1px solid hsl(var(--border))',
|
|
75
|
+
width: '600px',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<div style={{ fontWeight: 600, fontSize: '18px' }}>My Application</div>
|
|
79
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
80
|
+
<span style={{ fontSize: '14px', color: 'hsl(var(--muted-foreground))' }}>Settings</span>
|
|
81
|
+
<ModeToggle />
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
),
|
|
85
|
+
parameters: {
|
|
86
|
+
docs: {
|
|
87
|
+
description: {
|
|
88
|
+
story: 'Common placement of the mode toggle in an application header.',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* With Navigation
|
|
96
|
+
*
|
|
97
|
+
* Mode toggle alongside other navigation items.
|
|
98
|
+
*/
|
|
99
|
+
export const WithNavigation: Story = {
|
|
100
|
+
render: () => (
|
|
101
|
+
<div
|
|
102
|
+
style={{
|
|
103
|
+
display: 'flex',
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
gap: '16px',
|
|
106
|
+
padding: '16px',
|
|
107
|
+
borderBottom: '1px solid hsl(var(--border))',
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<div style={{ fontWeight: 600, fontSize: '18px', marginRight: 'auto' }}>Logo</div>
|
|
111
|
+
<a href="#" style={{ fontSize: '14px', textDecoration: 'none', color: 'hsl(var(--foreground))' }}>
|
|
112
|
+
Home
|
|
113
|
+
</a>
|
|
114
|
+
<a href="#" style={{ fontSize: '14px', textDecoration: 'none', color: 'hsl(var(--foreground))' }}>
|
|
115
|
+
About
|
|
116
|
+
</a>
|
|
117
|
+
<a href="#" style={{ fontSize: '14px', textDecoration: 'none', color: 'hsl(var(--foreground))' }}>
|
|
118
|
+
Contact
|
|
119
|
+
</a>
|
|
120
|
+
<ModeToggle />
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
parameters: {
|
|
124
|
+
docs: {
|
|
125
|
+
description: {
|
|
126
|
+
story: 'Mode toggle integrated with navigation menu.',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Light Theme Preview
|
|
134
|
+
*
|
|
135
|
+
* Mode toggle in light theme context.
|
|
136
|
+
*/
|
|
137
|
+
export const LightTheme: Story = {
|
|
138
|
+
render: () => (
|
|
139
|
+
<div className="light" style={{ padding: '24px', background: 'hsl(0 0% 100%)', borderRadius: '8px' }}>
|
|
140
|
+
<div style={{ marginBottom: '16px', color: 'hsl(222.2 47.4% 11.2%)' }}>
|
|
141
|
+
<p style={{ marginBottom: '8px' }}>Current theme: Light</p>
|
|
142
|
+
<ModeToggle />
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
),
|
|
146
|
+
parameters: {
|
|
147
|
+
docs: {
|
|
148
|
+
description: {
|
|
149
|
+
story: 'Mode toggle appearance in light theme.',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
backgrounds: { disable: true },
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Dark Theme Preview
|
|
158
|
+
*
|
|
159
|
+
* Mode toggle in dark theme context.
|
|
160
|
+
*/
|
|
161
|
+
export const DarkTheme: Story = {
|
|
162
|
+
render: () => (
|
|
163
|
+
<div className="dark" style={{ padding: '24px', background: 'hsl(222.2 84% 4.9%)', borderRadius: '8px' }}>
|
|
164
|
+
<div style={{ marginBottom: '16px', color: 'hsl(210 40% 98%)' }}>
|
|
165
|
+
<p style={{ marginBottom: '8px' }}>Current theme: Dark</p>
|
|
166
|
+
<ModeToggle />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
),
|
|
170
|
+
parameters: {
|
|
171
|
+
docs: {
|
|
172
|
+
description: {
|
|
173
|
+
story: 'Mode toggle appearance in dark theme.',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
backgrounds: { disable: true },
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Interactive Demo
|
|
182
|
+
*
|
|
183
|
+
* Try switching themes to see the effect.
|
|
184
|
+
*/
|
|
185
|
+
export const InteractiveDemo: Story = {
|
|
186
|
+
render: () => (
|
|
187
|
+
<div style={{ padding: '24px', border: '1px solid hsl(var(--border))', borderRadius: '8px', minWidth: '400px' }}>
|
|
188
|
+
<div style={{ marginBottom: '16px' }}>
|
|
189
|
+
<h3 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>Theme Switcher Demo</h3>
|
|
190
|
+
<p style={{ fontSize: '14px', color: 'hsl(var(--muted-foreground))', marginBottom: '16px' }}>
|
|
191
|
+
Click the button below to switch between light, dark, and system themes.
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
195
|
+
<span style={{ fontSize: '14px' }}>Theme:</span>
|
|
196
|
+
<ModeToggle />
|
|
197
|
+
</div>
|
|
198
|
+
<div style={{ marginTop: '24px', padding: '16px', background: 'hsl(var(--muted))', borderRadius: '6px' }}>
|
|
199
|
+
<p style={{ fontSize: '14px' }}>
|
|
200
|
+
This content will adapt to the selected theme. The theme preference is saved to localStorage.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
),
|
|
205
|
+
parameters: {
|
|
206
|
+
docs: {
|
|
207
|
+
description: {
|
|
208
|
+
story: 'Interactive demo showing theme switching in action.',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Button } from "@/components/primitives/Button"
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from "@/components/primitives/DropdownMenu"
|
|
9
|
+
import { Icon } from "@/components/primitives/Icon"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ModeToggle Composite
|
|
13
|
+
*
|
|
14
|
+
* A theme mode switcher component that allows users to toggle between light, dark, and system themes.
|
|
15
|
+
* Composes Button and DropdownMenu primitives.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <ModeToggle />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface ModeToggleProps {
|
|
24
|
+
/**
|
|
25
|
+
* Additional CSS classes
|
|
26
|
+
*/
|
|
27
|
+
className?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ModeToggle component
|
|
32
|
+
*
|
|
33
|
+
* Provides a dropdown menu to switch between light, dark, and system theme modes.
|
|
34
|
+
* Uses local storage to persist the user's theme preference.
|
|
35
|
+
*/
|
|
36
|
+
export const ModeToggle = React.memo<ModeToggleProps>(({ className }) => {
|
|
37
|
+
const [theme, setThemeState] = React.useState<"light" | "dark" | "system">("system")
|
|
38
|
+
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
// Load theme from localStorage on mount
|
|
41
|
+
const stored = localStorage.getItem("theme") as "light" | "dark" | "system" | null
|
|
42
|
+
if (stored) {
|
|
43
|
+
setThemeState(stored)
|
|
44
|
+
}
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
// Apply theme to document
|
|
49
|
+
const root = window.document.documentElement
|
|
50
|
+
root.classList.remove("light", "dark")
|
|
51
|
+
|
|
52
|
+
if (theme === "system") {
|
|
53
|
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
54
|
+
? "dark"
|
|
55
|
+
: "light"
|
|
56
|
+
root.classList.add(systemTheme)
|
|
57
|
+
} else {
|
|
58
|
+
root.classList.add(theme)
|
|
59
|
+
}
|
|
60
|
+
}, [theme])
|
|
61
|
+
|
|
62
|
+
const setTheme = (newTheme: "light" | "dark" | "system") => {
|
|
63
|
+
localStorage.setItem("theme", newTheme)
|
|
64
|
+
setThemeState(newTheme)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<DropdownMenu>
|
|
69
|
+
<DropdownMenuTrigger asChild>
|
|
70
|
+
<Button variant="ghost" size="icon" className={className}>
|
|
71
|
+
<Icon
|
|
72
|
+
name="sun"
|
|
73
|
+
className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
|
|
74
|
+
/>
|
|
75
|
+
<Icon
|
|
76
|
+
name="moon"
|
|
77
|
+
className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
|
|
78
|
+
/>
|
|
79
|
+
<span className="sr-only">Toggle theme</span>
|
|
80
|
+
</Button>
|
|
81
|
+
</DropdownMenuTrigger>
|
|
82
|
+
<DropdownMenuContent align="end">
|
|
83
|
+
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
84
|
+
<Icon name="sun" className="mr-2 h-4 w-4" />
|
|
85
|
+
<span>Light</span>
|
|
86
|
+
</DropdownMenuItem>
|
|
87
|
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
88
|
+
<Icon name="moon" className="mr-2 h-4 w-4" />
|
|
89
|
+
<span>Dark</span>
|
|
90
|
+
</DropdownMenuItem>
|
|
91
|
+
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
92
|
+
<Icon name="laptop" className="mr-2 h-4 w-4" />
|
|
93
|
+
<span>System</span>
|
|
94
|
+
</DropdownMenuItem>
|
|
95
|
+
</DropdownMenuContent>
|
|
96
|
+
</DropdownMenu>
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
ModeToggle.displayName = "ModeToggle"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { NavUser } from './NavUser'
|
|
3
|
+
import { SidebarProvider, Sidebar, SidebarFooter } from '@/components/primitives/Sidebar'
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Composites/NavUser',
|
|
7
|
+
component: NavUser,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: { layout: 'padded' },
|
|
10
|
+
} satisfies Meta<typeof NavUser>
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj<typeof meta>
|
|
14
|
+
|
|
15
|
+
const user = {
|
|
16
|
+
name: 'John Doe',
|
|
17
|
+
email: 'john@example.com',
|
|
18
|
+
avatar: 'https://github.com/shadcn.png',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<SidebarProvider>
|
|
24
|
+
<Sidebar>
|
|
25
|
+
<SidebarFooter>
|
|
26
|
+
<NavUser user={user} />
|
|
27
|
+
</SidebarFooter>
|
|
28
|
+
</Sidebar>
|
|
29
|
+
</SidebarProvider>
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const WithActions: Story = {
|
|
34
|
+
render: () => (
|
|
35
|
+
<SidebarProvider>
|
|
36
|
+
<Sidebar>
|
|
37
|
+
<SidebarFooter>
|
|
38
|
+
<NavUser
|
|
39
|
+
user={user}
|
|
40
|
+
actions={[
|
|
41
|
+
{ label: 'Profile', onClick: () => console.log('Profile') },
|
|
42
|
+
{ label: 'Settings', onClick: () => console.log('Settings') },
|
|
43
|
+
{ label: 'Logout', onClick: () => console.log('Logout') },
|
|
44
|
+
]}
|
|
45
|
+
/>
|
|
46
|
+
</SidebarFooter>
|
|
47
|
+
</Sidebar>
|
|
48
|
+
</SidebarProvider>
|
|
49
|
+
),
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import {
|
|
3
|
+
SidebarMenu,
|
|
4
|
+
SidebarMenuItem,
|
|
5
|
+
SidebarMenuButton,
|
|
6
|
+
} from "@/components/primitives/Sidebar"
|
|
7
|
+
import { Avatar, AvatarImage, AvatarFallback } from "@/components/primitives/Avatar"
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from "@/components/primitives/DropdownMenu"
|
|
14
|
+
|
|
15
|
+
export interface NavUserProps {
|
|
16
|
+
user: {
|
|
17
|
+
name: string
|
|
18
|
+
email: string
|
|
19
|
+
avatar?: string
|
|
20
|
+
}
|
|
21
|
+
actions?: { label: string; onClick: () => void }[]
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const NavUser = React.memo<NavUserProps>(({ user, actions, className }) => {
|
|
26
|
+
return (
|
|
27
|
+
<SidebarMenu className={className}>
|
|
28
|
+
<SidebarMenuItem>
|
|
29
|
+
<DropdownMenu>
|
|
30
|
+
<DropdownMenuTrigger asChild>
|
|
31
|
+
<SidebarMenuButton
|
|
32
|
+
size="lg"
|
|
33
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
34
|
+
>
|
|
35
|
+
<Avatar className="h-8 w-8 rounded-lg">
|
|
36
|
+
<AvatarImage src={user.avatar} alt={user.name} />
|
|
37
|
+
<AvatarFallback className="rounded-lg">{user.name[0]}</AvatarFallback>
|
|
38
|
+
</Avatar>
|
|
39
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
40
|
+
<span className="truncate font-medium">{user.name}</span>
|
|
41
|
+
<span className="truncate text-xs text-muted-foreground">{user.email}</span>
|
|
42
|
+
</div>
|
|
43
|
+
</SidebarMenuButton>
|
|
44
|
+
</DropdownMenuTrigger>
|
|
45
|
+
{actions && actions.length > 0 && (
|
|
46
|
+
<DropdownMenuContent align="end" className="w-56">
|
|
47
|
+
{actions.map((action) => (
|
|
48
|
+
<DropdownMenuItem key={action.label} onClick={action.onClick}>
|
|
49
|
+
{action.label}
|
|
50
|
+
</DropdownMenuItem>
|
|
51
|
+
))}
|
|
52
|
+
</DropdownMenuContent>
|
|
53
|
+
)}
|
|
54
|
+
</DropdownMenu>
|
|
55
|
+
</SidebarMenuItem>
|
|
56
|
+
</SidebarMenu>
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
NavUser.displayName = "NavUser"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { NavigationList } from './NavigationList'
|
|
3
|
+
import { SidebarProvider, Sidebar, SidebarContent } from '@/components/primitives/Sidebar'
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Composites/NavigationList',
|
|
7
|
+
component: NavigationList,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: { layout: 'padded' },
|
|
10
|
+
} satisfies Meta<typeof NavigationList>
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj<typeof meta>
|
|
14
|
+
|
|
15
|
+
const items = [
|
|
16
|
+
{ title: 'Dashboard', url: '#', icon: 'layout-dashboard' },
|
|
17
|
+
{ title: 'Projects', url: '#', icon: 'folder' },
|
|
18
|
+
{ title: 'Tasks', url: '#', icon: 'check-square' },
|
|
19
|
+
{ title: 'Settings', url: '#', icon: 'settings' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<SidebarProvider>
|
|
25
|
+
<Sidebar>
|
|
26
|
+
<SidebarContent>
|
|
27
|
+
<NavigationList items={items} />
|
|
28
|
+
</SidebarContent>
|
|
29
|
+
</Sidebar>
|
|
30
|
+
</SidebarProvider>
|
|
31
|
+
),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const WithActiveState: Story = {
|
|
35
|
+
render: () => (
|
|
36
|
+
<SidebarProvider>
|
|
37
|
+
<Sidebar>
|
|
38
|
+
<SidebarContent>
|
|
39
|
+
<NavigationList
|
|
40
|
+
items={items.map((item, i) => ({ ...item, isActive: i === 0 }))}
|
|
41
|
+
/>
|
|
42
|
+
</SidebarContent>
|
|
43
|
+
</Sidebar>
|
|
44
|
+
</SidebarProvider>
|
|
45
|
+
),
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import {
|
|
3
|
+
SidebarMenu,
|
|
4
|
+
SidebarMenuItem,
|
|
5
|
+
SidebarMenuButton,
|
|
6
|
+
} from "@/components/primitives/Sidebar"
|
|
7
|
+
import { Icon } from "@/components/primitives/Icon"
|
|
8
|
+
|
|
9
|
+
export interface NavigationItem {
|
|
10
|
+
title: string
|
|
11
|
+
url: string
|
|
12
|
+
icon?: string
|
|
13
|
+
isActive?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NavigationListProps {
|
|
17
|
+
items: NavigationItem[]
|
|
18
|
+
onItemClick?: (item: NavigationItem) => void
|
|
19
|
+
className?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const NavigationList = React.memo<NavigationListProps>(
|
|
23
|
+
({ items, onItemClick, className }) => {
|
|
24
|
+
return (
|
|
25
|
+
<SidebarMenu className={className}>
|
|
26
|
+
{items.map((item) => (
|
|
27
|
+
<SidebarMenuItem key={item.title}>
|
|
28
|
+
<SidebarMenuButton
|
|
29
|
+
tooltip={item.title}
|
|
30
|
+
isActive={item.isActive}
|
|
31
|
+
onClick={() => onItemClick?.(item)}
|
|
32
|
+
asChild
|
|
33
|
+
>
|
|
34
|
+
<a href={item.url}>
|
|
35
|
+
{item.icon && <Icon name={item.icon} />}
|
|
36
|
+
<span>{item.title}</span>
|
|
37
|
+
</a>
|
|
38
|
+
</SidebarMenuButton>
|
|
39
|
+
</SidebarMenuItem>
|
|
40
|
+
))}
|
|
41
|
+
</SidebarMenu>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
NavigationList.displayName = "NavigationList"
|