@weichenlin/swell-ui 0.0.0-stage → 0.2.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,207 @@
1
+ /*
2
+ * swell-ui design tokens and base styles. Neutral grays; brand color is one knob (--color-primary).
3
+ * Import right after tailwindcss. Contains no utilities and no @source:
4
+ * use styles.css to also have Tailwind scan the components.
5
+ *
6
+ * Every color is written once as light-dark(<light>, <dark>). Which side applies follows
7
+ * `color-scheme` on the root: the system by default, forced by data-theme="light" / "dark".
8
+ * Tailwind's build polyfills light-dark() for browsers that lack it.
9
+ */
10
+
11
+ @theme {
12
+ /*
13
+ * Brand. The only two tokens to override for a rebrand: input focus, checked controls and
14
+ * their halos derive from these at runtime (plain var() references, no baked copies).
15
+ * Default is neutral: near-black on light, near-white on dark.
16
+ */
17
+ --color-primary: light-dark(#141414, #f9f9f9);
18
+ --color-primary-fg: light-dark(#f9f9f9, #141414);
19
+ /* Secondary brand color; no component uses it, so it just follows primary unless set */
20
+ --color-accent: var(--color-primary);
21
+
22
+ /* Surfaces and text: pure grays, contrast-checked (fg-subtle >=4.5:1 on every surface) */
23
+ --color-bg: light-dark(#f4f4f4, #141414);
24
+ --color-surface: light-dark(#f9f9f9, #303030);
25
+ --color-surface-raised: light-dark(#ffffff, #303030);
26
+ --color-surface-muted: light-dark(#e6e6e6, #262626); /* dark: sunk below surface */
27
+ --color-fg: light-dark(#141414, #f9f9f9);
28
+ --color-fg-strong: light-dark(#4c4c4c, #aeaeae);
29
+ --color-fg-muted: light-dark(#5d5d5d, #aeaeae);
30
+ --color-fg-subtle: light-dark(#676767, #aaaaaa);
31
+ --color-border: light-dark(#eeeeee, #383838); /* row dividers */
32
+ --color-border-strong: light-dark(#e6e6e6, #3d3d3d);
33
+ --color-ring: light-dark(#cfcfcf, #3d3d3d);
34
+ --color-ring-deep: light-dark(#c0c0c0, #4c4c4c);
35
+ --color-inverse: light-dark(#303030, #141414);
36
+ --color-inverse-fg: light-dark(#f9f9f9, #aeaeae);
37
+ /* Light red + dark text on dark: one danger token can't pass 4.5:1 both as fill and as text */
38
+ --color-danger: light-dark(#b53333, #ee8a80);
39
+ --color-danger-fg: light-dark(#f9f9f9, #141414);
40
+ --color-focus: #3898ec; /* Focus Blue */
41
+
42
+ /*
43
+ * Text fields: a slightly sunk solid fill (fg 4% over bg in oklab, light; white 6%, dark).
44
+ * Borders are kept >=3:1 against both the page and the fill (WCAG 1.4.11).
45
+ * Hover stacks cues (stronger border, lifted fill, faint drop shadow): a border step alone is too subtle.
46
+ */
47
+ --color-input-bg: light-dark(#eaeaea, #1f1f1f);
48
+ --color-input-border: light-dark(#7e7e7e, #818181);
49
+ --color-input-placeholder: light-dark(#696969, #989898);
50
+ --color-input-border-hover: light-dark(#4c4c4c, #aeaeae);
51
+ --color-input-bg-hover: light-dark(#f2f2f2, #262626);
52
+ --color-input-focus: var(--color-primary);
53
+ --color-input-highlight: light-dark(rgb(0 0 0 / 0.03), rgb(255 255 255 / 0.04));
54
+ --shadow-input: inset 0 1px 0 var(--color-input-highlight);
55
+ /* A drop shadow doesn't read on near-black, so it fades out on dark */
56
+ --shadow-input-lift: 0 1px 2px light-dark(rgb(0 0 0 / 0.06), transparent);
57
+ /* Focus/error halos: decorative 3px rings outside the border */
58
+ --color-input-halo: color-mix(in oklab, var(--color-input-focus) 14%, transparent);
59
+ --color-input-halo-invalid: color-mix(in oklab, var(--color-danger) 12%, transparent);
60
+ --color-input-halo-invalid-focus: color-mix(in oklab, var(--color-danger) 22%, transparent);
61
+
62
+ /*
63
+ * Selection controls (Switch, Checkbox): the checked fill and the mark drawn on it (thumb, check, dash).
64
+ * The off track/box reuses the input border, which is already >=3:1 on the page and the fill.
65
+ */
66
+ --color-control-checked: var(--color-primary);
67
+ --color-control-checked-fg: var(--color-primary-fg);
68
+ /* Thumb: a hairline plus a soft drop straight down (a sideways offset makes the small thumb look shifted) */
69
+ --shadow-switch-thumb: 0 0 0 0.5px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.2);
70
+
71
+ /*
72
+ * Popovers (the Select picker): raised above the page with a hairline and a soft shadow.
73
+ * Highlight is the hovered/keyboard-active row; same fill for both so they never disagree.
74
+ * Dark shadows are darker and denser: the surface itself already separates from near-black.
75
+ */
76
+ --color-popover-bg: light-dark(#ffffff, #303030);
77
+ --color-popover-border: light-dark(#dedede, #3d3d3d);
78
+ --color-popover-highlight: light-dark(#ededed, #3d3d3d);
79
+ --shadow-popover:
80
+ 0 8px 24px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.4)),
81
+ 0 1px 3px light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.3));
82
+ /*
83
+ * Dialogs: same surface and hairline as popovers, one elevation step higher. The backdrop is
84
+ * never blurred; it is darker on dark because a 40% dim barely reads over a near-black page.
85
+ */
86
+ --shadow-dialog:
87
+ 0 16px 48px light-dark(rgb(0 0 0 / 0.12), rgb(0 0 0 / 0.5)),
88
+ 0 2px 6px light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.3));
89
+ --color-dialog-backdrop: light-dark(rgb(20 20 20 / 0.4), rgb(0 0 0 / 0.6));
90
+
91
+ /*
92
+ * Status tones (Badge, Toast, ...). Per tone: main = solid fill / standalone icon,
93
+ * -fg = text on a solid fill, -subtle-bg/-subtle-fg = tinted badge, -border = its hairline.
94
+ * subtle-bg is main mixed over bg in oklab (14% light / 16% dark), border likewise (35% / 40%),
95
+ * baked to hex; subtle-fg is >=5:1 on subtle-bg. Meaning is carried by text + icon; hue and
96
+ * lightness are spaced so success vs danger also survives red-green CVD.
97
+ */
98
+ /* success: olive; deepest tone so it separates from danger by lightness */
99
+ --color-success: light-dark(#394d00, #758e48);
100
+ --color-success-fg: light-dark(#f9f9f9, #141414);
101
+ --color-success-subtle-bg: light-dark(#d7dbd2, #22251d);
102
+ --color-success-subtle-fg: light-dark(#3a4c0e, #98b070);
103
+ --color-success-border: light-dark(#adb79f, #384129);
104
+ /* warning: amber; dark solid-fg because light text on amber fails 4.5:1 */
105
+ --color-warning: light-dark(#a5761d, #e6b463);
106
+ --color-warning-fg: #141414;
107
+ --color-warning-subtle-bg: light-dark(#e9e2d8, #302a21);
108
+ --color-warning-subtle-fg: light-dark(#7e5600, #e2b56e);
109
+ --color-warning-border: light-dark(#d9c7ad, #5f4e34);
110
+ /* danger: main/-fg are above */
111
+ --color-danger-subtle-bg: light-dark(#efdad8, #322523);
112
+ --color-danger-subtle-fg: light-dark(#a2302f, #f0958c);
113
+ --color-danger-border: light-dark(#e5b3ae, #63403c);
114
+ /* info: muted teal-blue */
115
+ --color-info: light-dark(#3e7087, #74a6bf);
116
+ --color-info-fg: light-dark(#f9f9f9, #141414);
117
+ --color-info-subtle-bg: light-dark(#dae1e4, #22282b);
118
+ --color-info-subtle-fg: light-dark(#356075, #87b4cb);
119
+ --color-info-border: light-dark(#b3c4cd, #384952);
120
+ /* neutral: gray */
121
+ --color-neutral: light-dark(#636363, #aeaeae);
122
+ --color-neutral-fg: light-dark(#f9f9f9, #141414);
123
+ --color-neutral-subtle-bg: light-dark(#dedede, #292929);
124
+ --color-neutral-subtle-fg: light-dark(#5b5b5b, #b2b2b2);
125
+ --color-neutral-border: light-dark(#bfbfbf, #4c4c4c);
126
+
127
+ /* Fonts and radii are Tailwind's defaults; see the README to change them */
128
+ --shadow-whisper: rgba(0, 0, 0, 0.05) 0 4px 24px;
129
+ --container-page: 1200px;
130
+ }
131
+
132
+ /* Follow the system unless data-theme forces one; light-dark() above reads this */
133
+ :root {
134
+ color-scheme: light dark;
135
+ }
136
+
137
+ :root[data-theme="light"] {
138
+ color-scheme: light;
139
+ }
140
+
141
+ :root[data-theme="dark"] {
142
+ color-scheme: dark;
143
+ }
144
+
145
+ /* `dark:` follows the tokens: data-theme="dark" on the root, or the system when not forced light */
146
+ @custom-variant dark {
147
+ &:where(:root[data-theme="dark"], :root[data-theme="dark"] *) {
148
+ @slot;
149
+ }
150
+
151
+ @media (prefers-color-scheme: dark) {
152
+ &:where(:root:not([data-theme="light"]), :root:not([data-theme="light"]) *) {
153
+ @slot;
154
+ }
155
+ }
156
+ }
157
+
158
+ /* Compact scrollbars with a visible, theme-aware thumb and no contrasting track. */
159
+ @utility scrollbar-subtle {
160
+ scrollbar-width: thin;
161
+ scrollbar-color: var(--color-fg-subtle) transparent;
162
+
163
+ @supports not (scrollbar-width: thin) {
164
+ &::-webkit-scrollbar {
165
+ width: 8px;
166
+ height: 8px;
167
+ }
168
+
169
+ &::-webkit-scrollbar-track {
170
+ background: transparent;
171
+ }
172
+
173
+ &::-webkit-scrollbar-thumb {
174
+ background: var(--color-fg-subtle);
175
+ border: 2px solid transparent;
176
+ border-radius: 999px;
177
+ background-clip: padding-box;
178
+ }
179
+ }
180
+
181
+ @media (forced-colors: active) {
182
+ scrollbar-color: auto;
183
+ }
184
+ }
185
+
186
+ /* Layered so the app's own utilities (e.g. a body background) win */
187
+ @layer base {
188
+ body {
189
+ margin: 0;
190
+ background: var(--color-bg);
191
+ color: var(--color-fg);
192
+ font-family: var(--font-sans);
193
+ line-height: 1.6;
194
+ }
195
+ }
196
+
197
+ /* Unlayered: beats Tailwind's layered utilities (e.g. `transition-*`) on cascade order alone */
198
+ @media (prefers-reduced-motion: reduce) {
199
+ *,
200
+ *::before,
201
+ *::after {
202
+ animation-duration: 0.01ms;
203
+ animation-iteration-count: 1;
204
+ transition-duration: 0.01ms;
205
+ scroll-behavior: auto;
206
+ }
207
+ }
@@ -0,0 +1,63 @@
1
+ /*
2
+ * Warm theme: parchment surfaces, warm grays and a brown primary (swell-ui's original look).
3
+ * Import after swell-ui's styles.css (or theme.css); add typography.css for the serif headings.
4
+ * Overrides the color tokens only; input focus and checked controls still derive from primary.
5
+ */
6
+ @theme {
7
+ /* Warm Brown; Warm Tan on dark, where the brown is too dark against the page */
8
+ --color-primary: light-dark(#8b5e3c, #c49a6c);
9
+ --color-primary-fg: light-dark(#faf9f5, #141413);
10
+ --color-accent: #c49a6c; /* Warm Tan */
11
+
12
+ --color-bg: light-dark(#f5f4ed, #141413); /* Parchment */
13
+ --color-surface: light-dark(#faf9f5, #30302e); /* Ivory / Dark Surface */
14
+ --color-surface-raised: light-dark(#ffffff, #30302e);
15
+ --color-surface-muted: light-dark(#e8e6dc, #262625); /* Warm Sand */
16
+ --color-fg: light-dark(#141413, #faf9f5);
17
+ --color-fg-strong: light-dark(#4d4c48, #b0aea5);
18
+ --color-fg-muted: light-dark(#5e5d59, #b0aea5);
19
+ --color-fg-subtle: light-dark(#686761, #a8a79e);
20
+ --color-border: light-dark(#f0eee6, #383836);
21
+ --color-border-strong: light-dark(#e8e6dc, #3d3d3a);
22
+ --color-ring: light-dark(#d1cfc5, #3d3d3a);
23
+ --color-ring-deep: light-dark(#c2c0b6, #4d4c48);
24
+ --color-inverse: light-dark(#30302e, #141413);
25
+ --color-inverse-fg: light-dark(#faf9f5, #b0aea5);
26
+ --color-danger: light-dark(#b53333, #ee8a80); /* Error Crimson */
27
+ --color-danger-fg: light-dark(#faf9f5, #141413);
28
+
29
+ --color-input-bg: light-dark(#ebeae3, #1f1f1e);
30
+ --color-input-border: light-dark(#7f7e7b, #81817e);
31
+ --color-input-placeholder: light-dark(#6a6964, #9a998f);
32
+ --color-input-border-hover: light-dark(#4d4c48, #b0aea5);
33
+ --color-input-bg-hover: light-dark(#f3f2ec, #262625);
34
+
35
+ --color-popover-bg: light-dark(#ffffff, #30302e);
36
+ --color-popover-border: light-dark(#e0ded4, #3d3d3a);
37
+ --color-popover-highlight: light-dark(#efede5, #3d3d3a);
38
+ --color-dialog-backdrop: light-dark(rgb(20 20 19 / 0.4), rgb(0 0 0 / 0.6));
39
+
40
+ --color-success: light-dark(#394d00, #758e48);
41
+ --color-success-fg: light-dark(#faf9f5, #141413);
42
+ --color-success-subtle-bg: light-dark(#d8dbcc, #22251c);
43
+ --color-success-subtle-fg: light-dark(#3a4c0e, #98b070);
44
+ --color-success-border: light-dark(#aeb79b, #384128);
45
+ --color-warning: light-dark(#a5761d, #e6b463);
46
+ --color-warning-fg: #141413;
47
+ --color-warning-subtle-bg: light-dark(#eae2d1, #302a20);
48
+ --color-warning-subtle-fg: light-dark(#7e5600, #e2b56e);
49
+ --color-warning-border: light-dark(#d9c7a9, #5f4e33);
50
+ --color-danger-subtle-bg: light-dark(#f0dad2, #322522);
51
+ --color-danger-subtle-fg: light-dark(#a2302f, #f0958c);
52
+ --color-danger-border: light-dark(#e5b3aa, #63403b);
53
+ --color-info: light-dark(#3e7087, #74a6bf);
54
+ --color-info-fg: light-dark(#faf9f5, #141413);
55
+ --color-info-subtle-bg: light-dark(#dbe1de, #22282a);
56
+ --color-info-subtle-fg: light-dark(#356075, #87b4cb);
57
+ --color-info-border: light-dark(#b4c4c9, #384951);
58
+ --color-neutral: light-dark(#66635c, #b0aea5);
59
+ --color-neutral-fg: light-dark(#faf9f5, #141413);
60
+ --color-neutral-subtle-bg: light-dark(#e0ded7, #292927);
61
+ --color-neutral-subtle-fg: light-dark(#5e5b55, #b4b2aa);
62
+ --color-neutral-border: light-dark(#c0bfb8, #4c4c48);
63
+ }
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Opt-in editorial headings: h1-h3 in the serif stack at weight 500.
3
+ * Import after swell-ui's styles.css (or theme.css). Layered, so utilities on a heading still win.
4
+ */
5
+ @layer base {
6
+ h1,
7
+ h2,
8
+ h3 {
9
+ font-family: var(--font-serif);
10
+ font-weight: 500;
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,77 @@
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.2.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
+ "./typography.css": "./dist/typography.css",
20
+ "./themes/warm.css": "./dist/themes/warm.css",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org/"
26
+ },
27
+ "dependencies": {
28
+ "class-variance-authority": "^0.7.1",
29
+ "clsx": "^2.1.1",
30
+ "tailwind-merge": "^3.7.0"
31
+ },
32
+ "devDependencies": {
33
+ "@storybook/addon-a11y": "^10.6.2",
34
+ "@storybook/addon-docs": "^10.6.2",
35
+ "@storybook/react-vite": "^10.6.2",
36
+ "@tailwindcss/vite": "^4.3.3",
37
+ "@testing-library/dom": "^10.4.2",
38
+ "@testing-library/jest-dom": "^7.0.1",
39
+ "@testing-library/react": "^16.3.3",
40
+ "@testing-library/user-event": "^14.6.8",
41
+ "@types/node": "^24.13.3",
42
+ "@types/react": "^19.2.18",
43
+ "@types/react-dom": "^19.2.7",
44
+ "@vitejs/plugin-react": "^6.1.1",
45
+ "eslint-plugin-better-tailwindcss": "^4.10.0",
46
+ "jsdom": "^30.1.2",
47
+ "knip": "^6.41.0",
48
+ "lefthook": "^2.2.2",
49
+ "lucide-react": "^1.56.0",
50
+ "react": "^19.2.8",
51
+ "react-dom": "^19.2.8",
52
+ "storybook": "^10.6.2",
53
+ "tailwindcss": "^4.3.3",
54
+ "typescript": "~6.0.2",
55
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
56
+ "vite-plus": "1.3.0",
57
+ "vitest": "5.0.5"
58
+ },
59
+ "peerDependencies": {
60
+ "lucide-react": ">=1.56.0",
61
+ "react": "^19.2.0",
62
+ "react-dom": "^19.2.0",
63
+ "tailwindcss": "^4.3.0"
64
+ },
65
+ "scripts": {
66
+ "build": "vp pack",
67
+ "typecheck": "tsc -b",
68
+ "lint": "vp check",
69
+ "lint:fix": "vp check --fix",
70
+ "format": "vp fmt",
71
+ "knip": "knip",
72
+ "test": "vp test run",
73
+ "test:watch": "vp test watch",
74
+ "storybook": "NODE_OPTIONS=--disable-warning=DEP0205 storybook dev -p 6006 --no-open",
75
+ "build-storybook": "storybook build"
76
+ }
6
77
  }