@weave-design-system/react 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/dist/index.cjs +7729 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +677 -0
- package/dist/index.d.ts +677 -0
- package/dist/index.js +7654 -0
- package/dist/index.js.map +1 -0
- package/dist/theme.css +2 -0
- package/dist/tokens.cjs +78 -0
- package/dist/tokens.cjs.map +1 -0
- package/dist/tokens.d.cts +67 -0
- package/dist/tokens.d.ts +67 -0
- package/dist/tokens.js +70 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +103 -0
- package/src/data-display/activity-feed/ActivityFeed.stories.tsx +16 -0
- package/src/data-display/activity-feed/ActivityFeed.test.tsx +11 -0
- package/src/data-display/activity-feed/ActivityFeed.tsx +34 -0
- package/src/data-display/activity-feed/index.ts +2 -0
- package/src/data-display/circular-progress/CircularProgress.stories.tsx +10 -0
- package/src/data-display/circular-progress/CircularProgress.test.tsx +11 -0
- package/src/data-display/circular-progress/CircularProgress.tsx +35 -0
- package/src/data-display/circular-progress/index.ts +2 -0
- package/src/data-display/empty-state/EmptyState.stories.tsx +15 -0
- package/src/data-display/empty-state/EmptyState.test.tsx +18 -0
- package/src/data-display/empty-state/EmptyState.tsx +34 -0
- package/src/data-display/empty-state/index.ts +2 -0
- package/src/data-display/progress-bar/ProgressBar.stories.tsx +10 -0
- package/src/data-display/progress-bar/ProgressBar.test.tsx +15 -0
- package/src/data-display/progress-bar/ProgressBar.tsx +38 -0
- package/src/data-display/progress-bar/index.ts +2 -0
- package/src/data-display/stat-card/StatCard.stories.tsx +10 -0
- package/src/data-display/stat-card/StatCard.test.tsx +15 -0
- package/src/data-display/stat-card/StatCard.tsx +40 -0
- package/src/data-display/stat-card/index.ts +2 -0
- package/src/data-display/table/Table.stories.tsx +44 -0
- package/src/data-display/table/Table.test.tsx +16 -0
- package/src/data-display/table/Table.tsx +71 -0
- package/src/data-display/table/index.ts +1 -0
- package/src/data-display/timeline/Timeline.stories.tsx +16 -0
- package/src/data-display/timeline/Timeline.test.tsx +11 -0
- package/src/data-display/timeline/Timeline.tsx +44 -0
- package/src/data-display/timeline/index.ts +2 -0
- package/src/docs/ComponentOverview.mdx +192 -0
- package/src/docs/DesignTokens.mdx +235 -0
- package/src/docs/GettingStarted.mdx +145 -0
- package/src/feedback/alert-banner/AlertBanner.stories.tsx +10 -0
- package/src/feedback/alert-banner/AlertBanner.test.tsx +16 -0
- package/src/feedback/alert-banner/AlertBanner.tsx +47 -0
- package/src/feedback/alert-banner/index.ts +2 -0
- package/src/feedback/modal/Modal.stories.tsx +31 -0
- package/src/feedback/modal/Modal.test.tsx +33 -0
- package/src/feedback/modal/Modal.tsx +88 -0
- package/src/feedback/modal/index.ts +2 -0
- package/src/feedback/skeleton-loader/SkeletonLoader.stories.tsx +23 -0
- package/src/feedback/skeleton-loader/SkeletonLoader.test.tsx +14 -0
- package/src/feedback/skeleton-loader/SkeletonLoader.tsx +61 -0
- package/src/feedback/skeleton-loader/index.ts +2 -0
- package/src/feedback/toast/Toast.stories.tsx +27 -0
- package/src/feedback/toast/Toast.test.tsx +32 -0
- package/src/feedback/toast/Toast.tsx +106 -0
- package/src/feedback/toast/index.ts +2 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/use-controllable-state.ts +34 -0
- package/src/hooks/use-focus-trap.ts +56 -0
- package/src/hooks/use-reduced-motion.ts +17 -0
- package/src/index.ts +148 -0
- package/src/layout/card/Card.stories.tsx +45 -0
- package/src/layout/card/Card.test.tsx +23 -0
- package/src/layout/card/Card.tsx +42 -0
- package/src/layout/card/Card.types.ts +6 -0
- package/src/layout/card/index.ts +2 -0
- package/src/layout/command-palette/CommandPalette.stories.tsx +34 -0
- package/src/layout/command-palette/CommandPalette.test.tsx +43 -0
- package/src/layout/command-palette/CommandPalette.tsx +188 -0
- package/src/layout/command-palette/CommandPalette.types.ts +18 -0
- package/src/layout/command-palette/index.ts +2 -0
- package/src/layout/sidebar/Sidebar.stories.tsx +60 -0
- package/src/layout/sidebar/Sidebar.test.tsx +27 -0
- package/src/layout/sidebar/Sidebar.tsx +57 -0
- package/src/layout/sidebar/Sidebar.types.ts +14 -0
- package/src/layout/sidebar/index.ts +2 -0
- package/src/layout/top-bar/TopBar.stories.tsx +51 -0
- package/src/layout/top-bar/TopBar.test.tsx +18 -0
- package/src/layout/top-bar/TopBar.tsx +19 -0
- package/src/layout/top-bar/TopBar.types.ts +10 -0
- package/src/layout/top-bar/index.ts +2 -0
- package/src/navigation/breadcrumbs/Breadcrumbs.stories.tsx +30 -0
- package/src/navigation/breadcrumbs/Breadcrumbs.test.tsx +43 -0
- package/src/navigation/breadcrumbs/Breadcrumbs.tsx +45 -0
- package/src/navigation/breadcrumbs/Breadcrumbs.types.ts +12 -0
- package/src/navigation/breadcrumbs/index.ts +2 -0
- package/src/navigation/sidebar-nav-item/SidebarNavItem.stories.tsx +41 -0
- package/src/navigation/sidebar-nav-item/SidebarNavItem.test.tsx +46 -0
- package/src/navigation/sidebar-nav-item/SidebarNavItem.tsx +38 -0
- package/src/navigation/sidebar-nav-item/SidebarNavItem.types.ts +12 -0
- package/src/navigation/sidebar-nav-item/index.ts +2 -0
- package/src/navigation/tabs/Tabs.stories.tsx +47 -0
- package/src/navigation/tabs/Tabs.test.tsx +67 -0
- package/src/navigation/tabs/Tabs.tsx +111 -0
- package/src/navigation/tabs/Tabs.types.ts +36 -0
- package/src/navigation/tabs/index.ts +2 -0
- package/src/patterns/accordion/Accordion.stories.tsx +25 -0
- package/src/patterns/accordion/Accordion.test.tsx +44 -0
- package/src/patterns/accordion/Accordion.tsx +92 -0
- package/src/patterns/accordion/index.ts +2 -0
- package/src/patterns/action-menu/ActionMenu.stories.tsx +18 -0
- package/src/patterns/action-menu/ActionMenu.test.tsx +18 -0
- package/src/patterns/action-menu/ActionMenu.tsx +41 -0
- package/src/patterns/action-menu/index.ts +2 -0
- package/src/patterns/carousel/Carousel.stories.tsx +16 -0
- package/src/patterns/carousel/Carousel.test.tsx +16 -0
- package/src/patterns/carousel/Carousel.tsx +69 -0
- package/src/patterns/carousel/index.ts +2 -0
- package/src/patterns/image-placeholder/ImagePlaceholder.stories.tsx +9 -0
- package/src/patterns/image-placeholder/ImagePlaceholder.test.tsx +10 -0
- package/src/patterns/image-placeholder/ImagePlaceholder.tsx +21 -0
- package/src/patterns/image-placeholder/index.ts +2 -0
- package/src/patterns/notification-dot/NotificationDot.stories.tsx +17 -0
- package/src/patterns/notification-dot/NotificationDot.test.tsx +14 -0
- package/src/patterns/notification-dot/NotificationDot.tsx +18 -0
- package/src/patterns/notification-dot/index.ts +2 -0
- package/src/patterns/pagination/Pagination.stories.tsx +14 -0
- package/src/patterns/pagination/Pagination.test.tsx +22 -0
- package/src/patterns/pagination/Pagination.tsx +67 -0
- package/src/patterns/pagination/index.ts +2 -0
- package/src/primitives/avatar/Avatar.stories.tsx +46 -0
- package/src/primitives/avatar/Avatar.test.tsx +35 -0
- package/src/primitives/avatar/Avatar.tsx +49 -0
- package/src/primitives/avatar/Avatar.types.ts +21 -0
- package/src/primitives/avatar/AvatarGroup.tsx +27 -0
- package/src/primitives/avatar/index.ts +3 -0
- package/src/primitives/badge/Badge.stories.tsx +28 -0
- package/src/primitives/badge/Badge.test.tsx +23 -0
- package/src/primitives/badge/Badge.tsx +44 -0
- package/src/primitives/badge/Badge.types.ts +14 -0
- package/src/primitives/badge/index.ts +2 -0
- package/src/primitives/button/Button.stories.tsx +81 -0
- package/src/primitives/button/Button.test.tsx +64 -0
- package/src/primitives/button/Button.tsx +85 -0
- package/src/primitives/button/Button.types.ts +17 -0
- package/src/primitives/button/index.ts +2 -0
- package/src/primitives/checkbox/Checkbox.stories.tsx +27 -0
- package/src/primitives/checkbox/Checkbox.test.tsx +30 -0
- package/src/primitives/checkbox/Checkbox.tsx +79 -0
- package/src/primitives/checkbox/Checkbox.types.ts +12 -0
- package/src/primitives/checkbox/index.ts +2 -0
- package/src/primitives/combobox/Combobox.stories.tsx +44 -0
- package/src/primitives/combobox/Combobox.test.tsx +44 -0
- package/src/primitives/combobox/Combobox.tsx +201 -0
- package/src/primitives/combobox/Combobox.types.ts +25 -0
- package/src/primitives/combobox/index.ts +2 -0
- package/src/primitives/date-input/DateInput.stories.tsx +23 -0
- package/src/primitives/date-input/DateInput.test.tsx +22 -0
- package/src/primitives/date-input/DateInput.tsx +66 -0
- package/src/primitives/date-input/DateInput.types.ts +10 -0
- package/src/primitives/date-input/index.ts +2 -0
- package/src/primitives/file-upload/FileUploadDropzone.stories.tsx +27 -0
- package/src/primitives/file-upload/FileUploadDropzone.test.tsx +26 -0
- package/src/primitives/file-upload/FileUploadDropzone.tsx +99 -0
- package/src/primitives/file-upload/FileUploadDropzone.types.ts +14 -0
- package/src/primitives/file-upload/index.ts +2 -0
- package/src/primitives/input/InputGroup.stories.tsx +31 -0
- package/src/primitives/input/InputGroup.test.tsx +40 -0
- package/src/primitives/input/InputGroup.tsx +65 -0
- package/src/primitives/input/InputGroup.types.ts +12 -0
- package/src/primitives/input/index.ts +2 -0
- package/src/primitives/link/Link.stories.tsx +28 -0
- package/src/primitives/link/Link.test.tsx +23 -0
- package/src/primitives/link/Link.tsx +28 -0
- package/src/primitives/link/Link.types.ts +8 -0
- package/src/primitives/link/index.ts +2 -0
- package/src/primitives/radio/Radio.stories.tsx +29 -0
- package/src/primitives/radio/Radio.test.tsx +32 -0
- package/src/primitives/radio/Radio.tsx +59 -0
- package/src/primitives/radio/Radio.types.ts +6 -0
- package/src/primitives/radio/index.ts +2 -0
- package/src/primitives/select/SelectGroup.stories.tsx +33 -0
- package/src/primitives/select/SelectGroup.test.tsx +34 -0
- package/src/primitives/select/SelectGroup.tsx +72 -0
- package/src/primitives/select/SelectGroup.types.ts +12 -0
- package/src/primitives/select/index.ts +2 -0
- package/src/primitives/slider/Slider.stories.tsx +23 -0
- package/src/primitives/slider/Slider.test.tsx +28 -0
- package/src/primitives/slider/Slider.tsx +80 -0
- package/src/primitives/slider/Slider.types.ts +22 -0
- package/src/primitives/slider/index.ts +2 -0
- package/src/primitives/textarea/TextareaGroup.stories.tsx +27 -0
- package/src/primitives/textarea/TextareaGroup.test.tsx +24 -0
- package/src/primitives/textarea/TextareaGroup.tsx +59 -0
- package/src/primitives/textarea/TextareaGroup.types.ts +10 -0
- package/src/primitives/textarea/index.ts +2 -0
- package/src/primitives/toggle/Toggle.stories.tsx +27 -0
- package/src/primitives/toggle/Toggle.test.tsx +31 -0
- package/src/primitives/toggle/Toggle.tsx +65 -0
- package/src/primitives/toggle/Toggle.types.ts +16 -0
- package/src/primitives/toggle/index.ts +2 -0
- package/src/primitives/tooltip/Tooltip.stories.tsx +45 -0
- package/src/primitives/tooltip/Tooltip.test.tsx +28 -0
- package/src/primitives/tooltip/Tooltip.tsx +94 -0
- package/src/primitives/tooltip/Tooltip.types.ts +16 -0
- package/src/primitives/tooltip/index.ts +2 -0
- package/src/productivity/comment-thread/CommentThread.stories.tsx +20 -0
- package/src/productivity/comment-thread/CommentThread.test.tsx +21 -0
- package/src/productivity/comment-thread/CommentThread.tsx +47 -0
- package/src/productivity/comment-thread/index.ts +2 -0
- package/src/productivity/kanban-board/KanbanBoard.tsx +41 -0
- package/src/productivity/kanban-board/index.ts +2 -0
- package/src/productivity/kanban-column/KanbanColumn.stories.tsx +131 -0
- package/src/productivity/kanban-column/KanbanColumn.test.tsx +18 -0
- package/src/productivity/kanban-column/KanbanColumn.tsx +58 -0
- package/src/productivity/kanban-column/SortableTaskCard.tsx +46 -0
- package/src/productivity/kanban-column/index.ts +4 -0
- package/src/productivity/priority-selector/PrioritySelector.stories.tsx +14 -0
- package/src/productivity/priority-selector/PrioritySelector.test.tsx +18 -0
- package/src/productivity/priority-selector/PrioritySelector.tsx +40 -0
- package/src/productivity/priority-selector/index.ts +2 -0
- package/src/productivity/rich-text-toolbar/RichTextToolbar.stories.tsx +19 -0
- package/src/productivity/rich-text-toolbar/RichTextToolbar.test.tsx +21 -0
- package/src/productivity/rich-text-toolbar/RichTextToolbar.tsx +50 -0
- package/src/productivity/rich-text-toolbar/index.ts +2 -0
- package/src/productivity/task-card/TaskCard.stories.tsx +20 -0
- package/src/productivity/task-card/TaskCard.test.tsx +21 -0
- package/src/productivity/task-card/TaskCard.tsx +76 -0
- package/src/productivity/task-card/index.ts +2 -0
- package/src/test-setup.ts +1 -0
- package/src/tokens/index.ts +1 -0
- package/src/tokens/tokens.ts +71 -0
- package/src/tokens/weave-theme.css +168 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cn } from '../../utils/cn';
|
|
2
|
+
|
|
3
|
+
export type SkeletonShape = 'text' | 'avatar' | 'image' | 'custom';
|
|
4
|
+
|
|
5
|
+
export interface SkeletonLoaderProps {
|
|
6
|
+
shape?: SkeletonShape;
|
|
7
|
+
width?: string | number;
|
|
8
|
+
height?: string | number;
|
|
9
|
+
lines?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function SkeletonLoader({ shape = 'text', width, height, lines = 3, className }: SkeletonLoaderProps) {
|
|
14
|
+
const baseClass = 'bg-surface animate-skeleton-pulse rounded';
|
|
15
|
+
|
|
16
|
+
if (shape === 'avatar') {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(baseClass, 'rounded-full', className)}
|
|
20
|
+
style={{ width: width ?? 40, height: height ?? 40 }}
|
|
21
|
+
aria-hidden
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (shape === 'image') {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className={cn(baseClass, 'rounded-lg', className)}
|
|
30
|
+
style={{ width: width ?? '100%', height: height ?? 200 }}
|
|
31
|
+
aria-hidden
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (shape === 'custom') {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={cn(baseClass, className)}
|
|
40
|
+
style={{ width, height }}
|
|
41
|
+
aria-hidden
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Text lines
|
|
47
|
+
return (
|
|
48
|
+
<div className={cn('flex flex-col gap-2', className)} aria-hidden>
|
|
49
|
+
{Array.from({ length: lines }).map((_, i) => (
|
|
50
|
+
<div
|
|
51
|
+
key={i}
|
|
52
|
+
className={baseClass}
|
|
53
|
+
style={{
|
|
54
|
+
height: height ?? 16,
|
|
55
|
+
width: i === lines - 1 ? '60%' : (width ?? '100%'),
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ToastProvider, useToast } from './Toast';
|
|
3
|
+
import { Button } from '../../primitives/button';
|
|
4
|
+
|
|
5
|
+
const meta: Meta = { title: 'Feedback/Toast', tags: ['autodocs'] };
|
|
6
|
+
export default meta;
|
|
7
|
+
type Story = StoryObj;
|
|
8
|
+
|
|
9
|
+
function ToastDemo() {
|
|
10
|
+
const { toast } = useToast();
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-wrap gap-3">
|
|
13
|
+
<Button variant="secondary" onClick={() => toast({ title: 'Task saved', message: 'Your changes have been saved.', variant: 'success' })}>Success</Button>
|
|
14
|
+
<Button variant="destructive" onClick={() => toast({ title: 'Error', message: 'Something went wrong.', variant: 'error' })}>Error</Button>
|
|
15
|
+
<Button variant="outline" onClick={() => toast({ title: 'Warning', message: 'Check your input.', variant: 'warning' })}>Warning</Button>
|
|
16
|
+
<Button variant="ghost" onClick={() => toast({ title: 'Info', message: 'New update available.', variant: 'info' })}>Info</Button>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<ToastProvider>
|
|
24
|
+
<ToastDemo />
|
|
25
|
+
</ToastProvider>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { ToastProvider, useToast } from './Toast';
|
|
5
|
+
|
|
6
|
+
function ToastTrigger() {
|
|
7
|
+
const { toast } = useToast();
|
|
8
|
+
return <button onClick={() => toast({ title: 'Saved', variant: 'success' })}>Show</button>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('Toast', () => {
|
|
12
|
+
it('shows toast on trigger', async () => {
|
|
13
|
+
render(
|
|
14
|
+
<ToastProvider>
|
|
15
|
+
<ToastTrigger />
|
|
16
|
+
</ToastProvider>,
|
|
17
|
+
);
|
|
18
|
+
await userEvent.click(screen.getByText('Show'));
|
|
19
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Saved');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('dismisses on close click', async () => {
|
|
23
|
+
render(
|
|
24
|
+
<ToastProvider>
|
|
25
|
+
<ToastTrigger />
|
|
26
|
+
</ToastProvider>,
|
|
27
|
+
);
|
|
28
|
+
await userEvent.click(screen.getByText('Show'));
|
|
29
|
+
await userEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
|
|
30
|
+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { CheckCircle2, AlertCircle, AlertTriangle, Info, X } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
|
|
5
|
+
export type ToastVariant = 'success' | 'error' | 'warning' | 'info';
|
|
6
|
+
|
|
7
|
+
export interface ToastData {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
variant: ToastVariant;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ToastContextValue {
|
|
16
|
+
toast: (data: Omit<ToastData, 'id'>) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ToastContext = createContext<ToastContextValue>({ toast: () => {} });
|
|
20
|
+
export const useToast = () => useContext(ToastContext);
|
|
21
|
+
|
|
22
|
+
const icons: Record<ToastVariant, React.ReactNode> = {
|
|
23
|
+
success: <CheckCircle2 size={18} />,
|
|
24
|
+
error: <AlertCircle size={18} />,
|
|
25
|
+
warning: <AlertTriangle size={18} />,
|
|
26
|
+
info: <Info size={18} />,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const variantStyles: Record<ToastVariant, string> = {
|
|
30
|
+
success: 'border-success text-success',
|
|
31
|
+
error: 'border-error text-error',
|
|
32
|
+
warning: 'border-warning text-warning',
|
|
33
|
+
info: 'border-info text-info',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let toastId = 0;
|
|
37
|
+
|
|
38
|
+
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
39
|
+
const [toasts, setToasts] = useState<ToastData[]>([]);
|
|
40
|
+
const timers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
41
|
+
|
|
42
|
+
const removeToast = useCallback((id: string) => {
|
|
43
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
44
|
+
const timer = timers.current.get(id);
|
|
45
|
+
if (timer) {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
timers.current.delete(id);
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const toast = useCallback(
|
|
52
|
+
(data: Omit<ToastData, 'id'>) => {
|
|
53
|
+
const id = `toast-${++toastId}`;
|
|
54
|
+
setToasts((prev) => [...prev, { ...data, id }]);
|
|
55
|
+
const duration = data.duration ?? 5000;
|
|
56
|
+
if (duration > 0) {
|
|
57
|
+
const timer = setTimeout(() => removeToast(id), duration);
|
|
58
|
+
timers.current.set(id, timer);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[removeToast],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Cleanup timers on unmount
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
return () => {
|
|
67
|
+
timers.current.forEach((timer) => clearTimeout(timer));
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ToastContext.Provider value={{ toast }}>
|
|
73
|
+
{children}
|
|
74
|
+
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-sm" aria-live="polite">
|
|
75
|
+
{toasts.map((t) => (
|
|
76
|
+
<div
|
|
77
|
+
key={t.id}
|
|
78
|
+
className={cn(
|
|
79
|
+
'flex items-start gap-3 rounded-lg bg-white border-l-4 p-4 shadow-2',
|
|
80
|
+
'animate-slide-in-right',
|
|
81
|
+
variantStyles[t.variant],
|
|
82
|
+
)}
|
|
83
|
+
role="alert"
|
|
84
|
+
>
|
|
85
|
+
<span className="shrink-0 mt-0.5">{icons[t.variant]}</span>
|
|
86
|
+
<div className="flex-1 min-w-0">
|
|
87
|
+
<p className="text-text-primary font-medium" style={{ fontSize: 'var(--text-body-sm)' }}>{t.title}</p>
|
|
88
|
+
{t.message && (
|
|
89
|
+
<p className="text-text-secondary mt-0.5" style={{ fontSize: 'var(--text-body-sm)' }}>{t.message}</p>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => removeToast(t.id)}
|
|
95
|
+
className="shrink-0 p-1 rounded text-text-secondary hover:bg-surface transition-colors"
|
|
96
|
+
aria-label="Dismiss"
|
|
97
|
+
style={{ transitionDuration: 'var(--duration-micro)' }}
|
|
98
|
+
>
|
|
99
|
+
<X size={14} />
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
</ToastContext.Provider>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseControllableStateParams<T> {
|
|
4
|
+
value?: T;
|
|
5
|
+
defaultValue: T;
|
|
6
|
+
onChange?: (value: T) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useControllableState<T>({
|
|
10
|
+
value: controlledValue,
|
|
11
|
+
defaultValue,
|
|
12
|
+
onChange,
|
|
13
|
+
}: UseControllableStateParams<T>): [T, (value: T) => void] {
|
|
14
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
15
|
+
const isControlled = controlledValue !== undefined;
|
|
16
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
17
|
+
|
|
18
|
+
const onChangeRef = useRef(onChange);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
onChangeRef.current = onChange;
|
|
21
|
+
}, [onChange]);
|
|
22
|
+
|
|
23
|
+
const setValue = useCallback(
|
|
24
|
+
(next: T) => {
|
|
25
|
+
if (!isControlled) {
|
|
26
|
+
setInternalValue(next);
|
|
27
|
+
}
|
|
28
|
+
onChangeRef.current?.(next);
|
|
29
|
+
},
|
|
30
|
+
[isControlled],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return [value, setValue];
|
|
34
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
const FOCUSABLE_SELECTOR = [
|
|
4
|
+
'a[href]',
|
|
5
|
+
'button:not([disabled])',
|
|
6
|
+
'input:not([disabled])',
|
|
7
|
+
'select:not([disabled])',
|
|
8
|
+
'textarea:not([disabled])',
|
|
9
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
10
|
+
].join(', ');
|
|
11
|
+
|
|
12
|
+
export function useFocusTrap<T extends HTMLElement>(active: boolean = true) {
|
|
13
|
+
const containerRef = useRef<T>(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!active) return;
|
|
17
|
+
const container = containerRef.current;
|
|
18
|
+
if (!container) return;
|
|
19
|
+
|
|
20
|
+
const focusableElements = () =>
|
|
21
|
+
Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
22
|
+
|
|
23
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
24
|
+
if (event.key !== 'Tab') return;
|
|
25
|
+
|
|
26
|
+
const elements = focusableElements();
|
|
27
|
+
if (elements.length === 0) return;
|
|
28
|
+
|
|
29
|
+
const first = elements[0];
|
|
30
|
+
const last = elements[elements.length - 1];
|
|
31
|
+
|
|
32
|
+
if (event.shiftKey) {
|
|
33
|
+
if (document.activeElement === first) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
last.focus();
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
if (document.activeElement === last) {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
first.focus();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Focus the first focusable element on mount
|
|
46
|
+
const elements = focusableElements();
|
|
47
|
+
if (elements.length > 0) {
|
|
48
|
+
elements[0].focus();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
container.addEventListener('keydown', handleKeyDown);
|
|
52
|
+
return () => container.removeEventListener('keydown', handleKeyDown);
|
|
53
|
+
}, [active]);
|
|
54
|
+
|
|
55
|
+
return containerRef;
|
|
56
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useReducedMotion(): boolean {
|
|
4
|
+
const [reducedMotion, setReducedMotion] = useState(() => {
|
|
5
|
+
if (typeof window === 'undefined') return false;
|
|
6
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
11
|
+
const handler = (event: MediaQueryListEvent) => setReducedMotion(event.matches);
|
|
12
|
+
mq.addEventListener('change', handler);
|
|
13
|
+
return () => mq.removeEventListener('change', handler);
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
return reducedMotion;
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Primitives
|
|
2
|
+
export { Button } from './primitives/button';
|
|
3
|
+
export type { ButtonProps, ButtonVariant, ButtonSize } from './primitives/button';
|
|
4
|
+
|
|
5
|
+
export { InputGroup } from './primitives/input';
|
|
6
|
+
export type { InputGroupProps } from './primitives/input';
|
|
7
|
+
|
|
8
|
+
export { TextareaGroup } from './primitives/textarea';
|
|
9
|
+
export type { TextareaGroupProps } from './primitives/textarea';
|
|
10
|
+
|
|
11
|
+
export { SelectGroup } from './primitives/select';
|
|
12
|
+
export type { SelectGroupProps } from './primitives/select';
|
|
13
|
+
|
|
14
|
+
export { Checkbox } from './primitives/checkbox';
|
|
15
|
+
export type { CheckboxProps } from './primitives/checkbox';
|
|
16
|
+
|
|
17
|
+
export { Radio } from './primitives/radio';
|
|
18
|
+
export type { RadioProps } from './primitives/radio';
|
|
19
|
+
|
|
20
|
+
export { Toggle } from './primitives/toggle';
|
|
21
|
+
export type { ToggleProps } from './primitives/toggle';
|
|
22
|
+
|
|
23
|
+
export { Badge } from './primitives/badge';
|
|
24
|
+
export type { BadgeProps, BadgeVariant } from './primitives/badge';
|
|
25
|
+
|
|
26
|
+
export { Avatar, AvatarGroup } from './primitives/avatar';
|
|
27
|
+
export type { AvatarProps, AvatarGroupProps, AvatarSize } from './primitives/avatar';
|
|
28
|
+
|
|
29
|
+
export { Link } from './primitives/link';
|
|
30
|
+
export type { LinkProps, LinkVariant } from './primitives/link';
|
|
31
|
+
|
|
32
|
+
export { Tooltip } from './primitives/tooltip';
|
|
33
|
+
export type { TooltipProps, TooltipVariant } from './primitives/tooltip';
|
|
34
|
+
|
|
35
|
+
export { Slider } from './primitives/slider';
|
|
36
|
+
export type { SliderProps } from './primitives/slider';
|
|
37
|
+
|
|
38
|
+
export { DateInput } from './primitives/date-input';
|
|
39
|
+
export type { DateInputProps } from './primitives/date-input';
|
|
40
|
+
|
|
41
|
+
export { Combobox } from './primitives/combobox';
|
|
42
|
+
export type { ComboboxProps, ComboboxOption } from './primitives/combobox';
|
|
43
|
+
|
|
44
|
+
export { FileUploadDropzone } from './primitives/file-upload';
|
|
45
|
+
export type { FileUploadDropzoneProps } from './primitives/file-upload';
|
|
46
|
+
|
|
47
|
+
// Layout
|
|
48
|
+
export { Card } from './layout/card';
|
|
49
|
+
export type { CardProps, CardHeaderProps, CardContentProps, CardActionsProps } from './layout/card';
|
|
50
|
+
|
|
51
|
+
export { Sidebar, SidebarContext, useSidebar } from './layout/sidebar';
|
|
52
|
+
export type { SidebarProps } from './layout/sidebar';
|
|
53
|
+
|
|
54
|
+
export { TopBar } from './layout/top-bar';
|
|
55
|
+
export type { TopBarProps } from './layout/top-bar';
|
|
56
|
+
|
|
57
|
+
export { CommandPalette } from './layout/command-palette';
|
|
58
|
+
export type { CommandPaletteProps, CommandItem } from './layout/command-palette';
|
|
59
|
+
|
|
60
|
+
// Navigation
|
|
61
|
+
export { SidebarNavItem } from './navigation/sidebar-nav-item';
|
|
62
|
+
export type { SidebarNavItemProps } from './navigation/sidebar-nav-item';
|
|
63
|
+
|
|
64
|
+
export { Tabs } from './navigation/tabs';
|
|
65
|
+
export type { TabsProps, TabsListProps, TabsTriggerProps, TabsPanelProps, TabsVariant } from './navigation/tabs';
|
|
66
|
+
|
|
67
|
+
export { Breadcrumbs } from './navigation/breadcrumbs';
|
|
68
|
+
export type { BreadcrumbsProps, BreadcrumbItem } from './navigation/breadcrumbs';
|
|
69
|
+
|
|
70
|
+
// Data Display
|
|
71
|
+
export { StatCard } from './data-display/stat-card';
|
|
72
|
+
export type { StatCardProps } from './data-display/stat-card';
|
|
73
|
+
|
|
74
|
+
export { ProgressBar } from './data-display/progress-bar';
|
|
75
|
+
export type { ProgressBarProps } from './data-display/progress-bar';
|
|
76
|
+
|
|
77
|
+
export { CircularProgress } from './data-display/circular-progress';
|
|
78
|
+
export type { CircularProgressProps } from './data-display/circular-progress';
|
|
79
|
+
|
|
80
|
+
export { Table } from './data-display/table';
|
|
81
|
+
|
|
82
|
+
export { Timeline } from './data-display/timeline';
|
|
83
|
+
export type { TimelineProps, TimelineItem, TimelineColor } from './data-display/timeline';
|
|
84
|
+
|
|
85
|
+
export { ActivityFeed } from './data-display/activity-feed';
|
|
86
|
+
export type { ActivityFeedProps, ActivityItem } from './data-display/activity-feed';
|
|
87
|
+
|
|
88
|
+
export { EmptyState } from './data-display/empty-state';
|
|
89
|
+
export type { EmptyStateProps } from './data-display/empty-state';
|
|
90
|
+
|
|
91
|
+
// Feedback
|
|
92
|
+
export { ToastProvider, useToast } from './feedback/toast';
|
|
93
|
+
export type { ToastData, ToastVariant } from './feedback/toast';
|
|
94
|
+
|
|
95
|
+
export { Modal } from './feedback/modal';
|
|
96
|
+
export type { ModalProps } from './feedback/modal';
|
|
97
|
+
|
|
98
|
+
export { AlertBanner } from './feedback/alert-banner';
|
|
99
|
+
export type { AlertBannerProps, AlertBannerVariant } from './feedback/alert-banner';
|
|
100
|
+
|
|
101
|
+
export { SkeletonLoader } from './feedback/skeleton-loader';
|
|
102
|
+
export type { SkeletonLoaderProps, SkeletonShape } from './feedback/skeleton-loader';
|
|
103
|
+
|
|
104
|
+
// Patterns
|
|
105
|
+
export { Accordion } from './patterns/accordion';
|
|
106
|
+
export type { AccordionProps, AccordionItemProps, AccordionTriggerProps, AccordionContentProps } from './patterns/accordion';
|
|
107
|
+
|
|
108
|
+
export { ActionMenu } from './patterns/action-menu';
|
|
109
|
+
export type { ActionMenuProps, ActionMenuItem } from './patterns/action-menu';
|
|
110
|
+
|
|
111
|
+
export { Pagination } from './patterns/pagination';
|
|
112
|
+
export type { PaginationProps } from './patterns/pagination';
|
|
113
|
+
|
|
114
|
+
export { NotificationDot } from './patterns/notification-dot';
|
|
115
|
+
export type { NotificationDotProps } from './patterns/notification-dot';
|
|
116
|
+
|
|
117
|
+
export { Carousel } from './patterns/carousel';
|
|
118
|
+
export type { CarouselProps } from './patterns/carousel';
|
|
119
|
+
|
|
120
|
+
export { ImagePlaceholder } from './patterns/image-placeholder';
|
|
121
|
+
export type { ImagePlaceholderProps } from './patterns/image-placeholder';
|
|
122
|
+
|
|
123
|
+
// Productivity
|
|
124
|
+
export { TaskCard } from './productivity/task-card';
|
|
125
|
+
export type { TaskCardProps } from './productivity/task-card';
|
|
126
|
+
|
|
127
|
+
export { KanbanColumn, SortableTaskCard } from './productivity/kanban-column';
|
|
128
|
+
export type { KanbanColumnProps, SortableTaskCardProps } from './productivity/kanban-column';
|
|
129
|
+
|
|
130
|
+
export { KanbanBoard } from './productivity/kanban-board';
|
|
131
|
+
export type { KanbanBoardProps } from './productivity/kanban-board';
|
|
132
|
+
|
|
133
|
+
export { PrioritySelector } from './productivity/priority-selector';
|
|
134
|
+
export type { PrioritySelectorProps, Priority } from './productivity/priority-selector';
|
|
135
|
+
|
|
136
|
+
export { CommentThread } from './productivity/comment-thread';
|
|
137
|
+
export type { CommentThreadProps, Comment } from './productivity/comment-thread';
|
|
138
|
+
|
|
139
|
+
export { RichTextToolbar } from './productivity/rich-text-toolbar';
|
|
140
|
+
export type { RichTextToolbarProps } from './productivity/rich-text-toolbar';
|
|
141
|
+
|
|
142
|
+
// Hooks
|
|
143
|
+
export { useReducedMotion } from './hooks/use-reduced-motion';
|
|
144
|
+
export { useControllableState } from './hooks/use-controllable-state';
|
|
145
|
+
export { useFocusTrap } from './hooks/use-focus-trap';
|
|
146
|
+
|
|
147
|
+
// Utils
|
|
148
|
+
export { cn } from './utils/cn';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Card } from './Card';
|
|
3
|
+
import { Button } from '../../primitives/button';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Card> = {
|
|
6
|
+
title: 'Layout/Card',
|
|
7
|
+
component: Card,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof Card>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<Card className="max-w-md">
|
|
17
|
+
<Card.Header>
|
|
18
|
+
<h3 className="text-text-primary font-semibold" style={{ fontSize: 'var(--text-h3)' }}>
|
|
19
|
+
Project Settings
|
|
20
|
+
</h3>
|
|
21
|
+
</Card.Header>
|
|
22
|
+
<Card.Content>
|
|
23
|
+
<p className="text-text-secondary" style={{ fontSize: 'var(--text-body)' }}>
|
|
24
|
+
Configure your project settings and preferences. Changes will be saved automatically.
|
|
25
|
+
</p>
|
|
26
|
+
</Card.Content>
|
|
27
|
+
<Card.Actions>
|
|
28
|
+
<Button variant="ghost">Cancel</Button>
|
|
29
|
+
<Button>Save Changes</Button>
|
|
30
|
+
</Card.Actions>
|
|
31
|
+
</Card>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Simple: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<Card className="max-w-sm">
|
|
38
|
+
<Card.Content>
|
|
39
|
+
<p className="text-text-primary" style={{ fontSize: 'var(--text-body)' }}>
|
|
40
|
+
A simple card with just content — no header or footer.
|
|
41
|
+
</p>
|
|
42
|
+
</Card.Content>
|
|
43
|
+
</Card>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { Card } from './Card';
|
|
4
|
+
|
|
5
|
+
describe('Card', () => {
|
|
6
|
+
it('renders compound structure', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Card>
|
|
9
|
+
<Card.Header>Header</Card.Header>
|
|
10
|
+
<Card.Content>Content</Card.Content>
|
|
11
|
+
<Card.Actions>Actions</Card.Actions>
|
|
12
|
+
</Card>,
|
|
13
|
+
);
|
|
14
|
+
expect(screen.getByText('Header')).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByText('Content')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText('Actions')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('applies custom className', () => {
|
|
20
|
+
const { container } = render(<Card className="max-w-md">Content</Card>);
|
|
21
|
+
expect(container.firstChild).toHaveClass('max-w-md');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { cn } from '../../utils/cn';
|
|
3
|
+
import type { CardProps, CardHeaderProps, CardContentProps, CardActionsProps } from './Card.types';
|
|
4
|
+
|
|
5
|
+
const CardRoot = forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => (
|
|
6
|
+
<div
|
|
7
|
+
ref={ref}
|
|
8
|
+
className={cn(
|
|
9
|
+
'rounded-lg bg-surface shadow-1 overflow-hidden',
|
|
10
|
+
'transition-all hover:shadow-2 hover:-translate-y-0.5',
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
style={{ transitionDuration: 'var(--duration-fast)', transitionTimingFunction: 'var(--ease-out)' }}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
));
|
|
17
|
+
CardRoot.displayName = 'Card';
|
|
18
|
+
|
|
19
|
+
const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(({ className, ...props }, ref) => (
|
|
20
|
+
<div ref={ref} className={cn('px-6 py-4 border-b border-border', className)} {...props} />
|
|
21
|
+
));
|
|
22
|
+
CardHeader.displayName = 'Card.Header';
|
|
23
|
+
|
|
24
|
+
const CardContent = forwardRef<HTMLDivElement, CardContentProps>(({ className, ...props }, ref) => (
|
|
25
|
+
<div ref={ref} className={cn('px-6 py-4', className)} {...props} />
|
|
26
|
+
));
|
|
27
|
+
CardContent.displayName = 'Card.Content';
|
|
28
|
+
|
|
29
|
+
const CardActions = forwardRef<HTMLDivElement, CardActionsProps>(({ className, ...props }, ref) => (
|
|
30
|
+
<div
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn('px-6 py-4 border-t border-border flex justify-end gap-3', className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
));
|
|
36
|
+
CardActions.displayName = 'Card.Actions';
|
|
37
|
+
|
|
38
|
+
export const Card = Object.assign(CardRoot, {
|
|
39
|
+
Header: CardHeader,
|
|
40
|
+
Content: CardContent,
|
|
41
|
+
Actions: CardActions,
|
|
42
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CardProps extends ComponentPropsWithoutRef<'div'> {}
|
|
4
|
+
export interface CardHeaderProps extends ComponentPropsWithoutRef<'div'> {}
|
|
5
|
+
export interface CardContentProps extends ComponentPropsWithoutRef<'div'> {}
|
|
6
|
+
export interface CardActionsProps extends ComponentPropsWithoutRef<'div'> {}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Home, Settings, Users, FileText, Plus } from 'lucide-react';
|
|
4
|
+
import { CommandPalette } from './CommandPalette';
|
|
5
|
+
import { Button } from '../../primitives/button';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof CommandPalette> = {
|
|
8
|
+
title: 'Layout/CommandPalette',
|
|
9
|
+
component: CommandPalette,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof CommandPalette>;
|
|
15
|
+
|
|
16
|
+
const items = [
|
|
17
|
+
{ id: '1', label: 'Go to Dashboard', icon: <Home size={16} />, section: 'Navigation', onSelect: () => {} },
|
|
18
|
+
{ id: '2', label: 'Go to Settings', icon: <Settings size={16} />, section: 'Navigation', onSelect: () => {} },
|
|
19
|
+
{ id: '3', label: 'Go to Team', icon: <Users size={16} />, section: 'Navigation', onSelect: () => {} },
|
|
20
|
+
{ id: '4', label: 'Create new task', icon: <Plus size={16} />, section: 'Actions', onSelect: () => {} },
|
|
21
|
+
{ id: '5', label: 'Create new document', icon: <FileText size={16} />, section: 'Actions', onSelect: () => {} },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => {
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Button onClick={() => setOpen(true)}>Open Command Palette (⌘K)</Button>
|
|
30
|
+
<CommandPalette open={open} onOpenChange={setOpen} items={items} />
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
};
|