@saasflare/ui 1.0.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.
- package/LICENSE +23 -0
- package/dist/index.d.mts +2103 -0
- package/dist/index.d.ts +2103 -0
- package/dist/index.js +7244 -0
- package/dist/index.mjs +6866 -0
- package/fonts/index.ts +56 -0
- package/fonts/presets/default.ts +37 -0
- package/fonts/presets/distinctive.ts +43 -0
- package/fonts/presets/editorial.ts +44 -0
- package/fonts/presets/geometric.ts +37 -0
- package/fonts/presets/neutral.ts +41 -0
- package/fonts/presets/rounded.ts +38 -0
- package/package.json +90 -0
- package/styles/globals.css +12 -0
- package/styles/motion.css +53 -0
- package/styles/palettes.css +147 -0
- package/styles/surfaces.css +51 -0
- package/styles/theme.css +388 -0
package/styles/theme.css
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Saasflare core theme — semantic tokens + Tailwind bridge.
|
|
3
|
+
* @module packages/ui/styles/theme
|
|
4
|
+
* @package ui
|
|
5
|
+
* @reviewed 2026-04-19
|
|
6
|
+
*
|
|
7
|
+
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
8
|
+
* │ Two-axis design system │
|
|
9
|
+
* │ │
|
|
10
|
+
* │ BRAND → --primary-h / --primary-c / --primary-l │
|
|
11
|
+
* │ Drives --primary, --ring, and optionally tinted neutrals. │
|
|
12
|
+
* │ │
|
|
13
|
+
* │ NEUTRAL → --neutral-h / --neutral-c │
|
|
14
|
+
* │ Drives --background, --muted, --border, --card, … │
|
|
15
|
+
* │ Defaults to the brand hue with very low chroma so │
|
|
16
|
+
* │ neutrals feel subtly primary-tinted ("brand warmth"). │
|
|
17
|
+
* │ Themes that want truly achromatic greys (ink, stone) │
|
|
18
|
+
* │ set --neutral-c to 0. │
|
|
19
|
+
* └─────────────────────────────────────────────────────────────────────┘
|
|
20
|
+
*
|
|
21
|
+
* To rebrand globally: override --primary-h/c/l in your app's globals.css.
|
|
22
|
+
* To add a preset: add one selector block in palettes.css.
|
|
23
|
+
* To add a custom palette at runtime: pass <SaasflareProvider palette={…}/>.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
@import "tailwindcss";
|
|
27
|
+
@import "tw-animate-css";
|
|
28
|
+
@import "./motion.css";
|
|
29
|
+
@import "./surfaces.css";
|
|
30
|
+
@import "./palettes.css";
|
|
31
|
+
|
|
32
|
+
/* Tailwind v4 source discovery — scan the package itself so consumers
|
|
33
|
+
* don't have to declare @source in their app globals. */
|
|
34
|
+
@source "../src/components";
|
|
35
|
+
@source "../src/providers";
|
|
36
|
+
|
|
37
|
+
@custom-variant dark (&:is(.dark *));
|
|
38
|
+
|
|
39
|
+
/* ============================================
|
|
40
|
+
* Light Mode
|
|
41
|
+
* ============================================ */
|
|
42
|
+
:root {
|
|
43
|
+
/* ── Brand axis (rebrand surface) ── */
|
|
44
|
+
--primary-h: 259.1;
|
|
45
|
+
--primary-c: 0.214;
|
|
46
|
+
--primary-l: 0.623;
|
|
47
|
+
|
|
48
|
+
/* ── Neutral axis ── defaults to brand hue with tiny chroma */
|
|
49
|
+
--neutral-h: var(--primary-h);
|
|
50
|
+
--neutral-c: 0.009;
|
|
51
|
+
|
|
52
|
+
--radius: 0.625rem;
|
|
53
|
+
|
|
54
|
+
/* ── Typography axis ── purpose-named slots. Consumed by Tailwind via
|
|
55
|
+
* @theme inline below AND by the auto-apply rules in @layer base.
|
|
56
|
+
* Apps load actual fonts with next/font (variable: "--font-body" etc.)
|
|
57
|
+
* which override these baselines via the cascade. The package never
|
|
58
|
+
* bundles fonts — it only provides the vocabulary and system fallbacks.
|
|
59
|
+
*
|
|
60
|
+
* See packages/ui/examples/fonts.example.ts for the consumer pattern. */
|
|
61
|
+
--font-body: ui-sans-serif, system-ui, -apple-system, Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
62
|
+
--font-heading: var(--font-body);
|
|
63
|
+
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "JetBrains Mono", monospace;
|
|
64
|
+
|
|
65
|
+
/* ── Background & foreground ── */
|
|
66
|
+
--background: oklch(1 0 0);
|
|
67
|
+
--foreground: oklch(0.141 0.005 var(--neutral-h));
|
|
68
|
+
|
|
69
|
+
/* ── Primary (derived from brand axis) ── */
|
|
70
|
+
--primary: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
71
|
+
--primary-foreground: oklch(1 0 0);
|
|
72
|
+
|
|
73
|
+
/* ── Secondary / Accent / Muted (from neutral axis) ── */
|
|
74
|
+
--secondary: oklch(0.965 var(--neutral-c) var(--neutral-h));
|
|
75
|
+
--secondary-foreground: oklch(0.24 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
76
|
+
|
|
77
|
+
--accent: oklch(0.95 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
78
|
+
--accent-foreground: oklch(0.24 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
79
|
+
|
|
80
|
+
--muted: oklch(0.965 var(--neutral-c) var(--neutral-h));
|
|
81
|
+
--muted-foreground: oklch(0.55 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
82
|
+
|
|
83
|
+
/* ── Surfaces ── */
|
|
84
|
+
--card: oklch(1 0 0);
|
|
85
|
+
--card-foreground: oklch(0.141 0.005 var(--neutral-h));
|
|
86
|
+
--popover: oklch(1 0 0);
|
|
87
|
+
--popover-foreground: oklch(0.141 0.005 var(--neutral-h));
|
|
88
|
+
|
|
89
|
+
/* ── Borders & rings ── */
|
|
90
|
+
--border: oklch(0.915 calc(var(--neutral-c) * 0.8) var(--neutral-h));
|
|
91
|
+
--input: oklch(0.915 calc(var(--neutral-c) * 0.8) var(--neutral-h));
|
|
92
|
+
--ring: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
93
|
+
|
|
94
|
+
/* ── Semantic (fixed hues, brand-independent) ── */
|
|
95
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
96
|
+
--destructive-foreground: oklch(1 0 0);
|
|
97
|
+
--success: oklch(0.59 0.17 149);
|
|
98
|
+
--success-foreground: oklch(1 0 0);
|
|
99
|
+
--warning: oklch(0.75 0.17 65);
|
|
100
|
+
--warning-foreground: oklch(0.22 0.05 65);
|
|
101
|
+
--info: oklch(0.68 0.15 230);
|
|
102
|
+
--info-foreground: oklch(1 0 0);
|
|
103
|
+
|
|
104
|
+
/* ── Charts (offsets from primary hue) ── */
|
|
105
|
+
--chart-1: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
106
|
+
--chart-2: oklch(0.59 0.17 calc(var(--primary-h) - 110));
|
|
107
|
+
--chart-3: oklch(0.75 0.17 calc(var(--primary-h) - 194));
|
|
108
|
+
--chart-4: oklch(0.62 0.20 calc(var(--primary-h) + 51));
|
|
109
|
+
--chart-5: oklch(0.65 0.22 calc(var(--primary-h) - 237));
|
|
110
|
+
|
|
111
|
+
/* ── Sidebar ── */
|
|
112
|
+
--sidebar: oklch(0.985 calc(var(--neutral-c) * 0.6) var(--neutral-h));
|
|
113
|
+
--sidebar-foreground: oklch(0.141 0.005 var(--neutral-h));
|
|
114
|
+
--sidebar-primary: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
115
|
+
--sidebar-primary-foreground: oklch(1 0 0);
|
|
116
|
+
--sidebar-accent: oklch(0.95 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
117
|
+
--sidebar-accent-foreground: oklch(0.24 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
118
|
+
--sidebar-border: oklch(0.915 calc(var(--neutral-c) * 0.8) var(--neutral-h));
|
|
119
|
+
--sidebar-ring: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ============================================
|
|
123
|
+
* Dark Mode
|
|
124
|
+
* ============================================ */
|
|
125
|
+
.dark {
|
|
126
|
+
--primary-l: 0.65;
|
|
127
|
+
--primary-c: 0.22;
|
|
128
|
+
|
|
129
|
+
--background: oklch(0.16 calc(var(--neutral-c) * 1.5) var(--neutral-h));
|
|
130
|
+
--foreground: oklch(0.985 0 0);
|
|
131
|
+
|
|
132
|
+
--primary: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
133
|
+
--primary-foreground: oklch(1 0 0);
|
|
134
|
+
|
|
135
|
+
--secondary: oklch(0.27 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
136
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
137
|
+
|
|
138
|
+
--accent: oklch(0.27 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
139
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
140
|
+
|
|
141
|
+
--muted: oklch(0.27 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
142
|
+
--muted-foreground: oklch(0.65 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
143
|
+
|
|
144
|
+
--card: oklch(0.21 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
145
|
+
--card-foreground: oklch(0.985 0 0);
|
|
146
|
+
--popover: oklch(0.19 calc(var(--neutral-c) * 1.6) var(--neutral-h));
|
|
147
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
148
|
+
|
|
149
|
+
--border: oklch(1 0 0 / 0.10);
|
|
150
|
+
--input: oklch(1 0 0 / 0.15);
|
|
151
|
+
--ring: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
152
|
+
|
|
153
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
154
|
+
--destructive-foreground: oklch(0.15 0.03 27);
|
|
155
|
+
--success: oklch(0.68 0.19 149);
|
|
156
|
+
--success-foreground: oklch(0.15 0.03 149);
|
|
157
|
+
--warning: oklch(0.8 0.17 65);
|
|
158
|
+
--warning-foreground: oklch(0.2 0.04 65);
|
|
159
|
+
--info: oklch(0.72 0.15 230);
|
|
160
|
+
--info-foreground: oklch(0.15 0.03 230);
|
|
161
|
+
|
|
162
|
+
--chart-1: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
163
|
+
--chart-2: oklch(0.68 0.19 calc(var(--primary-h) - 110));
|
|
164
|
+
--chart-3: oklch(0.80 0.17 calc(var(--primary-h) - 194));
|
|
165
|
+
--chart-4: oklch(0.65 0.22 calc(var(--primary-h) + 51));
|
|
166
|
+
--chart-5: oklch(0.70 0.22 calc(var(--primary-h) - 237));
|
|
167
|
+
|
|
168
|
+
--sidebar: oklch(0.14 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
169
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
170
|
+
--sidebar-primary: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
171
|
+
--sidebar-primary-foreground: oklch(1 0 0);
|
|
172
|
+
--sidebar-accent: oklch(0.27 calc(var(--neutral-c) * 2) var(--neutral-h));
|
|
173
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
174
|
+
--sidebar-border: oklch(1 0 0 / 0.10);
|
|
175
|
+
--sidebar-ring: oklch(var(--primary-l) var(--primary-c) var(--primary-h));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ============================================
|
|
179
|
+
* Tailwind Theme Registration — bridges every
|
|
180
|
+
* semantic token into a Tailwind utility class.
|
|
181
|
+
* ============================================ */
|
|
182
|
+
@theme inline {
|
|
183
|
+
/* Radius */
|
|
184
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
185
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
186
|
+
--radius-lg: var(--radius);
|
|
187
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
188
|
+
|
|
189
|
+
/* Core colors */
|
|
190
|
+
--color-background: var(--background);
|
|
191
|
+
--color-foreground: var(--foreground);
|
|
192
|
+
--color-card: var(--card);
|
|
193
|
+
--color-card-foreground: var(--card-foreground);
|
|
194
|
+
--color-popover: var(--popover);
|
|
195
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
196
|
+
--color-primary: var(--primary);
|
|
197
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
198
|
+
--color-secondary: var(--secondary);
|
|
199
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
200
|
+
--color-muted: var(--muted);
|
|
201
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
202
|
+
--color-accent: var(--accent);
|
|
203
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
204
|
+
--color-destructive: var(--destructive);
|
|
205
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
206
|
+
--color-border: var(--border);
|
|
207
|
+
--color-input: var(--input);
|
|
208
|
+
--color-ring: var(--ring);
|
|
209
|
+
|
|
210
|
+
/* Semantic */
|
|
211
|
+
--color-success: var(--success);
|
|
212
|
+
--color-success-foreground: var(--success-foreground);
|
|
213
|
+
--color-warning: var(--warning);
|
|
214
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
215
|
+
--color-info: var(--info);
|
|
216
|
+
--color-info-foreground: var(--info-foreground);
|
|
217
|
+
|
|
218
|
+
/* Charts */
|
|
219
|
+
--color-chart-1: var(--chart-1);
|
|
220
|
+
--color-chart-2: var(--chart-2);
|
|
221
|
+
--color-chart-3: var(--chart-3);
|
|
222
|
+
--color-chart-4: var(--chart-4);
|
|
223
|
+
--color-chart-5: var(--chart-5);
|
|
224
|
+
|
|
225
|
+
/* Sidebar */
|
|
226
|
+
--color-sidebar: var(--sidebar);
|
|
227
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
228
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
229
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
230
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
231
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
232
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
233
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
234
|
+
|
|
235
|
+
/* Motion */
|
|
236
|
+
--duration-fast: var(--duration-fast);
|
|
237
|
+
--duration-normal: var(--duration-normal);
|
|
238
|
+
--duration-slow: var(--duration-slow);
|
|
239
|
+
--duration-overlay: var(--duration-overlay);
|
|
240
|
+
|
|
241
|
+
/* Typography — registers Tailwind utilities font-body / font-heading /
|
|
242
|
+
* font-mono that read the runtime CSS vars defined in :root above.
|
|
243
|
+
* `font-sans` is kept as a legacy alias mapped to --font-body so
|
|
244
|
+
* shadcn-pattern components using `font-sans` keep working. */
|
|
245
|
+
--font-body: var(--font-body);
|
|
246
|
+
--font-heading: var(--font-heading);
|
|
247
|
+
--font-mono: var(--font-mono);
|
|
248
|
+
--font-sans: var(--font-body);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ============================================
|
|
252
|
+
* Base Styles
|
|
253
|
+
* ============================================ */
|
|
254
|
+
html, body {
|
|
255
|
+
height: 100%;
|
|
256
|
+
box-sizing: border-box;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
html {
|
|
260
|
+
scroll-padding-top: 80px;
|
|
261
|
+
scroll-behavior: smooth;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@layer base {
|
|
265
|
+
* {
|
|
266
|
+
@apply border-border outline-ring/50;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
body {
|
|
270
|
+
@apply bg-background text-foreground;
|
|
271
|
+
font-family: var(--font-body);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
h1, h2, h3, h4, h5, h6 {
|
|
275
|
+
font-family: var(--font-heading);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
code, pre, kbd, samp {
|
|
279
|
+
font-family: var(--font-mono);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@layer utilities {
|
|
284
|
+
.text-balance {
|
|
285
|
+
text-wrap: balance;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* ============================================
|
|
290
|
+
* Cursor behavior
|
|
291
|
+
* ============================================ */
|
|
292
|
+
button,
|
|
293
|
+
[role="button"],
|
|
294
|
+
a,
|
|
295
|
+
select,
|
|
296
|
+
summary,
|
|
297
|
+
[role="tab"],
|
|
298
|
+
[role="menuitem"],
|
|
299
|
+
[role="option"],
|
|
300
|
+
[role="checkbox"],
|
|
301
|
+
[role="radio"],
|
|
302
|
+
[role="switch"] {
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
[disabled],
|
|
307
|
+
[aria-disabled="true"] {
|
|
308
|
+
cursor: not-allowed !important;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
input:not([type]),
|
|
312
|
+
input[type="text"],
|
|
313
|
+
input[type="email"],
|
|
314
|
+
input[type="password"],
|
|
315
|
+
input[type="search"],
|
|
316
|
+
input[type="url"],
|
|
317
|
+
input[type="tel"],
|
|
318
|
+
input[type="number"],
|
|
319
|
+
textarea,
|
|
320
|
+
[contenteditable="true"],
|
|
321
|
+
[role="textbox"] {
|
|
322
|
+
cursor: text;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* ============================================
|
|
326
|
+
* Focus ring doctrine
|
|
327
|
+
* ============================================ */
|
|
328
|
+
:focus-visible {
|
|
329
|
+
outline: 2px solid var(--ring);
|
|
330
|
+
outline-offset: 2px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ============================================
|
|
334
|
+
* Radius axis — discrete preset switched via [data-radius] on <html>.
|
|
335
|
+
*
|
|
336
|
+
* Baseline default is "rounded" (set in :root above as --radius: 0.625rem).
|
|
337
|
+
* "pill" overrides the entire derived scale explicitly: relying on calc()
|
|
338
|
+
* would give 9995–10003px for sm/lg, which is visually pill on tall elements
|
|
339
|
+
* but NOT pill on a 200px-wide component. Lock the whole scale instead.
|
|
340
|
+
*
|
|
341
|
+
* Custom per-palette override (CustomPalette.radius) sets --radius inline
|
|
342
|
+
* and wins by specificity — intentional escape hatch.
|
|
343
|
+
* ============================================ */
|
|
344
|
+
:root[data-radius="sharp"] { --radius: 0; }
|
|
345
|
+
:root[data-radius="soft"] { --radius: 0.25rem; }
|
|
346
|
+
:root[data-radius="rounded"] { --radius: 0.625rem; }
|
|
347
|
+
:root[data-radius="pill"] {
|
|
348
|
+
--radius: 9999px;
|
|
349
|
+
--radius-sm: 9999px;
|
|
350
|
+
--radius-md: 9999px;
|
|
351
|
+
--radius-lg: 9999px;
|
|
352
|
+
--radius-xl: 9999px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* ============================================
|
|
356
|
+
* Intent color system — components set
|
|
357
|
+
* data-intent="primary|success|warning|…" to
|
|
358
|
+
* activate intent-aware coloring via --intent.
|
|
359
|
+
* ============================================ */
|
|
360
|
+
[data-intent="primary"] {
|
|
361
|
+
--intent: var(--primary);
|
|
362
|
+
--intent-fg: var(--primary-foreground);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
[data-intent="neutral"] {
|
|
366
|
+
--intent: var(--secondary);
|
|
367
|
+
--intent-fg: var(--secondary-foreground);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
[data-intent="success"] {
|
|
371
|
+
--intent: var(--success);
|
|
372
|
+
--intent-fg: var(--success-foreground);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
[data-intent="warning"] {
|
|
376
|
+
--intent: var(--warning);
|
|
377
|
+
--intent-fg: var(--warning-foreground);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
[data-intent="danger"] {
|
|
381
|
+
--intent: var(--destructive);
|
|
382
|
+
--intent-fg: var(--destructive-foreground);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
[data-intent="info"] {
|
|
386
|
+
--intent: var(--info);
|
|
387
|
+
--intent-fg: var(--info-foreground);
|
|
388
|
+
}
|