@structuralists/scaffolding 0.0.1

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 (225) hide show
  1. package/.storybook/main.ts +9 -0
  2. package/.storybook/manager.ts +13 -0
  3. package/.storybook/preview.tsx +18 -0
  4. package/CLAUDE.md +30 -0
  5. package/LICENSE +21 -0
  6. package/README.md +7 -0
  7. package/bun.lock +947 -0
  8. package/bunfig.toml +2 -0
  9. package/eslint.config.mjs +106 -0
  10. package/index.ts +1 -0
  11. package/package.json +50 -0
  12. package/src/components/Chat/ChatComposer/ChatComposer.stories.tsx +68 -0
  13. package/src/components/Chat/ChatComposer/index.tsx +74 -0
  14. package/src/components/Chat/ChatComposer/styles.module.css +88 -0
  15. package/src/components/Chat/ChatComposer/types.ts +11 -0
  16. package/src/components/Chat/ChatMessage/ChatMessage.stories.tsx +111 -0
  17. package/src/components/Chat/ChatMessage/index.tsx +42 -0
  18. package/src/components/Chat/ChatMessage/styles.module.css +58 -0
  19. package/src/components/Chat/ChatMessage/types.ts +14 -0
  20. package/src/components/Chat/ChatRecipientsHeader/ChatRecipientsHeader.stories.tsx +145 -0
  21. package/src/components/Chat/ChatRecipientsHeader/index.tsx +29 -0
  22. package/src/components/Chat/ChatRecipientsHeader/styles.module.css +48 -0
  23. package/src/components/Chat/ChatRecipientsHeader/types.ts +26 -0
  24. package/src/components/Chat/ChatShell/ChatShell.stories.tsx +203 -0
  25. package/src/components/Chat/ChatShell/index.tsx +16 -0
  26. package/src/components/Chat/ChatShell/styles.module.css +27 -0
  27. package/src/components/Chat/ChatShell/types.ts +7 -0
  28. package/src/components/Chat/PillCombobox/PillCombobox.stories.tsx +59 -0
  29. package/src/components/Chat/PillCombobox/index.tsx +17 -0
  30. package/src/components/Chat/PillCombobox/styles.module.css +29 -0
  31. package/src/components/Chat/PillCombobox/types.ts +28 -0
  32. package/src/components/Chat/PillComboboxCore/Core.tsx +235 -0
  33. package/src/components/Chat/PillComboboxCore/styles.module.css +79 -0
  34. package/src/components/Chat/index.ts +12 -0
  35. package/src/components/Content/Badge/Badge.stories.tsx +31 -0
  36. package/src/components/Content/Badge/index.tsx +22 -0
  37. package/src/components/Content/Badge/styles.module.css +25 -0
  38. package/src/components/Content/Badge/types.ts +7 -0
  39. package/src/components/Content/Card/Card.stories.tsx +24 -0
  40. package/src/components/Content/Card/index.tsx +21 -0
  41. package/src/components/Content/Card/styles.module.css +13 -0
  42. package/src/components/Content/Card/types.ts +8 -0
  43. package/src/components/Content/EditableMarkdown/EditableMarkdown.stories.tsx +58 -0
  44. package/src/components/Content/EditableMarkdown/index.tsx +140 -0
  45. package/src/components/Content/EditableMarkdown/styles.module.css +221 -0
  46. package/src/components/Content/EditableMarkdown/types.ts +11 -0
  47. package/src/components/Content/Heading/Heading.stories.tsx +26 -0
  48. package/src/components/Content/Heading/index.tsx +20 -0
  49. package/src/components/Content/Heading/styles.module.css +19 -0
  50. package/src/components/Content/Heading/types.ts +8 -0
  51. package/src/components/Content/Link/Link.stories.tsx +21 -0
  52. package/src/components/Content/Link/index.tsx +19 -0
  53. package/src/components/Content/Link/styles.module.css +11 -0
  54. package/src/components/Content/Link/types.ts +8 -0
  55. package/src/components/Content/List/List.stories.tsx +62 -0
  56. package/src/components/Content/List/index.tsx +26 -0
  57. package/src/components/Content/List/styles.module.css +41 -0
  58. package/src/components/Content/List/types.ts +33 -0
  59. package/src/components/Content/LoadingContainer/LoadingContainer.stories.tsx +105 -0
  60. package/src/components/Content/LoadingContainer/index.tsx +36 -0
  61. package/src/components/Content/LoadingContainer/styles.module.css +54 -0
  62. package/src/components/Content/LoadingContainer/types.ts +8 -0
  63. package/src/components/Content/Markdown/Markdown.stories.tsx +39 -0
  64. package/src/components/Content/Markdown/index.tsx +28 -0
  65. package/src/components/Content/Markdown/styles.module.css +79 -0
  66. package/src/components/Content/Markdown/types.ts +8 -0
  67. package/src/components/Content/Menu/Menu.stories.tsx +186 -0
  68. package/src/components/Content/Menu/index.tsx +259 -0
  69. package/src/components/Content/Menu/styles.module.css +103 -0
  70. package/src/components/Content/Menu/types.ts +25 -0
  71. package/src/components/Content/Text/Text.stories.tsx +36 -0
  72. package/src/components/Content/Text/index.tsx +35 -0
  73. package/src/components/Content/Text/styles.module.css +30 -0
  74. package/src/components/Content/Text/types.ts +11 -0
  75. package/src/components/Forms/Button/Button.stories.tsx +40 -0
  76. package/src/components/Forms/Button/index.tsx +43 -0
  77. package/src/components/Forms/Button/styles.module.css +67 -0
  78. package/src/components/Forms/Button/types.ts +16 -0
  79. package/src/components/Forms/ColorInput/index.tsx +22 -0
  80. package/src/components/Forms/ColorInput/styles.module.css +19 -0
  81. package/src/components/Forms/ColorInput/types.ts +12 -0
  82. package/src/components/Forms/Field/Field.stories.tsx +35 -0
  83. package/src/components/Forms/Field/index.tsx +17 -0
  84. package/src/components/Forms/Field/styles.module.css +21 -0
  85. package/src/components/Forms/Field/types.ts +9 -0
  86. package/src/components/Forms/IconButton/IconButton.stories.tsx +91 -0
  87. package/src/components/Forms/IconButton/index.tsx +55 -0
  88. package/src/components/Forms/IconButton/styles.module.css +61 -0
  89. package/src/components/Forms/IconButton/types.ts +23 -0
  90. package/src/components/Forms/Input/Input.stories.tsx +22 -0
  91. package/src/components/Forms/Input/index.tsx +42 -0
  92. package/src/components/Forms/Input/styles.module.css +30 -0
  93. package/src/components/Forms/Input/types.ts +18 -0
  94. package/src/components/Forms/SearchInput/index.tsx +41 -0
  95. package/src/components/Forms/SearchInput/styles.module.css +30 -0
  96. package/src/components/Forms/SearchInput/types.ts +17 -0
  97. package/src/components/Forms/Select/MultiSelect/MultiSelect.stories.tsx +116 -0
  98. package/src/components/Forms/Select/MultiSelect/index.tsx +74 -0
  99. package/src/components/Forms/Select/MultiSelect/types.ts +15 -0
  100. package/src/components/Forms/Select/SingleSelect/SingleSelect.stories.tsx +174 -0
  101. package/src/components/Forms/Select/SingleSelect/index.tsx +62 -0
  102. package/src/components/Forms/Select/SingleSelect/types.ts +12 -0
  103. package/src/components/Forms/Select/index.ts +4 -0
  104. package/src/components/Forms/Select/internal/OptionList.tsx +124 -0
  105. package/src/components/Forms/Select/internal/SelectTrigger.tsx +60 -0
  106. package/src/components/Forms/Select/internal/styles.module.css +122 -0
  107. package/src/components/Forms/Textarea/Textarea.stories.tsx +25 -0
  108. package/src/components/Forms/Textarea/index.tsx +48 -0
  109. package/src/components/Forms/Textarea/styles.module.css +34 -0
  110. package/src/components/Forms/Textarea/types.ts +24 -0
  111. package/src/components/Json/Json/Json.stories.tsx +33 -0
  112. package/src/components/Json/Json/index.tsx +38 -0
  113. package/src/components/Json/Json/types.ts +21 -0
  114. package/src/components/Json/JsonTable/JsonLeafNode.tsx +31 -0
  115. package/src/components/Json/JsonTable/JsonTable.stories.tsx +52 -0
  116. package/src/components/Json/JsonTable/index.tsx +33 -0
  117. package/src/components/Json/JsonTable/types.ts +13 -0
  118. package/src/components/Json/JsonTable/utils.ts +6 -0
  119. package/src/components/Layout/Bar/Bar.stories.tsx +100 -0
  120. package/src/components/Layout/Bar/index.tsx +17 -0
  121. package/src/components/Layout/Bar/styles.module.css +34 -0
  122. package/src/components/Layout/Bar/types.ts +10 -0
  123. package/src/components/Layout/Debug/Debug.stories.tsx +86 -0
  124. package/src/components/Layout/Debug/index.tsx +41 -0
  125. package/src/components/Layout/Debug/styles.module.css +13 -0
  126. package/src/components/Layout/Debug/types.ts +12 -0
  127. package/src/components/Layout/Divider/Divider.stories.tsx +22 -0
  128. package/src/components/Layout/Divider/index.tsx +3 -0
  129. package/src/components/Layout/Divider/styles.module.css +6 -0
  130. package/src/components/Layout/Grid/Grid.stories.tsx +28 -0
  131. package/src/components/Layout/Grid/index.tsx +29 -0
  132. package/src/components/Layout/Grid/styles.module.css +12 -0
  133. package/src/components/Layout/Grid/types.ts +9 -0
  134. package/src/components/Layout/Panels/Panels.stories.tsx +287 -0
  135. package/src/components/Layout/Panels/index.tsx +119 -0
  136. package/src/components/Layout/Panels/styles.module.css +103 -0
  137. package/src/components/Layout/Panels/types.ts +36 -0
  138. package/src/components/Layout/Stack/Stack.stories.tsx +45 -0
  139. package/src/components/Layout/Stack/index.tsx +73 -0
  140. package/src/components/Layout/Stack/styles.module.css +41 -0
  141. package/src/components/Layout/Stack/types.ts +17 -0
  142. package/src/components/Modals/ConfirmModal/ConfirmModal.stories.tsx +73 -0
  143. package/src/components/Modals/ConfirmModal/index.tsx +72 -0
  144. package/src/components/Modals/ConfirmModal/styles.module.css +62 -0
  145. package/src/components/Modals/ConfirmModal/types.ts +14 -0
  146. package/src/components/Modals/LargeModal/LargeModal.stories.tsx +75 -0
  147. package/src/components/Modals/LargeModal/index.tsx +9 -0
  148. package/src/components/Modals/LargeModal/styles.module.css +6 -0
  149. package/src/components/Modals/LargeModal/types.ts +18 -0
  150. package/src/components/Modals/MediumModal/MediumModal.stories.tsx +121 -0
  151. package/src/components/Modals/MediumModal/MediumModal.test.tsx +48 -0
  152. package/src/components/Modals/MediumModal/index.tsx +9 -0
  153. package/src/components/Modals/MediumModal/styles.module.css +5 -0
  154. package/src/components/Modals/MediumModal/types.ts +18 -0
  155. package/src/components/Modals/index.ts +3 -0
  156. package/src/components/Modals/internal/ModalBody.tsx +21 -0
  157. package/src/components/Modals/internal/ModalFooter.tsx +12 -0
  158. package/src/components/Modals/internal/ModalHeader.tsx +27 -0
  159. package/src/components/Modals/internal/ModalShell.tsx +112 -0
  160. package/src/components/Modals/internal/styles.module.css +141 -0
  161. package/src/components/Navigation/TabBar/TabBar.stories.tsx +59 -0
  162. package/src/components/Navigation/TabBar/index.tsx +25 -0
  163. package/src/components/Navigation/TabBar/styles.module.css +32 -0
  164. package/src/components/Navigation/TabBar/types.ts +22 -0
  165. package/src/components/Navigation/VerticalNav/VerticalNav.stories.tsx +41 -0
  166. package/src/components/Navigation/VerticalNav/index.tsx +25 -0
  167. package/src/components/Navigation/VerticalNav/styles.module.css +28 -0
  168. package/src/components/Navigation/VerticalNav/types.ts +19 -0
  169. package/src/components/Overlays/Popover/Popover.stories.tsx +154 -0
  170. package/src/components/Overlays/Popover/index.tsx +175 -0
  171. package/src/components/Overlays/Popover/styles.module.css +59 -0
  172. package/src/components/Overlays/Popover/types.ts +34 -0
  173. package/src/components/Overlays/Tooltip/Tooltip.stories.tsx +41 -0
  174. package/src/components/Overlays/Tooltip/index.tsx +115 -0
  175. package/src/components/Overlays/Tooltip/styles.module.css +25 -0
  176. package/src/components/Overlays/Tooltip/types.ts +15 -0
  177. package/src/components/Primitives/EmptyValue/EmptyValue.stories.tsx +18 -0
  178. package/src/components/Primitives/EmptyValue/index.tsx +3 -0
  179. package/src/components/Primitives/EmptyValue/styles.module.css +3 -0
  180. package/src/components/Primitives/LinedStack/LinedStack.stories.tsx +101 -0
  181. package/src/components/Primitives/LinedStack/index.tsx +41 -0
  182. package/src/components/Primitives/LinedStack/styles.module.css +27 -0
  183. package/src/components/Primitives/LinedStack/types.ts +49 -0
  184. package/src/components/Primitives/LongText/LongText.stories.tsx +72 -0
  185. package/src/components/Primitives/LongText/index.tsx +67 -0
  186. package/src/components/Primitives/LongText/styles.module.css +30 -0
  187. package/src/components/Primitives/LongText/types.ts +4 -0
  188. package/src/components/Primitives/Num/Num.stories.tsx +51 -0
  189. package/src/components/Primitives/Num/index.tsx +37 -0
  190. package/src/components/Primitives/Num/types.ts +19 -0
  191. package/src/components/Primitives/Percent/Percent.stories.tsx +48 -0
  192. package/src/components/Primitives/Percent/index.tsx +15 -0
  193. package/src/components/Primitives/Percent/types.ts +10 -0
  194. package/src/components/Primitives/RelativeTime/RelativeTime.stories.tsx +57 -0
  195. package/src/components/Primitives/RelativeTime/index.tsx +31 -0
  196. package/src/components/Primitives/RelativeTime/types.ts +3 -0
  197. package/src/components/Tables/BigTable/BigTable.stories.tsx +367 -0
  198. package/src/components/Tables/BigTable/CLAUDE.md +118 -0
  199. package/src/components/Tables/BigTable/columnDefs.tsx +208 -0
  200. package/src/components/Tables/BigTable/index.tsx +104 -0
  201. package/src/components/Tables/BigTable/styles.module.css +83 -0
  202. package/src/components/Tables/BigTable/types.ts +20 -0
  203. package/src/components/Tables/QuickTable/CLAUDE.md +118 -0
  204. package/src/components/Tables/QuickTable/QuickTable.stories.tsx +121 -0
  205. package/src/components/Tables/QuickTable/index.tsx +86 -0
  206. package/src/components/Tables/QuickTable/internal.tsx +48 -0
  207. package/src/components/Tables/QuickTable/styles.module.css +65 -0
  208. package/src/components/Tables/QuickTable/types.ts +40 -0
  209. package/src/env.d.ts +4 -0
  210. package/src/index.ts +87 -0
  211. package/src/storybook/CLAUDE.md +35 -0
  212. package/src/storybook/Composition.stories.tsx +269 -0
  213. package/src/storybook/Lorem/index.tsx +54 -0
  214. package/src/storybook/Placeholder/index.tsx +27 -0
  215. package/src/storybook/Placeholder/styles.module.css +20 -0
  216. package/src/storybook/Repeat/index.tsx +23 -0
  217. package/src/storybook/Toggle/index.tsx +29 -0
  218. package/src/storybook/_StoryUtils.stories.tsx +58 -0
  219. package/src/storybook/index.ts +4 -0
  220. package/src/tokens.ts +31 -0
  221. package/src/utils.test.ts +24 -0
  222. package/src/utils.ts +2 -0
  223. package/test-setup.ts +3 -0
  224. package/tokens.css +323 -0
  225. package/tsconfig.json +16 -0
@@ -0,0 +1,103 @@
1
+ .trigger {
2
+ padding: 2px 8px;
3
+ border: 1px solid transparent;
4
+ border-radius: var(--ui-radius);
5
+ background-color: transparent;
6
+ color: var(--ui-foreground);
7
+ font-family: var(--ui-font);
8
+ font-size: inherit;
9
+ line-height: 1;
10
+ cursor: pointer;
11
+ }
12
+
13
+ .trigger:hover {
14
+ background-color: var(--ui-background-1);
15
+ }
16
+
17
+ .panel {
18
+ min-width: 11rem;
19
+ background-color: var(--ui-background-0);
20
+ border: 1px solid var(--ui-border);
21
+ border-radius: var(--ui-radius);
22
+ box-shadow: 0 2px 8px var(--ui-shadow-soft);
23
+ padding: var(--ui-space-1) 0;
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ /* Root panel is portaled to <body> and positioned in viewport coords by JS.
29
+ * Same z-index tier as every other portal layer (Modal, Popover, Tooltip);
30
+ * stacking between layers is decided by DOM-append order. */
31
+ .panelRoot {
32
+ position: fixed;
33
+ z-index: 100;
34
+ }
35
+
36
+ /* Submenus stay nested inside the root panel and sit relative to their
37
+ * itemWrapper. */
38
+ .panelSubmenu {
39
+ position: absolute;
40
+ }
41
+
42
+ .subOpenRight { left: calc(100% + 2px); }
43
+ .subOpenLeft { right: calc(100% + 2px); }
44
+ .subAlignTop { top: calc(-1 * var(--ui-space-1)); }
45
+ .subAlignBottom { bottom: calc(-1 * var(--ui-space-1)); }
46
+
47
+ .title {
48
+ padding: var(--ui-space-1) var(--ui-space-3);
49
+ border-bottom: 1px solid var(--ui-border-subtle);
50
+ margin-bottom: var(--ui-space-1);
51
+ font-size: var(--ui-text-xsmall);
52
+ color: var(--ui-muted);
53
+ cursor: default;
54
+ user-select: none;
55
+ white-space: nowrap;
56
+ }
57
+
58
+ .itemWrapper {
59
+ position: relative;
60
+ }
61
+
62
+ .item {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: space-between;
66
+ gap: var(--ui-space-3);
67
+ width: 100%;
68
+ padding: var(--ui-space-2) var(--ui-space-3);
69
+ background-color: transparent;
70
+ border: 0;
71
+ color: var(--ui-foreground);
72
+ font-family: var(--ui-font);
73
+ font-size: var(--ui-text-small);
74
+ text-align: left;
75
+ cursor: pointer;
76
+ white-space: nowrap;
77
+ }
78
+
79
+ .item:hover,
80
+ .itemWrapper:hover > .item {
81
+ background-color: var(--ui-background-1);
82
+ }
83
+
84
+ .itemDisabled,
85
+ .itemDisabled:hover {
86
+ color: var(--ui-muted);
87
+ background-color: transparent;
88
+ cursor: default;
89
+ }
90
+
91
+ .branchItem {
92
+ cursor: default;
93
+ }
94
+
95
+ .caret {
96
+ color: var(--ui-muted);
97
+ font-size: var(--ui-text-xsmall);
98
+ }
99
+
100
+ .check {
101
+ color: var(--ui-muted);
102
+ font-size: var(--ui-text-small);
103
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type MenuLeaf = {
4
+ label: string;
5
+ onSelect: () => void;
6
+ /** Renders a trailing checkmark. Use for one-of-N selections. */
7
+ isActive?: boolean;
8
+ /** Dims the item and blocks selection. Hover/keyboard focus still works. */
9
+ isDisabled?: boolean;
10
+ };
11
+
12
+ export type MenuBranch = {
13
+ label: string;
14
+ items: MenuItem[];
15
+ };
16
+
17
+ export type MenuItem = MenuLeaf | MenuBranch;
18
+
19
+ export type MenuProps = {
20
+ trigger: ReactNode;
21
+ ariaLabel: string;
22
+ items: MenuItem[];
23
+ /** Non-interactive heading rendered at the top of the root panel. */
24
+ title?: string;
25
+ };
@@ -0,0 +1,36 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Text } from './index';
3
+
4
+ const meta: Meta<typeof Text> = {
5
+ title: 'Content/Text',
6
+ component: Text,
7
+ argTypes: {
8
+ size: { control: 'inline-radio', options: ['small', 'medium', 'large'] },
9
+ as: { control: 'inline-radio', options: ['p', 'span', 'div'] },
10
+ },
11
+ args: {
12
+ children: 'The quick brown fox jumps over the lazy dog.',
13
+ size: 'medium',
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof Text>;
19
+
20
+ export const Default: Story = {};
21
+
22
+ export const Sizes: Story = {
23
+ render: () => (
24
+ <>
25
+ <Text size="small">Small — The quick brown fox jumps over the lazy dog.</Text>
26
+ <Text size="medium">Medium — The quick brown fox jumps over the lazy dog.</Text>
27
+ <Text size="large">Large — The quick brown fox jumps over the lazy dog.</Text>
28
+ </>
29
+ ),
30
+ };
31
+
32
+ export const Muted: Story = { args: { isMuted: true } };
33
+
34
+ export const Mono: Story = {
35
+ args: { isMono: true, children: 'const answer = 42;' },
36
+ };
@@ -0,0 +1,35 @@
1
+ import type { TextProps, TextSize } from './types';
2
+ import { cx } from '../../../utils';
3
+ import styles from './styles.module.css';
4
+
5
+ const SIZE_MAP: Record<TextSize, string> = {
6
+ xsmall: styles.xsmall,
7
+ small: styles.small,
8
+ medium: styles.medium,
9
+ large: styles.large,
10
+ };
11
+
12
+ export const Text = (props: TextProps) => {
13
+ const {
14
+ children,
15
+ size = 'medium',
16
+ isMuted,
17
+ isMono,
18
+ as: As = 'p',
19
+ } = props;
20
+
21
+ return (
22
+ <As
23
+ className={cx(
24
+ styles.text,
25
+ SIZE_MAP[size],
26
+ isMuted && styles.muted,
27
+ isMono && styles.mono,
28
+ )}
29
+ >
30
+ {children}
31
+ </As>
32
+ );
33
+ };
34
+
35
+ export type { TextProps };
@@ -0,0 +1,30 @@
1
+ .text {
2
+ margin: 0;
3
+ font-family: var(--ui-font);
4
+ line-height: var(--ui-line-height);
5
+ color: var(--ui-foreground);
6
+ }
7
+
8
+ .xsmall {
9
+ font-size: var(--ui-text-xsmall);
10
+ }
11
+
12
+ .small {
13
+ font-size: var(--ui-text-small);
14
+ }
15
+
16
+ .medium {
17
+ font-size: var(--ui-text-medium);
18
+ }
19
+
20
+ .large {
21
+ font-size: var(--ui-text-large);
22
+ }
23
+
24
+ .muted {
25
+ color: var(--ui-muted);
26
+ }
27
+
28
+ .mono {
29
+ font-family: var(--ui-font-mono);
30
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type TextSize = 'xsmall' | 'small' | 'medium' | 'large';
4
+
5
+ export type TextProps = {
6
+ children: ReactNode;
7
+ size?: TextSize;
8
+ isMuted?: boolean;
9
+ isMono?: boolean;
10
+ as?: 'p' | 'span' | 'div';
11
+ };
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Button } from './index';
3
+ import { Stack } from '../../Layout/Stack';
4
+
5
+ const meta: Meta<typeof Button> = {
6
+ title: 'Forms/Button',
7
+ component: Button,
8
+ argTypes: {
9
+ variant: { control: 'inline-radio', options: ['primary', 'secondary', 'ghost', 'danger'] },
10
+ size: { control: 'inline-radio', options: ['small', 'medium'] },
11
+ },
12
+ args: { children: 'Button', variant: 'secondary', size: 'medium' },
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof Button>;
17
+
18
+ export const Default: Story = {};
19
+
20
+ export const Variants: Story = {
21
+ render: () => (
22
+ <Stack direction="row" gap={2}>
23
+ <Button variant="primary">Primary</Button>
24
+ <Button variant="secondary">Secondary</Button>
25
+ <Button variant="ghost">Ghost</Button>
26
+ <Button variant="danger">Danger</Button>
27
+ </Stack>
28
+ ),
29
+ };
30
+
31
+ export const Sizes: Story = {
32
+ render: () => (
33
+ <Stack direction="row" gap={2} align="center">
34
+ <Button size="small">Small</Button>
35
+ <Button size="medium">Medium</Button>
36
+ </Stack>
37
+ ),
38
+ };
39
+
40
+ export const Disabled: Story = { args: { disabled: true } };
@@ -0,0 +1,43 @@
1
+ import type { ButtonProps, ButtonSize, ButtonVariant } from './types';
2
+ import { cx } from '../../../utils';
3
+ import styles from './styles.module.css';
4
+
5
+ const VARIANT_MAP: Record<ButtonVariant, string> = {
6
+ primary: styles.primary,
7
+ secondary: styles.secondary,
8
+ ghost: styles.ghost,
9
+ danger: styles.danger,
10
+ };
11
+
12
+ const SIZE_MAP: Record<ButtonSize, string> = {
13
+ small: styles.small,
14
+ medium: styles.medium,
15
+ };
16
+
17
+ export const Button = (props: ButtonProps) => {
18
+ const {
19
+ children,
20
+ variant = 'secondary',
21
+ size = 'medium',
22
+ type = 'button',
23
+ onClick,
24
+ disabled,
25
+ autoFocus,
26
+ ref,
27
+ } = props;
28
+
29
+ return (
30
+ <button
31
+ ref={ref}
32
+ type={type}
33
+ className={cx(styles.button, VARIANT_MAP[variant], SIZE_MAP[size])}
34
+ onClick={onClick}
35
+ disabled={disabled}
36
+ autoFocus={autoFocus}
37
+ >
38
+ {children}
39
+ </button>
40
+ );
41
+ };
42
+
43
+ export type { ButtonProps };
@@ -0,0 +1,67 @@
1
+ .button {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ font-family: var(--ui-font);
6
+ font-weight: 500;
7
+ line-height: 1;
8
+ border: 1px solid var(--ui-border);
9
+ border-radius: var(--ui-radius);
10
+ cursor: pointer;
11
+ }
12
+
13
+ .button:disabled {
14
+ opacity: 0.5;
15
+ cursor: not-allowed;
16
+ }
17
+
18
+ .small {
19
+ font-size: var(--ui-text-small);
20
+ padding: 4px 8px;
21
+ }
22
+
23
+ .medium {
24
+ font-size: var(--ui-text-medium);
25
+ padding: 6px 12px;
26
+ }
27
+
28
+ .secondary {
29
+ background-color: var(--ui-background-0);
30
+ color: var(--ui-foreground);
31
+ }
32
+
33
+ .secondary:hover:not(:disabled) {
34
+ background-color: var(--ui-background-1);
35
+ }
36
+
37
+ .primary {
38
+ background-color: var(--ui-accent);
39
+ color: var(--ui-on-accent);
40
+ border-color: var(--ui-accent);
41
+ }
42
+
43
+ .primary:hover:not(:disabled) {
44
+ background-color: var(--ui-accent-hover);
45
+ border-color: var(--ui-accent-hover);
46
+ }
47
+
48
+ .ghost {
49
+ background-color: transparent;
50
+ border-color: transparent;
51
+ color: var(--ui-foreground);
52
+ }
53
+
54
+ .ghost:hover:not(:disabled) {
55
+ background-color: var(--ui-background-3);
56
+ }
57
+
58
+ .danger {
59
+ background-color: var(--ui-danger);
60
+ color: var(--ui-on-danger);
61
+ border-color: var(--ui-danger);
62
+ }
63
+
64
+ .danger:hover:not(:disabled) {
65
+ background-color: var(--ui-danger-hover);
66
+ border-color: var(--ui-danger-hover);
67
+ }
@@ -0,0 +1,16 @@
1
+ import type { MouseEventHandler, ReactNode, Ref } from 'react';
2
+
3
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
4
+ export type ButtonSize = 'small' | 'medium';
5
+ export type ButtonType = 'button' | 'submit' | 'reset';
6
+
7
+ export type ButtonProps = {
8
+ children: ReactNode;
9
+ variant?: ButtonVariant;
10
+ size?: ButtonSize;
11
+ type?: ButtonType;
12
+ onClick?: MouseEventHandler<HTMLButtonElement>;
13
+ disabled?: boolean;
14
+ autoFocus?: boolean;
15
+ ref?: Ref<HTMLButtonElement>;
16
+ };
@@ -0,0 +1,22 @@
1
+ import type { ColorInputProps } from './types';
2
+ import styles from './styles.module.css';
3
+
4
+ export const ColorInput = (props: ColorInputProps) => {
5
+ const { label, value, defaultValue, onChange, disabled, id, name } = props;
6
+
7
+ return (
8
+ <input
9
+ type="color"
10
+ aria-label={label}
11
+ className={styles.input}
12
+ value={value}
13
+ defaultValue={defaultValue}
14
+ onChange={onChange}
15
+ disabled={disabled}
16
+ id={id}
17
+ name={name}
18
+ />
19
+ );
20
+ };
21
+
22
+ export type { ColorInputProps };
@@ -0,0 +1,19 @@
1
+ .input {
2
+ border: 1px solid var(--ui-border);
3
+ border-radius: var(--ui-radius);
4
+ background: var(--ui-background-0);
5
+ padding: 2px;
6
+ width: 2.25rem;
7
+ height: 2.25rem;
8
+ cursor: pointer;
9
+ }
10
+
11
+ .input:focus {
12
+ outline: 2px solid var(--ui-accent);
13
+ outline-offset: -1px;
14
+ }
15
+
16
+ .input:disabled {
17
+ opacity: 0.5;
18
+ cursor: not-allowed;
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { ChangeEventHandler } from 'react';
2
+
3
+ export type ColorInputProps = {
4
+ /** Accessible name for the color picker. Becomes the aria-label. */
5
+ label: string;
6
+ value?: string;
7
+ defaultValue?: string;
8
+ onChange?: ChangeEventHandler<HTMLInputElement>;
9
+ disabled?: boolean;
10
+ id?: string;
11
+ name?: string;
12
+ };
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Field } from './index';
3
+ import { Input } from '../Input';
4
+
5
+ const meta: Meta<typeof Field> = {
6
+ title: 'Forms/Field',
7
+ component: Field,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof Field>;
12
+
13
+ export const Default: Story = {
14
+ render: () => (
15
+ <Field label="Name">
16
+ <Input placeholder="Your name" />
17
+ </Field>
18
+ ),
19
+ };
20
+
21
+ export const WithHint: Story = {
22
+ render: () => (
23
+ <Field label="Email" hint="We'll never share it.">
24
+ <Input type="email" placeholder="you@example.com" />
25
+ </Field>
26
+ ),
27
+ };
28
+
29
+ export const WithError: Story = {
30
+ render: () => (
31
+ <Field label="Email" error="That doesn't look like a valid email.">
32
+ <Input type="email" defaultValue="not-an-email" />
33
+ </Field>
34
+ ),
35
+ };
@@ -0,0 +1,17 @@
1
+ import type { FieldProps } from './types';
2
+ import styles from './styles.module.css';
3
+
4
+ export const Field = (props: FieldProps) => {
5
+ const { label, hint, error, children, htmlFor } = props;
6
+
7
+ return (
8
+ <label className={styles.field} htmlFor={htmlFor}>
9
+ <span className={styles.label}>{label}</span>
10
+ {children}
11
+ {hint && !error && <span className={styles.hint}>{hint}</span>}
12
+ {error && <span className={styles.error}>{error}</span>}
13
+ </label>
14
+ );
15
+ };
16
+
17
+ export type { FieldProps };
@@ -0,0 +1,21 @@
1
+ .field {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 4px;
5
+ }
6
+
7
+ .label {
8
+ font-size: var(--ui-text-small);
9
+ font-weight: 500;
10
+ color: var(--ui-foreground);
11
+ }
12
+
13
+ .hint {
14
+ font-size: var(--ui-text-xsmall);
15
+ color: var(--ui-muted);
16
+ }
17
+
18
+ .error {
19
+ font-size: var(--ui-text-xsmall);
20
+ color: var(--ui-danger);
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type FieldProps = {
4
+ label: string;
5
+ hint?: string;
6
+ error?: string;
7
+ children: ReactNode;
8
+ htmlFor?: string;
9
+ };
@@ -0,0 +1,91 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { IconButton } from './index';
3
+ import type { IconButtonVariant, IconButtonSize } from './types';
4
+
5
+ const meta: Meta<typeof IconButton> = {
6
+ title: 'Forms/IconButton',
7
+ component: IconButton,
8
+ decorators: [
9
+ (Story) => (
10
+ <div style={{ padding: 32 }}>
11
+ <Story />
12
+ </div>
13
+ ),
14
+ ],
15
+ };
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof IconButton>;
19
+
20
+ const CloseIcon = () => (
21
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
22
+ <path
23
+ d="M4 4 L12 12 M12 4 L4 12"
24
+ stroke="currentColor"
25
+ strokeWidth="1.5"
26
+ strokeLinecap="round"
27
+ />
28
+ </svg>
29
+ );
30
+
31
+ const PlusIcon = () => (
32
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
33
+ <path
34
+ d="M8 3 V13 M3 8 H13"
35
+ stroke="currentColor"
36
+ strokeWidth="1.5"
37
+ strokeLinecap="round"
38
+ />
39
+ </svg>
40
+ );
41
+
42
+ const VARIANTS: IconButtonVariant[] = ['ghost', 'solid', 'danger'];
43
+ const SIZES: IconButtonSize[] = ['small', 'medium'];
44
+
45
+ export const Default: Story = {
46
+ render: () => (
47
+ <IconButton ariaLabel="Close">
48
+ <CloseIcon />
49
+ </IconButton>
50
+ ),
51
+ };
52
+
53
+ export const WithTooltip: Story = {
54
+ render: () => (
55
+ <IconButton ariaLabel="Close panel" tooltip="Close panel">
56
+ <CloseIcon />
57
+ </IconButton>
58
+ ),
59
+ };
60
+
61
+ export const Variants: Story = {
62
+ render: () => (
63
+ <div style={{ display: 'flex', gap: 16 }}>
64
+ {VARIANTS.map((variant) => (
65
+ <IconButton key={variant} ariaLabel={variant} tooltip={variant} variant={variant}>
66
+ <PlusIcon />
67
+ </IconButton>
68
+ ))}
69
+ </div>
70
+ ),
71
+ };
72
+
73
+ export const Sizes: Story = {
74
+ render: () => (
75
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
76
+ {SIZES.map((size) => (
77
+ <IconButton key={size} ariaLabel={size} tooltip={size} size={size}>
78
+ <PlusIcon />
79
+ </IconButton>
80
+ ))}
81
+ </div>
82
+ ),
83
+ };
84
+
85
+ export const Disabled: Story = {
86
+ render: () => (
87
+ <IconButton ariaLabel="Close" tooltip="Cannot close right now" disabled>
88
+ <CloseIcon />
89
+ </IconButton>
90
+ ),
91
+ };