@weichenlin/swell-ui 0.0.0-stage → 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.
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Tokens plus component scanning. Import right after tailwindcss.
3
+ * Published next to the bundled components, so this path is relative to the package's dist/.
4
+ */
5
+ @import "./theme.css";
6
+ @source "./*.js";
package/dist/theme.css ADDED
@@ -0,0 +1,337 @@
1
+ /*
2
+ * swell-ui design tokens, dark theme and base styles.
3
+ * Import right after tailwindcss. Contains no utilities and no @source:
4
+ * use styles.css to also have Tailwind scan the components.
5
+ */
6
+
7
+ /* Tokens. Light values live on bare :root (and @theme). */
8
+ @theme {
9
+ --color-bg: #f5f4ed; /* Parchment */
10
+ --color-surface: #faf9f5; /* Ivory */
11
+ --color-surface-raised: #ffffff; /* Pure White */
12
+ --color-surface-muted: #e8e6dc; /* Warm Sand */
13
+ --color-fg: #141413; /* Near Black */
14
+ --color-fg-strong: #4d4c48; /* Charcoal Warm */
15
+ --color-fg-muted: #5e5d59; /* Olive Gray */
16
+ --color-fg-subtle: #686761; /* Warm gray: >=4.5:1 even on surface-muted */
17
+ --color-border: #f0eee6; /* Border Cream */
18
+ --color-border-strong: #e8e6dc; /* Border Warm */
19
+ --color-ring: #d1cfc5; /* Ring Warm */
20
+ --color-ring-deep: #c2c0b6; /* Ring Deep */
21
+ --color-primary: #8b5e3c; /* Warm Brown */
22
+ --color-primary-fg: #faf9f5;
23
+ --color-accent: #c49a6c; /* Warm Tan */
24
+ --color-inverse: #30302e; /* Dark Surface */
25
+ --color-inverse-fg: #faf9f5;
26
+ --color-danger: #b53333; /* Error Crimson */
27
+ --color-danger-fg: #faf9f5;
28
+ --color-focus: #3898ec; /* Focus Blue */
29
+ /*
30
+ * Text fields: a slightly sunk solid fill.
31
+ * Borders are fg mixed over bg in oklab, kept >=3:1 against both the page and the fill (WCAG 1.4.11).
32
+ * Focus is neutral, not blue: border steps up to near-fg plus a soft halo.
33
+ */
34
+ /* Fill = fg 4% over bg in oklab, baked to hex: a color-mix token gets a wrong static fallback per theme */
35
+ --color-input-bg: #ebeae3;
36
+ --color-input-border: #7f7e7b;
37
+ --color-input-placeholder: #6a6964;
38
+ /* Hover stacks cues (stronger border, lifted fill, faint drop shadow): a border step alone is too subtle */
39
+ --color-input-border-hover: #4d4c48;
40
+ --color-input-bg-hover: #f3f2ec;
41
+ --color-input-focus: #8b5e3c; /* Warm Brown (primary) */
42
+ --color-input-highlight: rgb(0 0 0 / 0.03);
43
+ --shadow-input: inset 0 1px 0 var(--color-input-highlight);
44
+ --shadow-input-lift: 0 1px 2px rgb(0 0 0 / 0.06);
45
+ /* Focus/error halos: decorative 3px rings outside the border */
46
+ --color-input-halo: color-mix(in oklab, var(--color-input-focus) 14%, transparent);
47
+ --color-input-halo-invalid: color-mix(in oklab, var(--color-danger) 12%, transparent);
48
+ --color-input-halo-invalid-focus: color-mix(in oklab, var(--color-danger) 22%, transparent);
49
+ /*
50
+ * Selection controls (Switch, Checkbox): the checked fill and the mark drawn on it (thumb, check, dash).
51
+ * The off track/box reuses the input border, which is already >=3:1 on the page and the fill.
52
+ */
53
+ --color-control-checked: #8b5e3c; /* Warm Brown (primary) */
54
+ --color-control-checked-fg: #ffffff; /* 5.58:1 on the fill */
55
+ /* Thumb: a hairline plus a soft drop straight down. Geist's sideways offset made the small thumb
56
+ * look shifted, so the shadow stays horizontally symmetric. */
57
+ --shadow-switch-thumb: 0 0 0 0.5px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.2);
58
+ /*
59
+ * Popovers (the Select picker): raised above the page with a warm hairline and a soft shadow.
60
+ * Highlight is the hovered/keyboard-active row; same fill for both so they never disagree.
61
+ */
62
+ --color-popover-bg: #ffffff; /* Pure White */
63
+ --color-popover-border: #e0ded4; /* between Border Warm and Ring Warm: Border Warm vanishes on white */
64
+ --color-popover-highlight: #efede5;
65
+ --shadow-popover: 0 8px 24px rgb(0 0 0 / 0.08), 0 1px 3px rgb(0 0 0 / 0.06);
66
+ /*
67
+ * Dialogs: same surface and hairline as popovers, one elevation step higher (a wider, softer
68
+ * drop) since they sit over a dimmed page. Backdrop is warm near-black, never blurred.
69
+ */
70
+ --shadow-dialog: 0 16px 48px rgb(0 0 0 / 0.12), 0 2px 6px rgb(0 0 0 / 0.06);
71
+ --color-dialog-backdrop: rgb(20 20 19 / 0.4);
72
+ /*
73
+ * Status tones (Badge, later Dialog/Toast/table state). Per tone: main = solid fill / standalone icon,
74
+ * -fg = text on a solid fill, -subtle-bg/-subtle-fg = tinted badge, -border = its hairline.
75
+ * subtle-bg is main mixed over bg in oklab (14% light / 16% dark), baked to hex; subtle-fg is main
76
+ * pushed along lightness until >=5:1 on it and >=4.5:1 on the translucent equivalent over any surface.
77
+ * Meaning is carried by text + icon; hue/lightness are spaced so success vs danger also survives red-green CVD.
78
+ * danger main/-fg already exist above; only its subtle-* are new.
79
+ */
80
+ /* success: olive, hue 125; deepest tone so it separates from danger by lightness for red-green CVD */
81
+ --color-success: #394d00;
82
+ --color-success-fg: #faf9f5;
83
+ --color-success-subtle-bg: #d8dbcc;
84
+ --color-success-subtle-fg: #3a4c0e;
85
+ --color-success-border: #aeb79b;
86
+ /* warning: ochre/amber, hue 78; lighter than danger; dark solid-fg because ivory on amber fails 4.5:1 */
87
+ --color-warning: #a5761d;
88
+ --color-warning-fg: #141413;
89
+ --color-warning-subtle-bg: #eae2d1;
90
+ --color-warning-subtle-fg: #7e5600;
91
+ --color-warning-border: #d9c7a9;
92
+ /* danger: existing Error Crimson (unchanged) */
93
+ --color-danger-subtle-bg: #f0dad2;
94
+ --color-danger-subtle-fg: #a2302f;
95
+ --color-danger-border: #e5b3aa;
96
+ /* info: dusty teal-blue, hue 230, chroma 0.065 (muted; the only cool-leaning tone besides Focus Blue) */
97
+ --color-info: #3e7087;
98
+ --color-info-fg: #faf9f5;
99
+ --color-info-subtle-bg: #dbe1de;
100
+ --color-info-subtle-fg: #356075;
101
+ --color-info-border: #b4c4c9;
102
+ /* neutral: warm gray (Olive Gray family) */
103
+ --color-neutral: #66635c;
104
+ --color-neutral-fg: #faf9f5;
105
+ --color-neutral-subtle-bg: #e0ded7;
106
+ --color-neutral-subtle-fg: #5e5b55;
107
+ --color-neutral-border: #c0bfb8;
108
+
109
+ --font-sans: "Anthropic Sans", system-ui, Inter, Arial, sans-serif;
110
+ --font-serif: "Anthropic Serif", Georgia, serif;
111
+ --font-mono: "Anthropic Mono", ui-monospace, Menlo, monospace;
112
+
113
+ /* Radius scale */
114
+ --radius-sharp: 4px;
115
+ --radius-sm: 6px;
116
+ --radius-md: 8px;
117
+ --radius-lg: 12px;
118
+ --radius-xl: 16px;
119
+ --radius-2xl: 24px;
120
+ --radius-3xl: 32px;
121
+
122
+ --shadow-whisper: rgba(0, 0, 0, 0.05) 0 4px 24px;
123
+ --container-page: 1200px;
124
+ }
125
+
126
+ :root {
127
+ color-scheme: light;
128
+ }
129
+
130
+ /* Dark values */
131
+ :root:not([data-theme="light"]) {
132
+ @media (prefers-color-scheme: dark) {
133
+ color-scheme: dark;
134
+ --color-bg: #141413;
135
+ --color-surface: #30302e;
136
+ --color-surface-raised: #30302e;
137
+ --color-surface-muted: #262625; /* sunk below Dark Surface: header rows, hover fills */
138
+ --color-fg: #faf9f5;
139
+ --color-fg-strong: #b0aea5;
140
+ --color-fg-muted: #b0aea5;
141
+ --color-fg-subtle: #a8a79e; /* >=4.5:1 on raised and highlighted surfaces */
142
+ --color-border: #383836; /* between Dark Surface and Dark Warm: row dividers */
143
+ --color-border-strong: #3d3d3a; /* Dark Warm */
144
+ --color-ring: #3d3d3a; /* Dark Warm */
145
+ --color-ring-deep: #4d4c48; /* Charcoal Warm */
146
+ --color-inverse: #141413;
147
+ --color-inverse-fg: #b0aea5;
148
+ --color-danger: #ee8a80;
149
+ --color-danger-fg: #141413;
150
+ --color-input-bg: #1f1f1e; /* white 6% over bg in oklab */
151
+ --color-input-border: #81817e;
152
+ --color-input-placeholder: #9a998f;
153
+ --color-input-border-hover: #b0aea5;
154
+ --color-input-bg-hover: #262625;
155
+ --color-input-focus: #c49a6c; /* Warm Tan: primary is too dark on dark surfaces */
156
+ --color-control-checked: #c49a6c; /* Warm Tan, as the focus color */
157
+ --color-control-checked-fg: #141413; /* white on tan is only ~2.6:1; near-black is 7.2:1 */
158
+ --color-input-highlight: rgb(255 255 255 / 0.04);
159
+ --shadow-input-lift: 0 0 #0000; /* a drop shadow doesn't read on near-black */
160
+ --color-popover-bg: #30302e; /* Dark Surface */
161
+ --color-popover-border: #3d3d3a; /* Dark Warm */
162
+ --color-popover-highlight: #3d3d3a;
163
+ /* Darker and denser than light: the surface itself already separates from near-black */
164
+ --shadow-popover: 0 8px 24px rgb(0 0 0 / 0.4), 0 1px 3px rgb(0 0 0 / 0.3);
165
+ --shadow-dialog: 0 16px 48px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.3);
166
+ /* Darker than light: a 40% dim barely reads over a near-black page */
167
+ --color-dialog-backdrop: rgb(0 0 0 / 0.6);
168
+ /* Status tones: lightened for dark surfaces, solid-fg flips to near-black */
169
+ /* success: olive, lightened for dark; kept darker than danger for CVD */
170
+ --color-success: #758e48;
171
+ --color-success-fg: #141413;
172
+ --color-success-subtle-bg: #22251c;
173
+ --color-success-subtle-fg: #98b070;
174
+ --color-success-border: #384128;
175
+ /* warning: amber */
176
+ --color-warning: #e6b463;
177
+ --color-warning-fg: #141413;
178
+ --color-warning-subtle-bg: #302a20;
179
+ --color-warning-subtle-fg: #e2b56e;
180
+ --color-warning-border: #5f4e33;
181
+ /* danger: existing (unchanged) */
182
+ --color-danger-subtle-bg: #322522;
183
+ --color-danger-subtle-fg: #f0958c;
184
+ --color-danger-border: #63403b;
185
+ /* info: dusty teal-blue */
186
+ --color-info: #74a6bf;
187
+ --color-info-fg: #141413;
188
+ --color-info-subtle-bg: #22282a;
189
+ --color-info-subtle-fg: #87b4cb;
190
+ --color-info-border: #384951;
191
+ /* neutral: Warm Silver */
192
+ --color-neutral: #b0aea5;
193
+ --color-neutral-fg: #141413;
194
+ --color-neutral-subtle-bg: #292927;
195
+ --color-neutral-subtle-fg: #b4b2aa;
196
+ --color-neutral-border: #4c4c48;
197
+ }
198
+ }
199
+
200
+ :root[data-theme="dark"] {
201
+ color-scheme: dark;
202
+ --color-bg: #141413;
203
+ --color-surface: #30302e;
204
+ --color-surface-raised: #30302e;
205
+ --color-surface-muted: #262625; /* sunk below Dark Surface: header rows, hover fills */
206
+ --color-fg: #faf9f5;
207
+ --color-fg-strong: #b0aea5;
208
+ --color-fg-muted: #b0aea5;
209
+ --color-fg-subtle: #a8a79e; /* >=4.5:1 on raised and highlighted surfaces */
210
+ --color-border: #383836; /* between Dark Surface and Dark Warm: row dividers */
211
+ --color-border-strong: #3d3d3a; /* Dark Warm */
212
+ --color-ring: #3d3d3a; /* Dark Warm */
213
+ --color-ring-deep: #4d4c48; /* Charcoal Warm */
214
+ --color-inverse: #141413;
215
+ --color-inverse-fg: #b0aea5;
216
+ /* Light red + dark text: a single danger token can't pass 4.5:1 both as fill and as text on dark */
217
+ --color-danger: #ee8a80;
218
+ --color-danger-fg: #141413;
219
+ --color-input-bg: #1f1f1e; /* white 6% over bg in oklab */
220
+ --color-input-border: #81817e;
221
+ --color-input-placeholder: #9a998f;
222
+ --color-input-border-hover: #b0aea5;
223
+ --color-input-bg-hover: #262625;
224
+ --color-input-focus: #c49a6c; /* Warm Tan: primary is too dark on dark surfaces */
225
+ --color-control-checked: #c49a6c; /* Warm Tan, as the focus color */
226
+ --color-control-checked-fg: #141413; /* white on tan is only ~2.6:1; near-black is 7.2:1 */
227
+ --color-input-highlight: rgb(255 255 255 / 0.04);
228
+ --shadow-input-lift: 0 0 #0000; /* a drop shadow doesn't read on near-black */
229
+ --color-popover-bg: #30302e; /* Dark Surface */
230
+ --color-popover-border: #3d3d3a; /* Dark Warm */
231
+ --color-popover-highlight: #3d3d3a;
232
+ /* Darker and denser than light: the surface itself already separates from near-black */
233
+ --shadow-popover: 0 8px 24px rgb(0 0 0 / 0.4), 0 1px 3px rgb(0 0 0 / 0.3);
234
+ --shadow-dialog: 0 16px 48px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.3);
235
+ --color-dialog-backdrop: rgb(0 0 0 / 0.6);
236
+ /* Status tones: lightened for dark surfaces, solid-fg flips to near-black */
237
+ /* success: olive, lightened for dark; kept darker than danger for CVD */
238
+ --color-success: #758e48;
239
+ --color-success-fg: #141413;
240
+ --color-success-subtle-bg: #22251c;
241
+ --color-success-subtle-fg: #98b070;
242
+ --color-success-border: #384128;
243
+ /* warning: amber */
244
+ --color-warning: #e6b463;
245
+ --color-warning-fg: #141413;
246
+ --color-warning-subtle-bg: #302a20;
247
+ --color-warning-subtle-fg: #e2b56e;
248
+ --color-warning-border: #5f4e33;
249
+ /* danger: existing (unchanged) */
250
+ --color-danger-subtle-bg: #322522;
251
+ --color-danger-subtle-fg: #f0958c;
252
+ --color-danger-border: #63403b;
253
+ /* info: dusty teal-blue */
254
+ --color-info: #74a6bf;
255
+ --color-info-fg: #141413;
256
+ --color-info-subtle-bg: #22282a;
257
+ --color-info-subtle-fg: #87b4cb;
258
+ --color-info-border: #384951;
259
+ /* neutral: Warm Silver */
260
+ --color-neutral: #b0aea5;
261
+ --color-neutral-fg: #141413;
262
+ --color-neutral-subtle-bg: #292927;
263
+ --color-neutral-subtle-fg: #b4b2aa;
264
+ --color-neutral-border: #4c4c48;
265
+ }
266
+
267
+ /* `dark:` follows the tokens: data-theme="dark" on the root, or the system when not forced light */
268
+ @custom-variant dark {
269
+ &:where(:root[data-theme="dark"], :root[data-theme="dark"] *) {
270
+ @slot;
271
+ }
272
+
273
+ @media (prefers-color-scheme: dark) {
274
+ &:where(:root:not([data-theme="light"]), :root:not([data-theme="light"]) *) {
275
+ @slot;
276
+ }
277
+ }
278
+ }
279
+
280
+ /* Compact scrollbars with a visible, theme-aware thumb and no contrasting track. */
281
+ @utility scrollbar-subtle {
282
+ scrollbar-width: thin;
283
+ scrollbar-color: var(--color-fg-subtle) transparent;
284
+
285
+ @supports not (scrollbar-width: thin) {
286
+ &::-webkit-scrollbar {
287
+ width: 8px;
288
+ height: 8px;
289
+ }
290
+
291
+ &::-webkit-scrollbar-track {
292
+ background: transparent;
293
+ }
294
+
295
+ &::-webkit-scrollbar-thumb {
296
+ background: var(--color-fg-subtle);
297
+ border: 2px solid transparent;
298
+ border-radius: 999px;
299
+ background-clip: padding-box;
300
+ }
301
+ }
302
+
303
+ @media (forced-colors: active) {
304
+ scrollbar-color: auto;
305
+ }
306
+ }
307
+
308
+ /* Layered so the app's own utilities (e.g. a body background, `font-sans` on a title) win */
309
+ @layer base {
310
+ body {
311
+ margin: 0;
312
+ background: var(--color-bg);
313
+ color: var(--color-fg);
314
+ font-family: var(--font-sans);
315
+ line-height: 1.6;
316
+ }
317
+
318
+ /* Dialog and Drawer titles rely on this */
319
+ h1,
320
+ h2,
321
+ h3 {
322
+ font-family: var(--font-serif);
323
+ font-weight: 500;
324
+ }
325
+ }
326
+
327
+ /* Unlayered: beats Tailwind's layered utilities (e.g. `transition-*`) on cascade order alone */
328
+ @media (prefers-reduced-motion: reduce) {
329
+ *,
330
+ *::before,
331
+ *::after {
332
+ animation-duration: 0.01ms;
333
+ animation-iteration-count: 1;
334
+ transition-duration: 0.01ms;
335
+ scroll-behavior: auto;
336
+ }
337
+ }
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "@weichenlin/swell-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Hand-built React components styled with Tailwind CSS v4.",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "sideEffects": [
10
+ "**/*.css"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./theme.css": "./dist/theme.css",
18
+ "./styles.css": "./dist/styles.css",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "dependencies": {
26
+ "class-variance-authority": "^0.7.1",
27
+ "clsx": "^2.1.1",
28
+ "tailwind-merge": "^3.7.0"
29
+ },
30
+ "devDependencies": {
31
+ "@storybook/addon-a11y": "^10.6.2",
32
+ "@storybook/addon-docs": "^10.6.2",
33
+ "@storybook/react-vite": "^10.6.2",
34
+ "@tailwindcss/vite": "^4.3.3",
35
+ "@testing-library/dom": "^10.4.2",
36
+ "@testing-library/jest-dom": "^7.0.1",
37
+ "@testing-library/react": "^16.3.3",
38
+ "@testing-library/user-event": "^14.6.8",
39
+ "@types/node": "^24.13.3",
40
+ "@types/react": "^19.2.18",
41
+ "@types/react-dom": "^19.2.7",
42
+ "@vitejs/plugin-react": "^6.1.1",
43
+ "eslint-plugin-better-tailwindcss": "^4.10.0",
44
+ "jsdom": "^30.1.2",
45
+ "knip": "^6.41.0",
46
+ "lefthook": "^2.2.2",
47
+ "lucide-react": "^1.56.0",
48
+ "react": "^19.2.8",
49
+ "react-dom": "^19.2.8",
50
+ "storybook": "^10.6.2",
51
+ "tailwindcss": "^4.3.3",
52
+ "typescript": "~6.0.2",
53
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
54
+ "vite-plus": "1.3.0",
55
+ "vitest": "5.0.5"
56
+ },
57
+ "peerDependencies": {
58
+ "lucide-react": ">=1.56.0",
59
+ "react": "^19.2.0",
60
+ "react-dom": "^19.2.0",
61
+ "tailwindcss": "^4.3.0"
62
+ },
63
+ "scripts": {
64
+ "build": "vp pack",
65
+ "typecheck": "tsc -b",
66
+ "lint": "vp check",
67
+ "lint:fix": "vp check --fix",
68
+ "format": "vp fmt",
69
+ "knip": "knip",
70
+ "test": "vp test run",
71
+ "test:watch": "vp test watch",
72
+ "storybook": "NODE_OPTIONS=--disable-warning=DEP0205 storybook dev -p 6006 --no-open",
73
+ "build-storybook": "storybook build"
74
+ }
6
75
  }