@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.
Files changed (230) hide show
  1. package/dist/index.cjs +7729 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +677 -0
  4. package/dist/index.d.ts +677 -0
  5. package/dist/index.js +7654 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/theme.css +2 -0
  8. package/dist/tokens.cjs +78 -0
  9. package/dist/tokens.cjs.map +1 -0
  10. package/dist/tokens.d.cts +67 -0
  11. package/dist/tokens.d.ts +67 -0
  12. package/dist/tokens.js +70 -0
  13. package/dist/tokens.js.map +1 -0
  14. package/package.json +103 -0
  15. package/src/data-display/activity-feed/ActivityFeed.stories.tsx +16 -0
  16. package/src/data-display/activity-feed/ActivityFeed.test.tsx +11 -0
  17. package/src/data-display/activity-feed/ActivityFeed.tsx +34 -0
  18. package/src/data-display/activity-feed/index.ts +2 -0
  19. package/src/data-display/circular-progress/CircularProgress.stories.tsx +10 -0
  20. package/src/data-display/circular-progress/CircularProgress.test.tsx +11 -0
  21. package/src/data-display/circular-progress/CircularProgress.tsx +35 -0
  22. package/src/data-display/circular-progress/index.ts +2 -0
  23. package/src/data-display/empty-state/EmptyState.stories.tsx +15 -0
  24. package/src/data-display/empty-state/EmptyState.test.tsx +18 -0
  25. package/src/data-display/empty-state/EmptyState.tsx +34 -0
  26. package/src/data-display/empty-state/index.ts +2 -0
  27. package/src/data-display/progress-bar/ProgressBar.stories.tsx +10 -0
  28. package/src/data-display/progress-bar/ProgressBar.test.tsx +15 -0
  29. package/src/data-display/progress-bar/ProgressBar.tsx +38 -0
  30. package/src/data-display/progress-bar/index.ts +2 -0
  31. package/src/data-display/stat-card/StatCard.stories.tsx +10 -0
  32. package/src/data-display/stat-card/StatCard.test.tsx +15 -0
  33. package/src/data-display/stat-card/StatCard.tsx +40 -0
  34. package/src/data-display/stat-card/index.ts +2 -0
  35. package/src/data-display/table/Table.stories.tsx +44 -0
  36. package/src/data-display/table/Table.test.tsx +16 -0
  37. package/src/data-display/table/Table.tsx +71 -0
  38. package/src/data-display/table/index.ts +1 -0
  39. package/src/data-display/timeline/Timeline.stories.tsx +16 -0
  40. package/src/data-display/timeline/Timeline.test.tsx +11 -0
  41. package/src/data-display/timeline/Timeline.tsx +44 -0
  42. package/src/data-display/timeline/index.ts +2 -0
  43. package/src/docs/ComponentOverview.mdx +192 -0
  44. package/src/docs/DesignTokens.mdx +235 -0
  45. package/src/docs/GettingStarted.mdx +145 -0
  46. package/src/feedback/alert-banner/AlertBanner.stories.tsx +10 -0
  47. package/src/feedback/alert-banner/AlertBanner.test.tsx +16 -0
  48. package/src/feedback/alert-banner/AlertBanner.tsx +47 -0
  49. package/src/feedback/alert-banner/index.ts +2 -0
  50. package/src/feedback/modal/Modal.stories.tsx +31 -0
  51. package/src/feedback/modal/Modal.test.tsx +33 -0
  52. package/src/feedback/modal/Modal.tsx +88 -0
  53. package/src/feedback/modal/index.ts +2 -0
  54. package/src/feedback/skeleton-loader/SkeletonLoader.stories.tsx +23 -0
  55. package/src/feedback/skeleton-loader/SkeletonLoader.test.tsx +14 -0
  56. package/src/feedback/skeleton-loader/SkeletonLoader.tsx +61 -0
  57. package/src/feedback/skeleton-loader/index.ts +2 -0
  58. package/src/feedback/toast/Toast.stories.tsx +27 -0
  59. package/src/feedback/toast/Toast.test.tsx +32 -0
  60. package/src/feedback/toast/Toast.tsx +106 -0
  61. package/src/feedback/toast/index.ts +2 -0
  62. package/src/hooks/index.ts +3 -0
  63. package/src/hooks/use-controllable-state.ts +34 -0
  64. package/src/hooks/use-focus-trap.ts +56 -0
  65. package/src/hooks/use-reduced-motion.ts +17 -0
  66. package/src/index.ts +148 -0
  67. package/src/layout/card/Card.stories.tsx +45 -0
  68. package/src/layout/card/Card.test.tsx +23 -0
  69. package/src/layout/card/Card.tsx +42 -0
  70. package/src/layout/card/Card.types.ts +6 -0
  71. package/src/layout/card/index.ts +2 -0
  72. package/src/layout/command-palette/CommandPalette.stories.tsx +34 -0
  73. package/src/layout/command-palette/CommandPalette.test.tsx +43 -0
  74. package/src/layout/command-palette/CommandPalette.tsx +188 -0
  75. package/src/layout/command-palette/CommandPalette.types.ts +18 -0
  76. package/src/layout/command-palette/index.ts +2 -0
  77. package/src/layout/sidebar/Sidebar.stories.tsx +60 -0
  78. package/src/layout/sidebar/Sidebar.test.tsx +27 -0
  79. package/src/layout/sidebar/Sidebar.tsx +57 -0
  80. package/src/layout/sidebar/Sidebar.types.ts +14 -0
  81. package/src/layout/sidebar/index.ts +2 -0
  82. package/src/layout/top-bar/TopBar.stories.tsx +51 -0
  83. package/src/layout/top-bar/TopBar.test.tsx +18 -0
  84. package/src/layout/top-bar/TopBar.tsx +19 -0
  85. package/src/layout/top-bar/TopBar.types.ts +10 -0
  86. package/src/layout/top-bar/index.ts +2 -0
  87. package/src/navigation/breadcrumbs/Breadcrumbs.stories.tsx +30 -0
  88. package/src/navigation/breadcrumbs/Breadcrumbs.test.tsx +43 -0
  89. package/src/navigation/breadcrumbs/Breadcrumbs.tsx +45 -0
  90. package/src/navigation/breadcrumbs/Breadcrumbs.types.ts +12 -0
  91. package/src/navigation/breadcrumbs/index.ts +2 -0
  92. package/src/navigation/sidebar-nav-item/SidebarNavItem.stories.tsx +41 -0
  93. package/src/navigation/sidebar-nav-item/SidebarNavItem.test.tsx +46 -0
  94. package/src/navigation/sidebar-nav-item/SidebarNavItem.tsx +38 -0
  95. package/src/navigation/sidebar-nav-item/SidebarNavItem.types.ts +12 -0
  96. package/src/navigation/sidebar-nav-item/index.ts +2 -0
  97. package/src/navigation/tabs/Tabs.stories.tsx +47 -0
  98. package/src/navigation/tabs/Tabs.test.tsx +67 -0
  99. package/src/navigation/tabs/Tabs.tsx +111 -0
  100. package/src/navigation/tabs/Tabs.types.ts +36 -0
  101. package/src/navigation/tabs/index.ts +2 -0
  102. package/src/patterns/accordion/Accordion.stories.tsx +25 -0
  103. package/src/patterns/accordion/Accordion.test.tsx +44 -0
  104. package/src/patterns/accordion/Accordion.tsx +92 -0
  105. package/src/patterns/accordion/index.ts +2 -0
  106. package/src/patterns/action-menu/ActionMenu.stories.tsx +18 -0
  107. package/src/patterns/action-menu/ActionMenu.test.tsx +18 -0
  108. package/src/patterns/action-menu/ActionMenu.tsx +41 -0
  109. package/src/patterns/action-menu/index.ts +2 -0
  110. package/src/patterns/carousel/Carousel.stories.tsx +16 -0
  111. package/src/patterns/carousel/Carousel.test.tsx +16 -0
  112. package/src/patterns/carousel/Carousel.tsx +69 -0
  113. package/src/patterns/carousel/index.ts +2 -0
  114. package/src/patterns/image-placeholder/ImagePlaceholder.stories.tsx +9 -0
  115. package/src/patterns/image-placeholder/ImagePlaceholder.test.tsx +10 -0
  116. package/src/patterns/image-placeholder/ImagePlaceholder.tsx +21 -0
  117. package/src/patterns/image-placeholder/index.ts +2 -0
  118. package/src/patterns/notification-dot/NotificationDot.stories.tsx +17 -0
  119. package/src/patterns/notification-dot/NotificationDot.test.tsx +14 -0
  120. package/src/patterns/notification-dot/NotificationDot.tsx +18 -0
  121. package/src/patterns/notification-dot/index.ts +2 -0
  122. package/src/patterns/pagination/Pagination.stories.tsx +14 -0
  123. package/src/patterns/pagination/Pagination.test.tsx +22 -0
  124. package/src/patterns/pagination/Pagination.tsx +67 -0
  125. package/src/patterns/pagination/index.ts +2 -0
  126. package/src/primitives/avatar/Avatar.stories.tsx +46 -0
  127. package/src/primitives/avatar/Avatar.test.tsx +35 -0
  128. package/src/primitives/avatar/Avatar.tsx +49 -0
  129. package/src/primitives/avatar/Avatar.types.ts +21 -0
  130. package/src/primitives/avatar/AvatarGroup.tsx +27 -0
  131. package/src/primitives/avatar/index.ts +3 -0
  132. package/src/primitives/badge/Badge.stories.tsx +28 -0
  133. package/src/primitives/badge/Badge.test.tsx +23 -0
  134. package/src/primitives/badge/Badge.tsx +44 -0
  135. package/src/primitives/badge/Badge.types.ts +14 -0
  136. package/src/primitives/badge/index.ts +2 -0
  137. package/src/primitives/button/Button.stories.tsx +81 -0
  138. package/src/primitives/button/Button.test.tsx +64 -0
  139. package/src/primitives/button/Button.tsx +85 -0
  140. package/src/primitives/button/Button.types.ts +17 -0
  141. package/src/primitives/button/index.ts +2 -0
  142. package/src/primitives/checkbox/Checkbox.stories.tsx +27 -0
  143. package/src/primitives/checkbox/Checkbox.test.tsx +30 -0
  144. package/src/primitives/checkbox/Checkbox.tsx +79 -0
  145. package/src/primitives/checkbox/Checkbox.types.ts +12 -0
  146. package/src/primitives/checkbox/index.ts +2 -0
  147. package/src/primitives/combobox/Combobox.stories.tsx +44 -0
  148. package/src/primitives/combobox/Combobox.test.tsx +44 -0
  149. package/src/primitives/combobox/Combobox.tsx +201 -0
  150. package/src/primitives/combobox/Combobox.types.ts +25 -0
  151. package/src/primitives/combobox/index.ts +2 -0
  152. package/src/primitives/date-input/DateInput.stories.tsx +23 -0
  153. package/src/primitives/date-input/DateInput.test.tsx +22 -0
  154. package/src/primitives/date-input/DateInput.tsx +66 -0
  155. package/src/primitives/date-input/DateInput.types.ts +10 -0
  156. package/src/primitives/date-input/index.ts +2 -0
  157. package/src/primitives/file-upload/FileUploadDropzone.stories.tsx +27 -0
  158. package/src/primitives/file-upload/FileUploadDropzone.test.tsx +26 -0
  159. package/src/primitives/file-upload/FileUploadDropzone.tsx +99 -0
  160. package/src/primitives/file-upload/FileUploadDropzone.types.ts +14 -0
  161. package/src/primitives/file-upload/index.ts +2 -0
  162. package/src/primitives/input/InputGroup.stories.tsx +31 -0
  163. package/src/primitives/input/InputGroup.test.tsx +40 -0
  164. package/src/primitives/input/InputGroup.tsx +65 -0
  165. package/src/primitives/input/InputGroup.types.ts +12 -0
  166. package/src/primitives/input/index.ts +2 -0
  167. package/src/primitives/link/Link.stories.tsx +28 -0
  168. package/src/primitives/link/Link.test.tsx +23 -0
  169. package/src/primitives/link/Link.tsx +28 -0
  170. package/src/primitives/link/Link.types.ts +8 -0
  171. package/src/primitives/link/index.ts +2 -0
  172. package/src/primitives/radio/Radio.stories.tsx +29 -0
  173. package/src/primitives/radio/Radio.test.tsx +32 -0
  174. package/src/primitives/radio/Radio.tsx +59 -0
  175. package/src/primitives/radio/Radio.types.ts +6 -0
  176. package/src/primitives/radio/index.ts +2 -0
  177. package/src/primitives/select/SelectGroup.stories.tsx +33 -0
  178. package/src/primitives/select/SelectGroup.test.tsx +34 -0
  179. package/src/primitives/select/SelectGroup.tsx +72 -0
  180. package/src/primitives/select/SelectGroup.types.ts +12 -0
  181. package/src/primitives/select/index.ts +2 -0
  182. package/src/primitives/slider/Slider.stories.tsx +23 -0
  183. package/src/primitives/slider/Slider.test.tsx +28 -0
  184. package/src/primitives/slider/Slider.tsx +80 -0
  185. package/src/primitives/slider/Slider.types.ts +22 -0
  186. package/src/primitives/slider/index.ts +2 -0
  187. package/src/primitives/textarea/TextareaGroup.stories.tsx +27 -0
  188. package/src/primitives/textarea/TextareaGroup.test.tsx +24 -0
  189. package/src/primitives/textarea/TextareaGroup.tsx +59 -0
  190. package/src/primitives/textarea/TextareaGroup.types.ts +10 -0
  191. package/src/primitives/textarea/index.ts +2 -0
  192. package/src/primitives/toggle/Toggle.stories.tsx +27 -0
  193. package/src/primitives/toggle/Toggle.test.tsx +31 -0
  194. package/src/primitives/toggle/Toggle.tsx +65 -0
  195. package/src/primitives/toggle/Toggle.types.ts +16 -0
  196. package/src/primitives/toggle/index.ts +2 -0
  197. package/src/primitives/tooltip/Tooltip.stories.tsx +45 -0
  198. package/src/primitives/tooltip/Tooltip.test.tsx +28 -0
  199. package/src/primitives/tooltip/Tooltip.tsx +94 -0
  200. package/src/primitives/tooltip/Tooltip.types.ts +16 -0
  201. package/src/primitives/tooltip/index.ts +2 -0
  202. package/src/productivity/comment-thread/CommentThread.stories.tsx +20 -0
  203. package/src/productivity/comment-thread/CommentThread.test.tsx +21 -0
  204. package/src/productivity/comment-thread/CommentThread.tsx +47 -0
  205. package/src/productivity/comment-thread/index.ts +2 -0
  206. package/src/productivity/kanban-board/KanbanBoard.tsx +41 -0
  207. package/src/productivity/kanban-board/index.ts +2 -0
  208. package/src/productivity/kanban-column/KanbanColumn.stories.tsx +131 -0
  209. package/src/productivity/kanban-column/KanbanColumn.test.tsx +18 -0
  210. package/src/productivity/kanban-column/KanbanColumn.tsx +58 -0
  211. package/src/productivity/kanban-column/SortableTaskCard.tsx +46 -0
  212. package/src/productivity/kanban-column/index.ts +4 -0
  213. package/src/productivity/priority-selector/PrioritySelector.stories.tsx +14 -0
  214. package/src/productivity/priority-selector/PrioritySelector.test.tsx +18 -0
  215. package/src/productivity/priority-selector/PrioritySelector.tsx +40 -0
  216. package/src/productivity/priority-selector/index.ts +2 -0
  217. package/src/productivity/rich-text-toolbar/RichTextToolbar.stories.tsx +19 -0
  218. package/src/productivity/rich-text-toolbar/RichTextToolbar.test.tsx +21 -0
  219. package/src/productivity/rich-text-toolbar/RichTextToolbar.tsx +50 -0
  220. package/src/productivity/rich-text-toolbar/index.ts +2 -0
  221. package/src/productivity/task-card/TaskCard.stories.tsx +20 -0
  222. package/src/productivity/task-card/TaskCard.test.tsx +21 -0
  223. package/src/productivity/task-card/TaskCard.tsx +76 -0
  224. package/src/productivity/task-card/index.ts +2 -0
  225. package/src/test-setup.ts +1 -0
  226. package/src/tokens/index.ts +1 -0
  227. package/src/tokens/tokens.ts +71 -0
  228. package/src/tokens/weave-theme.css +168 -0
  229. package/src/utils/cn.ts +6 -0
  230. package/src/utils/index.ts +1 -0
@@ -0,0 +1,34 @@
1
+ import { cn } from '../../utils/cn';
2
+ import { Button } from '../../primitives/button';
3
+
4
+ export interface EmptyStateProps {
5
+ icon: React.ReactNode;
6
+ title: string;
7
+ description?: string;
8
+ actionLabel?: string;
9
+ onAction?: () => void;
10
+ className?: string;
11
+ }
12
+
13
+ export function EmptyState({ icon, title, description, actionLabel, onAction, className }: EmptyStateProps) {
14
+ return (
15
+ <div className={cn('flex flex-col items-center justify-center py-16 px-6 text-center', className)}>
16
+ <div className="w-16 h-16 rounded-full bg-surface flex items-center justify-center text-text-secondary mb-4">
17
+ {icon}
18
+ </div>
19
+ <h3 className="text-text-primary font-semibold" style={{ fontSize: 'var(--text-h3)', lineHeight: 'var(--leading-h3)' }}>
20
+ {title}
21
+ </h3>
22
+ {description && (
23
+ <p className="text-text-secondary mt-2 max-w-sm" style={{ fontSize: 'var(--text-body)', lineHeight: 'var(--leading-body)' }}>
24
+ {description}
25
+ </p>
26
+ )}
27
+ {actionLabel && onAction && (
28
+ <Button onClick={onAction} className="mt-6">
29
+ {actionLabel}
30
+ </Button>
31
+ )}
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,2 @@
1
+ export { EmptyState } from './EmptyState';
2
+ export type { EmptyStateProps } from './EmptyState';
@@ -0,0 +1,10 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ProgressBar } from './ProgressBar';
3
+
4
+ const meta: Meta<typeof ProgressBar> = { title: 'Data Display/ProgressBar', component: ProgressBar, tags: ['autodocs'] };
5
+ export default meta;
6
+ type Story = StoryObj<typeof ProgressBar>;
7
+
8
+ export const Default: Story = { args: { label: 'Project Progress', value: 65 } };
9
+ export const Full: Story = { args: { label: 'Upload Complete', value: 100 } };
10
+ export const Small: Story = { args: { label: 'Getting Started', value: 15 } };
@@ -0,0 +1,15 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { ProgressBar } from './ProgressBar';
4
+
5
+ describe('ProgressBar', () => {
6
+ it('renders progressbar role', () => {
7
+ render(<ProgressBar value={50} />);
8
+ expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50');
9
+ });
10
+ it('shows label and percentage', () => {
11
+ render(<ProgressBar label="Progress" value={75} />);
12
+ expect(screen.getByText('Progress')).toBeInTheDocument();
13
+ expect(screen.getByText('75%')).toBeInTheDocument();
14
+ });
15
+ });
@@ -0,0 +1,38 @@
1
+ import { cn } from '../../utils/cn';
2
+
3
+ export interface ProgressBarProps {
4
+ label?: string;
5
+ value: number;
6
+ max?: number;
7
+ showValue?: boolean;
8
+ className?: string;
9
+ }
10
+
11
+ export function ProgressBar({ label, value, max = 100, showValue = true, className }: ProgressBarProps) {
12
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
13
+
14
+ return (
15
+ <div className={cn('flex flex-col gap-1.5', className)}>
16
+ {(label || showValue) && (
17
+ <div className="flex justify-between">
18
+ {label && (
19
+ <span className="text-text-primary font-medium" style={{ fontSize: 'var(--text-body-sm)' }}>
20
+ {label}
21
+ </span>
22
+ )}
23
+ {showValue && (
24
+ <span className="text-text-secondary tabular-nums" style={{ fontSize: 'var(--text-body-sm)' }}>
25
+ {Math.round(percent)}%
26
+ </span>
27
+ )}
28
+ </div>
29
+ )}
30
+ <div className="h-2 w-full rounded-full bg-border overflow-hidden" role="progressbar" aria-valuenow={value} aria-valuemin={0} aria-valuemax={max}>
31
+ <div
32
+ className="h-full rounded-full bg-rust transition-all"
33
+ style={{ width: `${percent}%`, transitionDuration: 'var(--duration-normal)' }}
34
+ />
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,2 @@
1
+ export { ProgressBar } from './ProgressBar';
2
+ export type { ProgressBarProps } from './ProgressBar';
@@ -0,0 +1,10 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { StatCard } from './StatCard';
3
+
4
+ const meta: Meta<typeof StatCard> = { title: 'Data Display/StatCard', component: StatCard, tags: ['autodocs'] };
5
+ export default meta;
6
+ type Story = StoryObj<typeof StatCard>;
7
+
8
+ export const Default: Story = { args: { label: 'Total Revenue', value: '$45,231', trend: { value: 12.5, direction: 'up' } } };
9
+ export const Down: Story = { args: { label: 'Bounce Rate', value: '24.5%', trend: { value: 3.2, direction: 'down' } } };
10
+ export const NoTrend: Story = { args: { label: 'Total Users', value: '2,420' } };
@@ -0,0 +1,15 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { StatCard } from './StatCard';
4
+
5
+ describe('StatCard', () => {
6
+ it('renders label and value', () => {
7
+ render(<StatCard label="Revenue" value="$1,000" />);
8
+ expect(screen.getByText('Revenue')).toBeInTheDocument();
9
+ expect(screen.getByText('$1,000')).toBeInTheDocument();
10
+ });
11
+ it('renders trend', () => {
12
+ render(<StatCard label="Users" value="100" trend={{ value: 5, direction: 'up' }} />);
13
+ expect(screen.getByText('5%')).toBeInTheDocument();
14
+ });
15
+ });
@@ -0,0 +1,40 @@
1
+ import { TrendingUp, TrendingDown } from 'lucide-react';
2
+ import { cn } from '../../utils/cn';
3
+
4
+ export interface StatCardProps {
5
+ label: string;
6
+ value: string | number;
7
+ trend?: { value: number; direction: 'up' | 'down' };
8
+ className?: string;
9
+ }
10
+
11
+ export function StatCard({ label, value, trend, className }: StatCardProps) {
12
+ return (
13
+ <div className={cn('rounded-lg bg-surface p-6 shadow-1', className)}>
14
+ <p
15
+ className="text-text-secondary font-medium"
16
+ style={{ fontSize: 'var(--text-body-sm)', lineHeight: 'var(--leading-body-sm)' }}
17
+ >
18
+ {label}
19
+ </p>
20
+ <p
21
+ className="text-text-primary font-bold mt-1"
22
+ style={{ fontSize: 'var(--text-h1)', lineHeight: 'var(--leading-h1)' }}
23
+ >
24
+ {value}
25
+ </p>
26
+ {trend && (
27
+ <div
28
+ className={cn(
29
+ 'flex items-center gap-1 mt-2 font-medium',
30
+ trend.direction === 'up' ? 'text-success' : 'text-error',
31
+ )}
32
+ style={{ fontSize: 'var(--text-body-sm)' }}
33
+ >
34
+ {trend.direction === 'up' ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
35
+ {trend.value}%
36
+ </div>
37
+ )}
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,2 @@
1
+ export { StatCard } from './StatCard';
2
+ export type { StatCardProps } from './StatCard';
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Table } from './Table';
3
+ import { Avatar } from '../../primitives/avatar';
4
+ import { Badge } from '../../primitives/badge';
5
+
6
+ const meta: Meta<typeof Table> = { title: 'Data Display/Table', component: Table, tags: ['autodocs'] };
7
+ export default meta;
8
+ type Story = StoryObj<typeof Table>;
9
+
10
+ export const Default: Story = {
11
+ render: () => (
12
+ <Table>
13
+ <Table.Head>
14
+ <Table.Row>
15
+ <Table.HeaderCell>Member</Table.HeaderCell>
16
+ <Table.HeaderCell>Role</Table.HeaderCell>
17
+ <Table.HeaderCell>Status</Table.HeaderCell>
18
+ </Table.Row>
19
+ </Table.Head>
20
+ <Table.Body>
21
+ <Table.Row>
22
+ <Table.Cell>
23
+ <div className="flex items-center gap-3">
24
+ <Avatar name="Alice Smith" size="sm" />
25
+ <span>Alice Smith</span>
26
+ </div>
27
+ </Table.Cell>
28
+ <Table.Cell>Designer</Table.Cell>
29
+ <Table.Cell><Badge variant="success">Active</Badge></Table.Cell>
30
+ </Table.Row>
31
+ <Table.Row>
32
+ <Table.Cell>
33
+ <div className="flex items-center gap-3">
34
+ <Avatar name="Bob Jones" size="sm" />
35
+ <span>Bob Jones</span>
36
+ </div>
37
+ </Table.Cell>
38
+ <Table.Cell>Engineer</Table.Cell>
39
+ <Table.Cell><Badge variant="warning">Away</Badge></Table.Cell>
40
+ </Table.Row>
41
+ </Table.Body>
42
+ </Table>
43
+ ),
44
+ };
@@ -0,0 +1,16 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { Table } from './Table';
4
+
5
+ describe('Table', () => {
6
+ it('renders table structure', () => {
7
+ render(
8
+ <Table>
9
+ <Table.Head><Table.Row><Table.HeaderCell>Name</Table.HeaderCell></Table.Row></Table.Head>
10
+ <Table.Body><Table.Row><Table.Cell>Alice</Table.Cell></Table.Row></Table.Body>
11
+ </Table>,
12
+ );
13
+ expect(screen.getByText('Name')).toBeInTheDocument();
14
+ expect(screen.getByText('Alice')).toBeInTheDocument();
15
+ });
16
+ });
@@ -0,0 +1,71 @@
1
+ import { forwardRef, type ComponentPropsWithoutRef } from 'react';
2
+ import { cn } from '../../utils/cn';
3
+
4
+ const TableRoot = forwardRef<HTMLTableElement, ComponentPropsWithoutRef<'table'>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div className="w-full overflow-auto">
7
+ <table ref={ref} className={cn('w-full border-collapse', className)} {...props} />
8
+ </div>
9
+ ),
10
+ );
11
+ TableRoot.displayName = 'Table';
12
+
13
+ const TableHead = forwardRef<HTMLTableSectionElement, ComponentPropsWithoutRef<'thead'>>(
14
+ ({ className, ...props }, ref) => (
15
+ <thead ref={ref} className={cn('border-b border-border', className)} {...props} />
16
+ ),
17
+ );
18
+ TableHead.displayName = 'Table.Head';
19
+
20
+ const TableBody = forwardRef<HTMLTableSectionElement, ComponentPropsWithoutRef<'tbody'>>(
21
+ ({ className, ...props }, ref) => (
22
+ <tbody ref={ref} className={cn('[&>tr:last-child]:border-0', className)} {...props} />
23
+ ),
24
+ );
25
+ TableBody.displayName = 'Table.Body';
26
+
27
+ const TableRow = forwardRef<HTMLTableRowElement, ComponentPropsWithoutRef<'tr'>>(
28
+ ({ className, ...props }, ref) => (
29
+ <tr
30
+ ref={ref}
31
+ className={cn('border-b border-border transition-colors hover:bg-surface/50', className)}
32
+ style={{ transitionDuration: 'var(--duration-micro)' }}
33
+ {...props}
34
+ />
35
+ ),
36
+ );
37
+ TableRow.displayName = 'Table.Row';
38
+
39
+ const TableHeaderCell = forwardRef<HTMLTableCellElement, ComponentPropsWithoutRef<'th'>>(
40
+ ({ className, ...props }, ref) => (
41
+ <th
42
+ ref={ref}
43
+ className={cn('px-4 py-3 text-left text-text-secondary font-medium', className)}
44
+ style={{ fontSize: 'var(--text-caption)', lineHeight: 'var(--leading-caption)', letterSpacing: '0.05em', textTransform: 'uppercase' }}
45
+ {...props}
46
+ />
47
+ ),
48
+ );
49
+ TableHeaderCell.displayName = 'Table.HeaderCell';
50
+
51
+ const TableCell = forwardRef<HTMLTableCellElement, ComponentPropsWithoutRef<'td'>>(
52
+ ({ className, ...props }, ref) => (
53
+ <td
54
+ ref={ref}
55
+ className={cn('px-4 py-3 text-text-primary', className)}
56
+ style={{ fontSize: 'var(--text-body-sm)', lineHeight: 'var(--leading-body-sm)' }}
57
+ {...props}
58
+ />
59
+ ),
60
+ );
61
+ TableCell.displayName = 'Table.Cell';
62
+
63
+ export const Table = Object.assign(TableRoot, {
64
+ Head: TableHead,
65
+ Body: TableBody,
66
+ Row: TableRow,
67
+ HeaderCell: TableHeaderCell,
68
+ Cell: TableCell,
69
+ });
70
+
71
+ export type { ComponentPropsWithoutRef };
@@ -0,0 +1 @@
1
+ export { Table } from './Table';
@@ -0,0 +1,16 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Timeline } from './Timeline';
3
+
4
+ const meta: Meta<typeof Timeline> = { title: 'Data Display/Timeline', component: Timeline, tags: ['autodocs'] };
5
+ export default meta;
6
+ type Story = StoryObj<typeof Timeline>;
7
+
8
+ export const Default: Story = {
9
+ args: {
10
+ items: [
11
+ { id: '1', title: 'Task created', description: 'Created by Alice Smith', timestamp: '2 hours ago', color: 'rust' },
12
+ { id: '2', title: 'Assigned to Bob', timestamp: '1 hour ago', color: 'forest' },
13
+ { id: '3', title: 'Status changed to In Progress', timestamp: '30 min ago' },
14
+ ],
15
+ },
16
+ };
@@ -0,0 +1,11 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { Timeline } from './Timeline';
4
+
5
+ describe('Timeline', () => {
6
+ it('renders items', () => {
7
+ render(<Timeline items={[{ id: '1', title: 'Created', timestamp: '1h ago' }]} />);
8
+ expect(screen.getByText('Created')).toBeInTheDocument();
9
+ expect(screen.getByText('1h ago')).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,44 @@
1
+ import { cn } from '../../utils/cn';
2
+
3
+ export type TimelineColor = 'rust' | 'forest' | 'default';
4
+
5
+ export interface TimelineItem {
6
+ id: string;
7
+ title: string;
8
+ description?: string;
9
+ timestamp: string;
10
+ color?: TimelineColor;
11
+ }
12
+
13
+ export interface TimelineProps {
14
+ items: TimelineItem[];
15
+ className?: string;
16
+ }
17
+
18
+ const dotColor: Record<TimelineColor, string> = {
19
+ rust: 'bg-rust',
20
+ forest: 'bg-forest',
21
+ default: 'bg-border',
22
+ };
23
+
24
+ export function Timeline({ items, className }: TimelineProps) {
25
+ return (
26
+ <div className={cn('relative', className)}>
27
+ {items.map((item, i) => (
28
+ <div key={item.id} className="flex gap-4 pb-6 last:pb-0">
29
+ <div className="flex flex-col items-center">
30
+ <div className={cn('w-3 h-3 rounded-full shrink-0 mt-1', dotColor[item.color ?? 'default'])} />
31
+ {i < items.length - 1 && <div className="w-px flex-1 bg-border mt-1" />}
32
+ </div>
33
+ <div className="flex-1 min-w-0">
34
+ <p className="text-text-primary font-medium" style={{ fontSize: 'var(--text-body-sm)' }}>{item.title}</p>
35
+ {item.description && (
36
+ <p className="text-text-secondary mt-0.5" style={{ fontSize: 'var(--text-body-sm)' }}>{item.description}</p>
37
+ )}
38
+ <p className="text-text-secondary mt-1" style={{ fontSize: 'var(--text-caption)' }}>{item.timestamp}</p>
39
+ </div>
40
+ </div>
41
+ ))}
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,2 @@
1
+ export { Timeline } from './Timeline';
2
+ export type { TimelineProps, TimelineItem, TimelineColor } from './Timeline';
@@ -0,0 +1,192 @@
1
+ {/* @jsxImportSource react */}
2
+ import { Meta } from '@storybook/blocks';
3
+
4
+ <Meta title="Component Overview" />
5
+
6
+ # Component Overview
7
+
8
+ Weave ships **45 components** organized in layers — from foundational primitives through layout, navigation, data display, feedback, patterns, and productivity-specific components. Each layer builds on the one below it.
9
+
10
+ ---
11
+
12
+ ## Primitives (16)
13
+
14
+ The building blocks. Every component extends native HTML attributes and supports `ref` forwarding.
15
+
16
+ <DocTable
17
+ headers={['Component', 'Key Props', 'Notes']}
18
+ rows={[
19
+ ['Button', 'variant, size, loading', '5 variants, 3 sizes'],
20
+ ['InputGroup', 'label, error, success', 'Label + input + validation'],
21
+ ['TextareaGroup', 'label, error', 'Resizable multi-line'],
22
+ ['SelectGroup', 'label, placeholder, error', 'Native select with chevron'],
23
+ ['Checkbox', 'checked, onCheckedChange', 'Spring animation on check'],
24
+ ['Radio', 'label, name', 'Native radio group support'],
25
+ ['Toggle', 'checked, onCheckedChange', 'Switch role, spring thumb'],
26
+ ['Badge', 'variant, removable', '6 color variants'],
27
+ ['Avatar', 'name, src, size', 'Initials fallback, 4 sizes'],
28
+ ['AvatarGroup', 'max', 'Stacking with +N overflow'],
29
+ ['Link', 'variant', 'Inline or with external icon'],
30
+ ['Tooltip', 'content, variant, placement', 'Floating UI positioning'],
31
+ ['Slider', 'min, max, step, showValue', 'Range input with track'],
32
+ ['DateInput', 'label, error', 'Native date with calendar icon'],
33
+ ['Combobox', 'options, value, onChange', 'Searchable filtered dropdown'],
34
+ ['FileUploadDropzone', 'onFiles, accept, multiple', 'Drag & drop zone'],
35
+ ]}
36
+ />
37
+
38
+ ---
39
+
40
+ ## Layout (4)
41
+
42
+ Structural containers that define the app shell.
43
+
44
+ <DocTable
45
+ headers={['Component', 'Pattern', 'Notes']}
46
+ rows={[
47
+ ['Card', 'Compound: Card.Header, Card.Content, Card.Actions', 'Rounded surface with shadow'],
48
+ ['Sidebar', 'Context: useSidebar() for collapsed state', '240px expanded, 56px collapsed'],
49
+ ['TopBar', 'Slots: logo, navigation, actions', 'Fixed header bar'],
50
+ ['CommandPalette', 'Controlled: open + onOpenChange', 'Cmd+K, search, keyboard nav'],
51
+ ]}
52
+ />
53
+
54
+ ---
55
+
56
+ ## Navigation (3)
57
+
58
+ <DocTable
59
+ headers={['Component', 'Pattern', 'Notes']}
60
+ rows={[
61
+ ['SidebarNavItem', 'Uses SidebarContext', 'Active state, collapsed icon-only'],
62
+ ['Tabs', 'Compound: Tabs.List, Tabs.Trigger, Tabs.Panel', 'Underline + Pill variants'],
63
+ ['Breadcrumbs', 'Data-driven: items array', 'Last item = current page'],
64
+ ]}
65
+ />
66
+
67
+ ---
68
+
69
+ ## Data Display (7)
70
+
71
+ <DocTable
72
+ headers={['Component', 'Notes']}
73
+ rows={[
74
+ ['StatCard', 'Value + trend indicator with arrow'],
75
+ ['ProgressBar', 'Linear bar with label + percentage'],
76
+ ['CircularProgress', 'SVG donut ring'],
77
+ ['Table', 'Compound: Table.Head, Table.Body, Table.Row, Table.HeaderCell, Table.Cell'],
78
+ ['Timeline', 'Vertical connector with colored dots'],
79
+ ['ActivityFeed', 'Avatar + action + timestamp'],
80
+ ['EmptyState', 'Icon + title + CTA button'],
81
+ ]}
82
+ />
83
+
84
+ ---
85
+
86
+ ## Feedback (4)
87
+
88
+ <DocTable
89
+ headers={['Component', 'Pattern', 'Notes']}
90
+ rows={[
91
+ ['Toast', 'Provider: ToastProvider + useToast() hook', '4 variants, auto-dismiss, stacking'],
92
+ ['Modal', 'Compound: Modal.Header, Modal.Body, Modal.Footer', 'Focus trap, Escape to close'],
93
+ ['AlertBanner', 'Self-dismissing', 'Info, Warning, Error variants'],
94
+ ['SkeletonLoader', 'Shape-based: text, avatar, image', 'Pulse animation'],
95
+ ]}
96
+ />
97
+
98
+ ---
99
+
100
+ ## Patterns (6)
101
+
102
+ <DocTable
103
+ headers={['Component', 'Pattern', 'Notes']}
104
+ rows={[
105
+ ['Accordion', 'Compound: Accordion.Item, Accordion.Trigger, Accordion.Content', 'Multi-open support'],
106
+ ['ActionMenu', 'Data-driven: items array', 'Dividers, destructive items'],
107
+ ['Pagination', 'Controlled: currentPage + onPageChange', 'Ellipsis for large ranges'],
108
+ ['NotificationDot', 'Wrapper component', 'Red 8px dot positioned on child'],
109
+ ['Carousel', 'Children as slides', 'Prev/next + dot indicators'],
110
+ ['ImagePlaceholder', 'Static', 'Configurable size and label'],
111
+ ]}
112
+ />
113
+
114
+ ---
115
+
116
+ ## Productivity (5)
117
+
118
+ Domain-specific components for task management and collaboration.
119
+
120
+ <DocTable
121
+ headers={['Component', 'Notes']}
122
+ rows={[
123
+ ['TaskCard', 'Title, priority dot, labels, assignee, due date'],
124
+ ['KanbanColumn', 'Header with count + add button, card slot'],
125
+ ['PrioritySelector', '4 levels (urgent/high/medium/low) with color dots'],
126
+ ['CommentThread', 'Nested replies, avatar + timestamp'],
127
+ ['RichTextToolbar', 'Format toggle buttons with active state'],
128
+ ]}
129
+ />
130
+
131
+ ---
132
+
133
+ ## Component Patterns
134
+
135
+ ### Compound Components
136
+
137
+ Components like `Card`, `Tabs`, `Modal`, `Accordion`, and `Table` use the compound pattern with `Object.assign`:
138
+
139
+ ```tsx
140
+ <Card>
141
+ <Card.Header>Title</Card.Header>
142
+ <Card.Content>Body</Card.Content>
143
+ <Card.Actions>
144
+ <Button>Save</Button>
145
+ </Card.Actions>
146
+ </Card>
147
+ ```
148
+
149
+ ### Controlled + Uncontrolled
150
+
151
+ Form components support both patterns via `useControllableState`:
152
+
153
+ ```tsx
154
+ // Uncontrolled (manages its own state)
155
+ <Checkbox label="Accept" defaultChecked={false} />
156
+
157
+ // Controlled (you manage state)
158
+ <Checkbox label="Accept" checked={isChecked} onCheckedChange={setIsChecked} />
159
+ ```
160
+
161
+ ### Styling Overrides
162
+
163
+ Every component accepts `className` for Tailwind overrides via `cn()` (clsx + tailwind-merge):
164
+
165
+ ```tsx
166
+ <Button className="w-full rounded-xl">Full Width</Button>
167
+ ```
168
+
169
+ export function DocTable({ headers, rows }) {
170
+ return (
171
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14, marginTop: 12 }}>
172
+ <thead>
173
+ <tr style={{ borderBottom: '2px solid #D9D2C7', textAlign: 'left' }}>
174
+ {headers.map((h, i) => (
175
+ <th key={i} style={{ padding: '8px 12px', color: '#6B635A', fontWeight: 600 }}>{h}</th>
176
+ ))}
177
+ </tr>
178
+ </thead>
179
+ <tbody>
180
+ {rows.map((row, i) => (
181
+ <tr key={i} style={{ borderBottom: '1px solid #D9D2C7' }}>
182
+ {row.map((cell, j) => (
183
+ <td key={j} style={{ padding: '8px 12px', color: j === 0 ? '#2C2825' : '#6B635A', fontWeight: j === 0 ? 600 : 400 }}>
184
+ {j === 0 ? cell : cell}
185
+ </td>
186
+ ))}
187
+ </tr>
188
+ ))}
189
+ </tbody>
190
+ </table>
191
+ );
192
+ }