@ds-mo/ui 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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/index.css +1 -0
  4. package/dist/index.d.ts +969 -0
  5. package/dist/index.js +2623 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/r/accordion.json +79 -0
  8. package/dist/r/badge.json +69 -0
  9. package/dist/r/banner.json +90 -0
  10. package/dist/r/breadcrumb.json +70 -0
  11. package/dist/r/button-group.json +65 -0
  12. package/dist/r/button.json +137 -0
  13. package/dist/r/card.json +82 -0
  14. package/dist/r/checkbox.json +91 -0
  15. package/dist/r/divider.json +57 -0
  16. package/dist/r/empty-state.json +69 -0
  17. package/dist/r/error-boundary.json +66 -0
  18. package/dist/r/fade.json +65 -0
  19. package/dist/r/field.json +71 -0
  20. package/dist/r/header.json +67 -0
  21. package/dist/r/input.json +77 -0
  22. package/dist/r/loader.json +54 -0
  23. package/dist/r/menu.json +130 -0
  24. package/dist/r/modal.json +93 -0
  25. package/dist/r/pagination.json +80 -0
  26. package/dist/r/radio.json +85 -0
  27. package/dist/r/registry.json +1600 -0
  28. package/dist/r/scrollbar.json +68 -0
  29. package/dist/r/select.json +93 -0
  30. package/dist/r/sidebar.json +105 -0
  31. package/dist/r/skeleton.json +72 -0
  32. package/dist/r/slider.json +87 -0
  33. package/dist/r/surface.json +97 -0
  34. package/dist/r/tab-group.json +72 -0
  35. package/dist/r/tab.json +71 -0
  36. package/dist/r/table.json +110 -0
  37. package/dist/r/tag.json +110 -0
  38. package/dist/r/text.json +94 -0
  39. package/dist/r/toast.json +77 -0
  40. package/dist/r/toggle-button-group.json +65 -0
  41. package/dist/r/toggle-button.json +94 -0
  42. package/dist/r/toggle.json +65 -0
  43. package/dist/r/tooltip.json +86 -0
  44. package/package.json +80 -0
  45. package/src/components/Accordion/Accordion.module.css +66 -0
  46. package/src/components/Accordion/Accordion.stories.tsx +95 -0
  47. package/src/components/Accordion/Accordion.tsx +122 -0
  48. package/src/components/Accordion/index.ts +2 -0
  49. package/src/components/Badge/Badge.module.css +6 -0
  50. package/src/components/Badge/Badge.stories.tsx +99 -0
  51. package/src/components/Badge/Badge.tsx +23 -0
  52. package/src/components/Badge/index.ts +2 -0
  53. package/src/components/Banner/Banner.module.css +67 -0
  54. package/src/components/Banner/Banner.stories.tsx +81 -0
  55. package/src/components/Banner/Banner.tsx +109 -0
  56. package/src/components/Banner/index.ts +2 -0
  57. package/src/components/Breadcrumb/Breadcrumb.module.css +46 -0
  58. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +72 -0
  59. package/src/components/Breadcrumb/Breadcrumb.tsx +68 -0
  60. package/src/components/Breadcrumb/index.ts +2 -0
  61. package/src/components/Button/Button.module.css +343 -0
  62. package/src/components/Button/Button.stories.tsx +575 -0
  63. package/src/components/Button/Button.tsx +201 -0
  64. package/src/components/Button/index.ts +10 -0
  65. package/src/components/ButtonGroup/ButtonGroup.module.css +157 -0
  66. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +384 -0
  67. package/src/components/ButtonGroup/ButtonGroup.tsx +122 -0
  68. package/src/components/ButtonGroup/index.ts +2 -0
  69. package/src/components/Card/Card.module.css +36 -0
  70. package/src/components/Card/Card.stories.tsx +78 -0
  71. package/src/components/Card/Card.tsx +53 -0
  72. package/src/components/Card/index.ts +2 -0
  73. package/src/components/Checkbox/Checkbox.module.css +47 -0
  74. package/src/components/Checkbox/Checkbox.stories.tsx +129 -0
  75. package/src/components/Checkbox/Checkbox.tsx +68 -0
  76. package/src/components/Checkbox/index.ts +2 -0
  77. package/src/components/Divider/Divider.module.css +19 -0
  78. package/src/components/Divider/Divider.stories.tsx +55 -0
  79. package/src/components/Divider/Divider.tsx +28 -0
  80. package/src/components/Divider/index.ts +2 -0
  81. package/src/components/EmptyState/EmptyState.module.css +8 -0
  82. package/src/components/EmptyState/EmptyState.stories.tsx +53 -0
  83. package/src/components/EmptyState/EmptyState.tsx +31 -0
  84. package/src/components/EmptyState/index.ts +2 -0
  85. package/src/components/ErrorBoundary/ErrorBoundary.module.css +18 -0
  86. package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +38 -0
  87. package/src/components/ErrorBoundary/ErrorBoundary.tsx +50 -0
  88. package/src/components/ErrorBoundary/index.ts +2 -0
  89. package/src/components/Fade/Fade.module.css +31 -0
  90. package/src/components/Fade/Fade.stories.tsx +35 -0
  91. package/src/components/Fade/Fade.tsx +33 -0
  92. package/src/components/Fade/index.ts +2 -0
  93. package/src/components/Field/Field.module.css +5 -0
  94. package/src/components/Field/Field.stories.tsx +111 -0
  95. package/src/components/Field/Field.tsx +22 -0
  96. package/src/components/Field/index.ts +2 -0
  97. package/src/components/Header/Header.module.css +33 -0
  98. package/src/components/Header/Header.stories.tsx +52 -0
  99. package/src/components/Header/Header.tsx +42 -0
  100. package/src/components/Header/index.ts +2 -0
  101. package/src/components/Input/Input.module.css +71 -0
  102. package/src/components/Input/Input.stories.tsx +135 -0
  103. package/src/components/Input/Input.tsx +76 -0
  104. package/src/components/Input/index.ts +2 -0
  105. package/src/components/LabelWrap/LabelWrap.module.css +34 -0
  106. package/src/components/LabelWrap/LabelWrap.tsx +32 -0
  107. package/src/components/LabelWrap/index.ts +1 -0
  108. package/src/components/Loader/Loader.module.css +9 -0
  109. package/src/components/Loader/Loader.stories.tsx +44 -0
  110. package/src/components/Loader/Loader.tsx +43 -0
  111. package/src/components/Loader/index.ts +2 -0
  112. package/src/components/Menu/DestructiveMenuItem.module.css +57 -0
  113. package/src/components/Menu/DestructiveMenuItem.tsx +64 -0
  114. package/src/components/Menu/Menu.module.css +49 -0
  115. package/src/components/Menu/Menu.stories.tsx +129 -0
  116. package/src/components/Menu/Menu.tsx +269 -0
  117. package/src/components/Menu/MenuItem.module.css +84 -0
  118. package/src/components/Menu/MenuItem.tsx +101 -0
  119. package/src/components/Menu/index.ts +6 -0
  120. package/src/components/Modal/Modal.module.css +67 -0
  121. package/src/components/Modal/Modal.stories.tsx +71 -0
  122. package/src/components/Modal/Modal.tsx +122 -0
  123. package/src/components/Modal/index.ts +2 -0
  124. package/src/components/Pagination/Pagination.module.css +64 -0
  125. package/src/components/Pagination/Pagination.stories.tsx +51 -0
  126. package/src/components/Pagination/Pagination.tsx +103 -0
  127. package/src/components/Pagination/index.ts +2 -0
  128. package/src/components/Radio/Radio.module.css +61 -0
  129. package/src/components/Radio/Radio.stories.tsx +101 -0
  130. package/src/components/Radio/Radio.tsx +108 -0
  131. package/src/components/Radio/index.ts +2 -0
  132. package/src/components/Scrollbar/Scrollbar.module.css +81 -0
  133. package/src/components/Scrollbar/Scrollbar.stories.tsx +46 -0
  134. package/src/components/Scrollbar/Scrollbar.tsx +187 -0
  135. package/src/components/Scrollbar/index.ts +2 -0
  136. package/src/components/Select/Select.module.css +31 -0
  137. package/src/components/Select/Select.stories.tsx +59 -0
  138. package/src/components/Select/Select.tsx +92 -0
  139. package/src/components/Select/index.ts +2 -0
  140. package/src/components/Sidebar/Sidebar.module.css +161 -0
  141. package/src/components/Sidebar/Sidebar.stories.tsx +125 -0
  142. package/src/components/Sidebar/Sidebar.tsx +129 -0
  143. package/src/components/Sidebar/SidebarItem.tsx +57 -0
  144. package/src/components/Sidebar/SidebarSection.tsx +42 -0
  145. package/src/components/Sidebar/index.ts +6 -0
  146. package/src/components/Skeleton/Skeleton.module.css +53 -0
  147. package/src/components/Skeleton/Skeleton.stories.tsx +89 -0
  148. package/src/components/Skeleton/Skeleton.tsx +66 -0
  149. package/src/components/Skeleton/index.ts +2 -0
  150. package/src/components/Slider/Slider.module.css +62 -0
  151. package/src/components/Slider/Slider.stories.tsx +120 -0
  152. package/src/components/Slider/Slider.tsx +74 -0
  153. package/src/components/Slider/index.ts +2 -0
  154. package/src/components/Surface/Surface.module.css +222 -0
  155. package/src/components/Surface/Surface.stories.tsx +213 -0
  156. package/src/components/Surface/Surface.tsx +219 -0
  157. package/src/components/Surface/index.ts +12 -0
  158. package/src/components/Tab/Tab.module.css +66 -0
  159. package/src/components/Tab/Tab.stories.tsx +103 -0
  160. package/src/components/Tab/Tab.tsx +41 -0
  161. package/src/components/Tab/index.ts +2 -0
  162. package/src/components/TabGroup/TabGroup.module.css +26 -0
  163. package/src/components/TabGroup/TabGroup.stories.tsx +60 -0
  164. package/src/components/TabGroup/TabGroup.tsx +83 -0
  165. package/src/components/TabGroup/index.ts +2 -0
  166. package/src/components/Table/Table.module.css +130 -0
  167. package/src/components/Table/Table.stories.tsx +111 -0
  168. package/src/components/Table/Table.tsx +226 -0
  169. package/src/components/Table/index.ts +11 -0
  170. package/src/components/Tag/Tag.module.css +270 -0
  171. package/src/components/Tag/Tag.stories.tsx +399 -0
  172. package/src/components/Tag/Tag.tsx +162 -0
  173. package/src/components/Tag/index.ts +2 -0
  174. package/src/components/Text/Text.module.css +166 -0
  175. package/src/components/Text/Text.stories.tsx +212 -0
  176. package/src/components/Text/Text.tsx +181 -0
  177. package/src/components/Text/index.ts +13 -0
  178. package/src/components/Toast/Toast.module.css +91 -0
  179. package/src/components/Toast/Toast.stories.tsx +66 -0
  180. package/src/components/Toast/Toast.tsx +182 -0
  181. package/src/components/Toast/index.ts +8 -0
  182. package/src/components/Toggle/Toggle.module.css +87 -0
  183. package/src/components/Toggle/Toggle.stories.tsx +85 -0
  184. package/src/components/Toggle/Toggle.tsx +52 -0
  185. package/src/components/Toggle/index.ts +2 -0
  186. package/src/components/ToggleButton/ToggleButton.module.css +172 -0
  187. package/src/components/ToggleButton/ToggleButton.stories.tsx +299 -0
  188. package/src/components/ToggleButton/ToggleButton.tsx +118 -0
  189. package/src/components/ToggleButton/index.ts +2 -0
  190. package/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css +163 -0
  191. package/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx +341 -0
  192. package/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx +111 -0
  193. package/src/components/ToggleButtonGroup/index.ts +2 -0
  194. package/src/components/Tooltip/Tooltip.module.css +45 -0
  195. package/src/components/Tooltip/Tooltip.stories.tsx +46 -0
  196. package/src/components/Tooltip/Tooltip.tsx +224 -0
  197. package/src/components/Tooltip/index.ts +2 -0
  198. package/src/css-modules.d.ts +4 -0
  199. package/src/docs/ColorUsage.mdx +195 -0
  200. package/src/docs/ElevationUsage.mdx +95 -0
  201. package/src/docs/Introduction.mdx +97 -0
  202. package/src/docs/OpticalSizing.mdx +178 -0
  203. package/src/docs/TypographyUsage.mdx +121 -0
  204. package/src/index.ts +154 -0
  205. package/src/stories/Colors.stories.tsx +528 -0
  206. package/src/stories/Dimensions.stories.tsx +200 -0
  207. package/src/stories/Effects.stories.tsx +231 -0
  208. package/src/stories/Icons.stories.tsx +159 -0
  209. package/src/stories/Typography.stories.tsx +157 -0
  210. package/src/types/icons.ts +15 -0
  211. package/src/types/index.ts +1 -0
  212. package/src/utils/cn.ts +6 -0
  213. package/src/utils/css-tokens.ts +22 -0
@@ -0,0 +1,200 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+
4
+ const meta: Meta = {
5
+ title: 'Design System/Dimensions',
6
+ parameters: { layout: 'fullscreen' },
7
+ };
8
+
9
+ export default meta;
10
+
11
+ // ── Helpers ──────────────────────────────────────────────────────────────────
12
+
13
+ const S: Record<string, React.CSSProperties> = {
14
+ page: { fontFamily: 'var(--typography-font-family, system-ui)', padding: 24, display: 'flex', flexDirection: 'column', gap: 40 },
15
+ section: { display: 'flex', flexDirection: 'column', gap: 12 },
16
+ heading: { fontSize: 18, fontWeight: 600, color: 'var(--color-foreground-primary)', margin: 0, letterSpacing: -0.3 },
17
+ subheading: { fontSize: 13, fontWeight: 500, color: 'var(--color-foreground-secondary)', margin: 0 },
18
+ row: { display: 'flex', alignItems: 'center', gap: 12, padding: '6px 0' },
19
+ label: { fontSize: 11, color: 'var(--color-foreground-secondary)', minWidth: 140, flexShrink: 0, fontFamily: 'monospace' },
20
+ };
21
+
22
+ function Section({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
23
+ return (
24
+ <div style={S.section}>
25
+ <div>
26
+ <h2 style={S.heading}>{title}</h2>
27
+ {subtitle && <p style={{ ...S.subheading, marginTop: 2 }}>{subtitle}</p>}
28
+ </div>
29
+ {children}
30
+ </div>
31
+ );
32
+ }
33
+
34
+ // ── Stories ───────────────────────────────────────────────────────────────────
35
+
36
+ const SPACE_TOKENS = ['000', '012', '025', '050', '075', '100', '125', '150', '175', '200', '250', '300', '400', '600', '800'] as const;
37
+
38
+ function Spacing() {
39
+ return (
40
+ <div style={S.page}>
41
+ <Section title="Spacing" subtitle="--dimension-space-* (base: 8px)">
42
+ {SPACE_TOKENS.map(token => (
43
+ <div key={token} style={S.row}>
44
+ <span style={S.label}>space-{token}</span>
45
+ <div style={{
46
+ width: `var(--dimension-space-${token})`,
47
+ height: 20,
48
+ backgroundColor: 'var(--color-background-bold-brand)',
49
+ borderRadius: 2,
50
+ minWidth: token === '000' ? 0 : 1,
51
+ transition: 'width 0.2s',
52
+ }} />
53
+ </div>
54
+ ))}
55
+ </Section>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ const SIZE_TOKENS = ['000', '050', '100', '150', '200', '250', '300', '400', '500', '600', '800'] as const;
61
+
62
+ function Sizes() {
63
+ return (
64
+ <div style={S.page}>
65
+ <Section title="Size" subtitle="--dimension-size-* — element width/height (base: 8px)">
66
+ {SIZE_TOKENS.map(token => (
67
+ <div key={token} style={S.row}>
68
+ <span style={S.label}>size-{token}</span>
69
+ <div style={{
70
+ width: `var(--dimension-size-${token})`,
71
+ height: `var(--dimension-size-${token})`,
72
+ backgroundColor: 'var(--color-background-faint-brand)',
73
+ border: '1px solid var(--color-border-brand)',
74
+ borderRadius: 4,
75
+ minWidth: token === '000' ? 0 : 1,
76
+ minHeight: token === '000' ? 0 : 1,
77
+ }} />
78
+ </div>
79
+ ))}
80
+ </Section>
81
+
82
+ <Section title="Iconography Sizes" subtitle="--dimension-iconography-* — semantic icon size aliases">
83
+ {['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'].map(token => (
84
+ <div key={token} style={S.row}>
85
+ <span style={S.label}>iconography-{token}</span>
86
+ <div style={{
87
+ width: `var(--dimension-iconography-${token})`,
88
+ height: `var(--dimension-iconography-${token})`,
89
+ backgroundColor: 'var(--color-foreground-secondary)',
90
+ borderRadius: 'var(--dimension-radius-050)',
91
+ }} />
92
+ </div>
93
+ ))}
94
+ </Section>
95
+ </div>
96
+ );
97
+ }
98
+
99
+ const RADIUS_TOKENS = ['000', '012', '025', '037', '050', '075', '100', '125', '150', '175', '200', '250', '275', '300', '400', '600', 'half'] as const;
100
+
101
+ function Radii() {
102
+ return (
103
+ <div style={S.page}>
104
+ <Section title="Border Radius" subtitle="--dimension-radius-* (base: 8px, half: 9999px)">
105
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: 12 }}>
106
+ {RADIUS_TOKENS.map(token => (
107
+ <div key={token} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
108
+ <div style={{
109
+ width: 64,
110
+ height: 64,
111
+ backgroundColor: 'var(--color-background-bold-brand)',
112
+ borderRadius: `var(--dimension-radius-${token})`,
113
+ }} />
114
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', fontFamily: 'monospace' }}>
115
+ radius-{token}
116
+ </span>
117
+ </div>
118
+ ))}
119
+ </div>
120
+ </Section>
121
+ </div>
122
+ );
123
+ }
124
+
125
+ const STROKE_TOKENS = ['012', '015', '018', '025', '037', '050'] as const;
126
+
127
+ function Strokes() {
128
+ return (
129
+ <div style={S.page}>
130
+ <Section title="Stroke Widths" subtitle="--dimension-stroke-width-*">
131
+ {STROKE_TOKENS.map(token => (
132
+ <div key={token} style={S.row}>
133
+ <span style={S.label}>stroke-width-{token}</span>
134
+ <div style={{
135
+ width: 200,
136
+ height: 0,
137
+ borderTop: `var(--dimension-stroke-width-${token}) solid var(--color-foreground-primary)`,
138
+ }} />
139
+ </div>
140
+ ))}
141
+ </Section>
142
+
143
+ <Section title="Z-Index Layers" subtitle="--dimension-z-index-*">
144
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
145
+ {[
146
+ { token: 'base', value: '0' },
147
+ { token: 'raised', value: '50' },
148
+ { token: 'overlay', value: '250' },
149
+ { token: 'modal', value: '450' },
150
+ { token: 'floating', value: '500' },
151
+ { token: 'tooltip', value: '750' },
152
+ ].map(z => (
153
+ <div key={z.token} style={{
154
+ padding: '12px 16px',
155
+ backgroundColor: 'var(--color-background-faint-neutral)',
156
+ border: '1px solid var(--color-border-tertiary)',
157
+ borderRadius: 8,
158
+ textAlign: 'center',
159
+ }}>
160
+ <div style={{ fontSize: 20, fontWeight: 600, color: 'var(--color-foreground-primary)' }}>{z.value}</div>
161
+ <div style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', marginTop: 4 }}>z-index-{z.token}</div>
162
+ </div>
163
+ ))}
164
+ </div>
165
+ </Section>
166
+
167
+ <Section title="Component Widths" subtitle="Cards, modals, forms, menus, panels">
168
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 8 }}>
169
+ {[
170
+ { group: 'card-width', tokens: ['sm', 'md', 'lg'], values: ['400', '600', '800'] },
171
+ { group: 'modal-width', tokens: ['sm', 'md', 'lg'], values: ['400', '600', '800'] },
172
+ { group: 'menu-width', tokens: ['sm', 'md', 'lg'], values: ['200', '400', '600'] },
173
+ { group: 'panel-width', tokens: ['xs', 'sm', 'md', 'lg', 'xl'], values: ['200', '300', '400', '500', '600'] },
174
+ ].map(group => (
175
+ <div key={group.group} style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 8 }}>
176
+ <span style={{ fontSize: 12, fontWeight: 500, color: 'var(--color-foreground-secondary)' }}>{group.group}</span>
177
+ {group.tokens.map((token, i) => (
178
+ <div key={token} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
179
+ <span style={{ ...S.label, minWidth: 80 }}>{token}</span>
180
+ <div style={{
181
+ width: `min(var(--dimension-${group.group}-${token}), 100%)`,
182
+ height: 16,
183
+ backgroundColor: 'var(--color-background-medium-brand)',
184
+ borderRadius: 2,
185
+ }} />
186
+ <span style={{ fontSize: 10, color: 'var(--color-foreground-tertiary)' }}>{group.values[i]}px</span>
187
+ </div>
188
+ ))}
189
+ </div>
190
+ ))}
191
+ </div>
192
+ </Section>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ export const SpacingStory: StoryObj = { name: 'Spacing', render: () => <Spacing /> };
198
+ export const SizesStory: StoryObj = { name: 'Sizes', render: () => <Sizes /> };
199
+ export const RadiiStory: StoryObj = { name: 'Radii', render: () => <Radii /> };
200
+ export const StrokesAndMore: StoryObj = { name: 'Strokes & Layers', render: () => <Strokes /> };
@@ -0,0 +1,231 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+
4
+ const meta: Meta = {
5
+ title: 'Design System/Effects',
6
+ parameters: { layout: 'fullscreen' },
7
+ };
8
+
9
+ export default meta;
10
+
11
+ // ── Helpers ──────────────────────────────────────────────────────────────────
12
+
13
+ const S: Record<string, React.CSSProperties> = {
14
+ page: { fontFamily: 'var(--typography-font-family, system-ui)', padding: 24, display: 'flex', flexDirection: 'column', gap: 40 },
15
+ section: { display: 'flex', flexDirection: 'column', gap: 16 },
16
+ heading: { fontSize: 18, fontWeight: 600, color: 'var(--color-foreground-primary)', margin: 0, letterSpacing: -0.3 },
17
+ subheading: { fontSize: 13, fontWeight: 500, color: 'var(--color-foreground-secondary)', margin: 0 },
18
+ };
19
+
20
+ function Section({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
21
+ return (
22
+ <div style={S.section}>
23
+ <div>
24
+ <h2 style={S.heading}>{title}</h2>
25
+ {subtitle && <p style={{ ...S.subheading, marginTop: 2 }}>{subtitle}</p>}
26
+ </div>
27
+ {children}
28
+ </div>
29
+ );
30
+ }
31
+
32
+ // ── Stories ───────────────────────────────────────────────────────────────────
33
+
34
+ const ELEVATION_LEVELS = [
35
+ { token: 'elevated-none', label: 'None' },
36
+ { token: 'depressed-sm', label: 'Depressed SM' },
37
+ { token: 'depressed-md', label: 'Depressed MD' },
38
+ { token: 'elevated-sm', label: 'Elevated SM' },
39
+ { token: 'elevated-md', label: 'Elevated MD' },
40
+ { token: 'elevated-floating', label: 'Floating' },
41
+ ] as const;
42
+
43
+ const PANEL_LEVELS = [
44
+ { token: 'elevated-panel-top', label: 'Panel Top' },
45
+ { token: 'elevated-panel-right', label: 'Panel Right' },
46
+ { token: 'elevated-panel-bottom', label: 'Panel Bottom' },
47
+ { token: 'elevated-panel-left', label: 'Panel Left' },
48
+ ] as const;
49
+
50
+ function Elevation() {
51
+ return (
52
+ <div style={S.page}>
53
+ <Section title="Elevation" subtitle="--effect-elevation-* — combined shadow + highlight">
54
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 24, padding: 24 }}>
55
+ {ELEVATION_LEVELS.map(level => (
56
+ <div key={level.token} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
57
+ <div style={{
58
+ width: 120,
59
+ height: 80,
60
+ backgroundColor: 'var(--color-background-primary)',
61
+ borderRadius: 8,
62
+ boxShadow: `var(--effect-elevation-${level.token})`,
63
+ }} />
64
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', fontFamily: 'monospace' }}>
65
+ {level.label}
66
+ </span>
67
+ </div>
68
+ ))}
69
+ </div>
70
+ </Section>
71
+
72
+ <Section title="Panel Shadows" subtitle="Directional shadows for attached panels">
73
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 24, padding: 24 }}>
74
+ {PANEL_LEVELS.map(level => (
75
+ <div key={level.token} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
76
+ <div style={{
77
+ width: 120,
78
+ height: 80,
79
+ backgroundColor: 'var(--color-background-primary)',
80
+ borderRadius: 8,
81
+ boxShadow: `var(--effect-elevation-${level.token})`,
82
+ }} />
83
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', fontFamily: 'monospace' }}>
84
+ {level.label}
85
+ </span>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ </Section>
90
+ </div>
91
+ );
92
+ }
93
+
94
+ const DURATION_TOKENS = [
95
+ { token: 'instant', ms: '0' },
96
+ { token: 'short-1', ms: '50' },
97
+ { token: 'short-2', ms: '100' },
98
+ { token: 'short-3', ms: '200' },
99
+ { token: 'medium-1', ms: '300' },
100
+ { token: 'medium-2', ms: '400' },
101
+ { token: 'medium-3', ms: '500' },
102
+ { token: 'long-1', ms: '750' },
103
+ { token: 'long-2', ms: '1000' },
104
+ { token: 'long-3', ms: '2000' },
105
+ ] as const;
106
+
107
+ function MotionDemo({ token, ms }: { token: string; ms: string }) {
108
+ const [active, setActive] = React.useState(false);
109
+ return (
110
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '8px 0' }}>
111
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', minWidth: 100, fontFamily: 'monospace' }}>
112
+ {token}
113
+ </span>
114
+ <span style={{ fontSize: 10, color: 'var(--color-foreground-tertiary)', minWidth: 50 }}>{ms}ms</span>
115
+ <div
116
+ style={{
117
+ width: 200,
118
+ height: 24,
119
+ backgroundColor: 'var(--color-background-faint-neutral)',
120
+ borderRadius: 12,
121
+ position: 'relative',
122
+ cursor: 'pointer',
123
+ }}
124
+ onMouseEnter={() => setActive(true)}
125
+ onMouseLeave={() => setActive(false)}
126
+ >
127
+ <div style={{
128
+ position: 'absolute',
129
+ top: 2,
130
+ left: active ? 'calc(100% - 22px)' : '2px',
131
+ width: 20,
132
+ height: 20,
133
+ borderRadius: 10,
134
+ backgroundColor: 'var(--color-background-bold-brand)',
135
+ transition: `left var(--effect-motion-${token})`,
136
+ }} />
137
+ </div>
138
+ </div>
139
+ );
140
+ }
141
+
142
+ function EasingDemo({ token, bezier }: { token: string; bezier: string }) {
143
+ const [active, setActive] = React.useState(false);
144
+ return (
145
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '8px 0' }}>
146
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', minWidth: 140, fontFamily: 'monospace' }}>
147
+ {token}
148
+ </span>
149
+ <span style={{ fontSize: 10, color: 'var(--color-foreground-tertiary)', minWidth: 160, fontFamily: 'monospace' }}>
150
+ cubic-bezier({bezier})
151
+ </span>
152
+ <div
153
+ style={{
154
+ width: 200,
155
+ height: 24,
156
+ backgroundColor: 'var(--color-background-faint-neutral)',
157
+ borderRadius: 12,
158
+ position: 'relative',
159
+ cursor: 'pointer',
160
+ }}
161
+ onMouseEnter={() => setActive(true)}
162
+ onMouseLeave={() => setActive(false)}
163
+ >
164
+ <div style={{
165
+ position: 'absolute',
166
+ top: 2,
167
+ left: active ? 'calc(100% - 22px)' : '2px',
168
+ width: 20,
169
+ height: 20,
170
+ borderRadius: 10,
171
+ backgroundColor: 'var(--color-background-bold-positive)',
172
+ transition: `left var(--effect-animation-duration-medium-3) var(--effect-animation-easing-${token})`,
173
+ }} />
174
+ </div>
175
+ </div>
176
+ );
177
+ }
178
+
179
+ function Motion() {
180
+ return (
181
+ <div style={S.page}>
182
+ <Section title="Motion Presets" subtitle="--effect-motion-* — hover to animate">
183
+ {DURATION_TOKENS.map(d => (
184
+ <MotionDemo key={d.token} token={d.token} ms={d.ms} />
185
+ ))}
186
+ </Section>
187
+
188
+ <Section title="Easing Curves" subtitle="--effect-animation-easing-* — hover to compare curves (all use 500ms duration)">
189
+ {[
190
+ { token: 'ease-in', bezier: '0.47, 0, 0.75, 0.72' },
191
+ { token: 'ease-out', bezier: '0.17, 0.84, 0.44, 1' },
192
+ { token: 'ease-in-out', bezier: '0.77, 0, 0.18, 1' },
193
+ { token: 'ease-in-out-back', bezier: '0.22, 0.61, 0.01, 1.03' },
194
+ ].map(easing => (
195
+ <EasingDemo key={easing.token} token={easing.token} bezier={easing.bezier} />
196
+ ))}
197
+ </Section>
198
+
199
+ <Section title="Blur" subtitle="--effect-blur-*">
200
+ <div style={{ display: 'flex', gap: 24 }}>
201
+ {['sm', 'md', 'lg'].map(size => (
202
+ <div key={size} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
203
+ <div style={{
204
+ width: 120,
205
+ height: 80,
206
+ borderRadius: 8,
207
+ overflow: 'hidden',
208
+ position: 'relative',
209
+ background: 'linear-gradient(135deg, var(--color-background-bold-brand), var(--color-background-bold-negative))',
210
+ }}>
211
+ <div style={{
212
+ position: 'absolute',
213
+ inset: 0,
214
+ backdropFilter: `blur(var(--effect-blur-${size}))`,
215
+ WebkitBackdropFilter: `blur(var(--effect-blur-${size}))`,
216
+ backgroundColor: 'var(--color-background-translucent)',
217
+ }} />
218
+ </div>
219
+ <span style={{ fontSize: 11, color: 'var(--color-foreground-secondary)', fontFamily: 'monospace' }}>
220
+ blur-{size}
221
+ </span>
222
+ </div>
223
+ ))}
224
+ </div>
225
+ </Section>
226
+ </div>
227
+ );
228
+ }
229
+
230
+ export const ElevationStory: StoryObj = { name: 'Elevation', render: () => <Elevation /> };
231
+ export const MotionStory: StoryObj = { name: 'Motion & Blur', render: () => <Motion /> };
@@ -0,0 +1,159 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React, { useState } from 'react';
3
+ import * as Icons from '@ds-mo/icons';
4
+ import type { IconComponent } from '@ds-mo/icons';
5
+
6
+ const meta: Meta = {
7
+ title: 'Design System/Icons',
8
+ parameters: { layout: 'fullscreen' },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ // ── Helpers ──────────────────────────────────────────────────────────────────
14
+
15
+ const allIcons = Object.entries(Icons).filter(
16
+ ([name, v]) => name !== 'createIcon' && v != null
17
+ ) as [string, IconComponent][];
18
+
19
+ const SIZE_OPTIONS = [12, 16, 20, 24, 32] as const;
20
+ type Size = typeof SIZE_OPTIONS[number];
21
+
22
+ // ── Story ─────────────────────────────────────────────────────────────────────
23
+
24
+ function IconGrid() {
25
+ const [search, setSearch] = useState('');
26
+ const [size, setSize] = useState<Size>(20);
27
+ const [copied, setCopied] = useState<string | null>(null);
28
+
29
+ const filtered = allIcons.filter(([name]) =>
30
+ name.toLowerCase().includes(search.toLowerCase())
31
+ );
32
+
33
+ const copy = (name: string) => {
34
+ navigator.clipboard.writeText(name);
35
+ setCopied(name);
36
+ setTimeout(() => setCopied(null), 1500);
37
+ };
38
+
39
+ return (
40
+ <div style={{ fontFamily: 'var(--font-family-sans, system-ui)', padding: 24 }}>
41
+
42
+ {/* Toolbar */}
43
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24, flexWrap: 'wrap' }}>
44
+ <input
45
+ type="search"
46
+ placeholder={`Search ${allIcons.length} icons…`}
47
+ value={search}
48
+ onChange={e => setSearch(e.target.value)}
49
+ style={{
50
+ flex: '1 1 240px',
51
+ minWidth: 200,
52
+ maxWidth: 400,
53
+ height: 36,
54
+ padding: '0 12px',
55
+ border: '1px solid var(--color-border-tertiary, #e0e0e0)',
56
+ borderRadius: 6,
57
+ fontSize: 14,
58
+ outline: 'none',
59
+ background: 'var(--color-background-primary, #fff)',
60
+ color: 'var(--color-foreground-primary, #111)',
61
+ }}
62
+ />
63
+ <div style={{ display: 'flex', gap: 4 }}>
64
+ {SIZE_OPTIONS.map(s => (
65
+ <button
66
+ key={s}
67
+ onClick={() => setSize(s)}
68
+ style={{
69
+ height: 36,
70
+ padding: '0 12px',
71
+ border: '1px solid var(--color-border-tertiary, #e0e0e0)',
72
+ borderRadius: 6,
73
+ fontSize: 13,
74
+ cursor: 'pointer',
75
+ fontWeight: size === s ? 600 : 400,
76
+ background: size === s
77
+ ? 'var(--color-background-faint-brand, #eff3ff)'
78
+ : 'var(--color-background-primary, #fff)',
79
+ color: size === s
80
+ ? 'var(--color-foreground-bold-brand, #3a5ccc)'
81
+ : 'var(--color-foreground-secondary, #555)',
82
+ }}
83
+ >
84
+ {s}
85
+ </button>
86
+ ))}
87
+ </div>
88
+ <span style={{ fontSize: 13, color: 'var(--color-foreground-secondary, #888)', marginLeft: 'auto' }}>
89
+ {filtered.length} {filtered.length === 1 ? 'icon' : 'icons'}
90
+ </span>
91
+ </div>
92
+
93
+ {/* Grid */}
94
+ {filtered.length === 0 ? (
95
+ <p style={{ color: 'var(--color-foreground-secondary, #888)', fontSize: 14 }}>
96
+ No icons match "{search}"
97
+ </p>
98
+ ) : (
99
+ <div style={{
100
+ display: 'grid',
101
+ gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))',
102
+ gap: 4,
103
+ }}>
104
+ {filtered.map(([name, Icon]) => (
105
+ <button
106
+ key={name}
107
+ onClick={() => copy(name)}
108
+ title={`Click to copy: ${name}`}
109
+ style={{
110
+ display: 'flex',
111
+ flexDirection: 'column',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ gap: 8,
115
+ padding: '12px 8px',
116
+ border: '1px solid transparent',
117
+ borderRadius: 8,
118
+ cursor: 'pointer',
119
+ background: copied === name
120
+ ? 'var(--color-background-faint-brand, #eff3ff)'
121
+ : 'transparent',
122
+ color: 'var(--color-foreground-primary, #111)',
123
+ transition: 'background 0.12s',
124
+ textAlign: 'center',
125
+ }}
126
+ onMouseEnter={e => {
127
+ if (copied !== name)
128
+ (e.currentTarget as HTMLElement).style.background =
129
+ 'var(--color-background-faint-neutral, #f5f5f5)';
130
+ }}
131
+ onMouseLeave={e => {
132
+ if (copied !== name)
133
+ (e.currentTarget as HTMLElement).style.background = 'transparent';
134
+ }}
135
+ >
136
+ <Icon size={size} />
137
+ <span style={{
138
+ fontSize: 10,
139
+ lineHeight: 1.3,
140
+ color: copied === name
141
+ ? 'var(--color-foreground-bold-brand, #3a5ccc)'
142
+ : 'var(--color-foreground-secondary, #888)',
143
+ wordBreak: 'break-word',
144
+ maxWidth: '100%',
145
+ }}>
146
+ {copied === name ? '✓ copied' : name}
147
+ </span>
148
+ </button>
149
+ ))}
150
+ </div>
151
+ )}
152
+ </div>
153
+ );
154
+ }
155
+
156
+ export const AllIcons: StoryObj = {
157
+ name: 'All Icons',
158
+ render: () => <IconGrid />,
159
+ };