@aurora-ds/theme 3.4.0-dev → 3.5.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/README.md +640 -79
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -3
- package/dist/index.d.ts +131 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,150 +1,699 @@
|
|
|
1
1
|
# Aurora Theme
|
|
2
2
|
|
|
3
|
-
A performant, type-safe
|
|
3
|
+
> A performant, type-safe and fully customizable **CSS-in-JS** theming library for React.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
- 🔒 **Type-safe** - Full TypeScript support with module augmentation
|
|
9
|
-
- 🎨 **Flexible** - Define your own theme structure
|
|
10
|
-
- 📦 **Lightweight** - No runtime dependencies besides React
|
|
5
|
+
[](https://www.npmjs.com/package/@aurora-ds/theme)
|
|
6
|
+
[](https://bundlephobia.com/package/@aurora-ds/theme)
|
|
7
|
+
[](./LICENSE)
|
|
11
8
|
|
|
9
|
+
---
|
|
12
10
|
|
|
13
11
|
## Installation
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
npm install @aurora-ds/theme
|
|
15
|
+
# or
|
|
16
|
+
yarn add @aurora-ds/theme
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @aurora-ds/theme
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
**Peer dependencies:** `react >= 18`, `react-dom >= 18`
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
19
25
|
## Quick Start
|
|
20
26
|
|
|
27
|
+
### 1 — Define your theme
|
|
28
|
+
|
|
21
29
|
```typescript
|
|
22
|
-
// theme.ts
|
|
30
|
+
// src/theme/index.ts
|
|
23
31
|
import { createTheme } from '@aurora-ds/theme'
|
|
24
32
|
|
|
25
|
-
//
|
|
33
|
+
// Your theme type
|
|
26
34
|
type MyTheme = {
|
|
27
35
|
colors: {
|
|
28
36
|
primary: string
|
|
29
|
-
secondary: string
|
|
30
37
|
background: string
|
|
31
38
|
text: string
|
|
32
39
|
}
|
|
33
|
-
spacing: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
lg: string
|
|
37
|
-
}
|
|
40
|
+
spacing: { sm: string; md: string; lg: string }
|
|
41
|
+
radius: { md: string }
|
|
42
|
+
breakpoints: { md: string; lg: string }
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// 2. Module augmentation (required for autocomplete)
|
|
44
|
+
|
|
45
|
+
// Register for global autocomplete (module augmentation)
|
|
42
46
|
declare module '@aurora-ds/theme' {
|
|
43
47
|
interface ThemeRegistry {
|
|
44
48
|
theme: MyTheme
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
```
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
// 3. Create your themes (type is inferred automatically!)
|
|
52
|
+
// Create one or many themes — type is inferred automatically
|
|
51
53
|
export const lightTheme = createTheme({
|
|
52
|
-
colors: {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
54
|
+
colors: { primary: '#6366f1', background: '#ffffff', text: '#09090b' },
|
|
55
|
+
spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
|
|
56
|
+
radius: { md: '0.375rem' },
|
|
57
|
+
breakpoints: { md: '768px', lg: '1024px' },
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export const darkTheme = createTheme({
|
|
61
|
+
colors: { primary: '#818cf8', background: '#09090b', text: '#fafafa' },
|
|
62
|
+
spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
|
|
63
|
+
radius: { md: '0.375rem' },
|
|
64
|
+
breakpoints: { md: '768px', lg: '1024px' },
|
|
63
65
|
})
|
|
64
66
|
```
|
|
65
67
|
|
|
68
|
+
### 2 — Wrap your app
|
|
69
|
+
|
|
66
70
|
```tsx
|
|
67
71
|
// App.tsx
|
|
68
|
-
import { ThemeProvider
|
|
72
|
+
import { ThemeProvider } from '@aurora-ds/theme'
|
|
69
73
|
import { lightTheme } from './theme'
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
export function App() {
|
|
76
|
+
return (
|
|
77
|
+
<ThemeProvider theme={lightTheme}>
|
|
78
|
+
<Router />
|
|
79
|
+
</ThemeProvider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3 — Style your components
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Card.styles.ts
|
|
88
|
+
import { createStyles } from '@aurora-ds/theme'
|
|
89
|
+
|
|
90
|
+
export const styles = createStyles((theme) => ({
|
|
91
|
+
root: {
|
|
92
|
+
backgroundColor: theme.colors.background, // ✅ Full autocomplete
|
|
75
93
|
color: theme.colors.text,
|
|
94
|
+
padding: theme.spacing.md,
|
|
95
|
+
borderRadius: theme.radius.md,
|
|
96
|
+
':hover': { opacity: 0.95 },
|
|
97
|
+
'@media (min-width: 768px)': { padding: theme.spacing.lg },
|
|
76
98
|
},
|
|
99
|
+
title: {
|
|
100
|
+
fontSize: '1.25rem',
|
|
101
|
+
fontWeight: 600,
|
|
102
|
+
},
|
|
103
|
+
}), { id: 'card' })
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
// Card.tsx
|
|
108
|
+
import { styles } from './Card.styles'
|
|
109
|
+
|
|
110
|
+
export function Card({ title, children }) {
|
|
111
|
+
return (
|
|
112
|
+
<div className={styles.root}>
|
|
113
|
+
<h2 className={styles.title}>{title}</h2>
|
|
114
|
+
{children}
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Core API
|
|
123
|
+
|
|
124
|
+
### `createTheme(values)`
|
|
125
|
+
|
|
126
|
+
Creates a typed theme. Type is automatically inferred from your `ThemeRegistry` declaration.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { createTheme } from '@aurora-ds/theme'
|
|
130
|
+
|
|
131
|
+
const myTheme = createTheme({
|
|
132
|
+
colors: { primary: '#6366f1', background: '#fff', text: '#000' },
|
|
133
|
+
spacing: { sm: '8px', md: '16px', lg: '24px' },
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### `ThemeProvider`
|
|
140
|
+
|
|
141
|
+
Injects theme tokens as CSS variables on `:root`. Theme changes update CSS variables instantly — **no component re-renders**.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { ThemeProvider } from '@aurora-ds/theme'
|
|
145
|
+
|
|
146
|
+
<ThemeProvider theme={currentTheme}>
|
|
147
|
+
<App />
|
|
148
|
+
</ThemeProvider>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Props
|
|
152
|
+
|
|
153
|
+
| Prop | Type | Default | Description |
|
|
154
|
+
|---|---|---|---|
|
|
155
|
+
| `theme` | `Theme` | required | The active theme |
|
|
156
|
+
| `disableTransitionsOnChange` | `boolean` | `true` | Disables CSS transitions during theme switch to prevent flashes |
|
|
157
|
+
| `transitionDuration` | `number` | — | Forces a smooth color transition (ms) instead of disabling transitions |
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// Smooth 300ms transition on theme switch
|
|
161
|
+
<ThemeProvider theme={currentTheme} transitionDuration={300}>
|
|
162
|
+
<App />
|
|
163
|
+
</ThemeProvider>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Switching themes
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
function App() {
|
|
170
|
+
const [theme, setTheme] = useState(lightTheme)
|
|
171
|
+
return (
|
|
172
|
+
<ThemeProvider theme={theme} transitionDuration={250}>
|
|
173
|
+
<button onClick={() => setTheme(t => t === lightTheme ? darkTheme : lightTheme)}>
|
|
174
|
+
Toggle dark mode
|
|
175
|
+
</button>
|
|
176
|
+
</ThemeProvider>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Child components **never re-render** — only the CSS variables on `:root` change.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### `useTheme()`
|
|
186
|
+
|
|
187
|
+
Hook to access the current theme inside a component.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { useTheme } from '@aurora-ds/theme'
|
|
191
|
+
|
|
192
|
+
function Avatar({ name }: { name: string }) {
|
|
193
|
+
const theme = useTheme()
|
|
194
|
+
return (
|
|
195
|
+
<div style={{ backgroundColor: theme.colors.primary, color: '#fff' }}>
|
|
196
|
+
{name[0]}
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> **Note:** throws if called outside a `ThemeProvider`.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### `createStyles`
|
|
207
|
+
|
|
208
|
+
The main CSS-in-JS API. Returns a map of stable class name strings.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { createStyles } from '@aurora-ds/theme'
|
|
212
|
+
|
|
213
|
+
const styles = createStyles((theme) => ({
|
|
77
214
|
button: {
|
|
78
215
|
backgroundColor: theme.colors.primary,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
216
|
+
color: 'white',
|
|
217
|
+
padding: `${theme.spacing.sm} ${theme.spacing.md}`,
|
|
218
|
+
borderRadius: theme.radius.md,
|
|
219
|
+
border: 'none',
|
|
220
|
+
cursor: 'pointer',
|
|
221
|
+
|
|
222
|
+
// Pseudo-classes
|
|
223
|
+
':hover': { opacity: 0.9 },
|
|
224
|
+
':focus-visible': { outline: `2px solid ${theme.colors.primary}` },
|
|
225
|
+
':disabled': { opacity: 0.5, cursor: 'not-allowed' },
|
|
226
|
+
|
|
227
|
+
// Attribute / child selectors
|
|
228
|
+
'&[data-loading="true"]': { color: 'transparent' },
|
|
229
|
+
'& > svg': { marginRight: theme.spacing.sm },
|
|
230
|
+
|
|
231
|
+
// At-rules
|
|
232
|
+
'@media (max-width: 480px)': { width: '100%' },
|
|
233
|
+
'@container sidebar (min-width: 300px)': { flexDirection: 'row' },
|
|
234
|
+
'@supports (display: grid)': { display: 'grid' },
|
|
235
|
+
},
|
|
84
236
|
}))
|
|
85
237
|
|
|
86
|
-
|
|
238
|
+
<button className={styles.button}>Click</button>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Supported selector syntax
|
|
242
|
+
|
|
243
|
+
| Syntax | Example | Generated |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| Pseudo-class | `':hover'` | `.cls:hover { … }` |
|
|
246
|
+
| Pseudo-element | `':before'` | `.cls:before { … }` |
|
|
247
|
+
| `&` child | `'& > span'` | `.cls > span { … }` |
|
|
248
|
+
| `&` attribute | `'&[disabled]'` | `.cls[disabled] { … }` |
|
|
249
|
+
| Media query | `'@media (…)'` | `@media (…) { .cls { … } }` |
|
|
250
|
+
| Container query | `'@container (…)'` | `@container (…) { .cls { … } }` |
|
|
251
|
+
| Supports | `'@supports (…)'` | `@supports (…) { .cls { … } }` |
|
|
252
|
+
|
|
253
|
+
#### Dynamic styles
|
|
254
|
+
|
|
255
|
+
A style key can be a **function** — Aurora caches the result per unique argument combination.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const styles = createStyles(() => ({
|
|
259
|
+
item: (active: boolean) => ({
|
|
260
|
+
backgroundColor: active ? '#6366f1' : 'transparent',
|
|
261
|
+
color: active ? 'white' : 'inherit',
|
|
262
|
+
}),
|
|
263
|
+
}))
|
|
264
|
+
|
|
265
|
+
// Cached after the first call for each unique value
|
|
266
|
+
<li className={styles.item(isSelected)}>…</li>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Explicit module id (recommended in production)
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Guarantees identical class names across SSR/CSR + between builds
|
|
273
|
+
export const styles = createStyles((theme) => ({
|
|
274
|
+
root: { padding: theme.spacing.md },
|
|
275
|
+
}), { id: 'card' })
|
|
276
|
+
// → always generates "card-root"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### `createVariants`
|
|
282
|
+
|
|
283
|
+
Declare all your component variants in a single config and get a fully-typed className builder. Inspired by CVA / Stitches, built on top of `createStyles`.
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// Button.styles.ts
|
|
287
|
+
import { createVariants } from '@aurora-ds/theme'
|
|
288
|
+
|
|
289
|
+
export const button = createVariants((theme) => ({
|
|
290
|
+
base: {
|
|
291
|
+
display: 'inline-flex',
|
|
292
|
+
alignItems: 'center',
|
|
293
|
+
gap: theme.spacing.sm,
|
|
294
|
+
border: 'none',
|
|
295
|
+
cursor: 'pointer',
|
|
296
|
+
fontWeight: 600,
|
|
297
|
+
borderRadius: theme.radius.md,
|
|
298
|
+
transition: 'all 0.2s ease',
|
|
299
|
+
':disabled': { opacity: 0.5, cursor: 'not-allowed' },
|
|
300
|
+
':hover:not(:disabled)': { transform: 'translateY(-1px)' },
|
|
301
|
+
},
|
|
302
|
+
variants: {
|
|
303
|
+
size: {
|
|
304
|
+
sm: { padding: `${theme.spacing.xs} ${theme.spacing.sm}`, fontSize: 12 },
|
|
305
|
+
md: { padding: `${theme.spacing.sm} ${theme.spacing.md}`, fontSize: 14 },
|
|
306
|
+
lg: { padding: `${theme.spacing.md} ${theme.spacing.lg}`, fontSize: 16 },
|
|
307
|
+
},
|
|
308
|
+
variant: {
|
|
309
|
+
primary: { backgroundColor: theme.colors.primary, color: 'white' },
|
|
310
|
+
secondary: { backgroundColor: theme.colors.secondary, color: theme.colors.text },
|
|
311
|
+
ghost: { backgroundColor: 'transparent', color: theme.colors.text },
|
|
312
|
+
danger: { backgroundColor: theme.colors.error, color: 'white' },
|
|
313
|
+
},
|
|
314
|
+
fullWidth: {
|
|
315
|
+
true: { width: '100%' },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
defaultVariants: { size: 'md', variant: 'primary' },
|
|
319
|
+
// Applied only when ALL conditions match simultaneously
|
|
320
|
+
compoundVariants: [
|
|
321
|
+
{ size: 'sm', variant: 'ghost', styles: { fontWeight: 400 } },
|
|
322
|
+
],
|
|
323
|
+
}), { id: 'button' })
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// Button.tsx
|
|
328
|
+
import { button } from './Button.styles'
|
|
329
|
+
|
|
330
|
+
function Button({ size, variant, fullWidth, className, children, ...props }) {
|
|
87
331
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
332
|
+
<button
|
|
333
|
+
className={button({ size, variant, fullWidth: String(fullWidth) as 'true' }, className)}
|
|
334
|
+
{...props}
|
|
335
|
+
>
|
|
336
|
+
{children}
|
|
337
|
+
</button>
|
|
93
338
|
)
|
|
94
339
|
}
|
|
340
|
+
|
|
341
|
+
// All props are optional thanks to defaultVariants
|
|
342
|
+
<Button>Primary md</Button>
|
|
343
|
+
<Button size="lg" variant="ghost">Large ghost</Button>
|
|
344
|
+
<Button variant="danger" fullWidth>Delete account</Button>
|
|
95
345
|
```
|
|
96
346
|
|
|
97
|
-
|
|
347
|
+
---
|
|
98
348
|
|
|
99
|
-
###
|
|
349
|
+
### Responsive tokens
|
|
100
350
|
|
|
101
|
-
|
|
351
|
+
Replace any CSS property value with an object keyed by breakpoint names:
|
|
102
352
|
|
|
103
353
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
354
|
+
// theme.ts — declare breakpoints
|
|
355
|
+
const myTheme = createTheme({
|
|
356
|
+
// …
|
|
357
|
+
breakpoints: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px' },
|
|
107
358
|
})
|
|
108
359
|
```
|
|
109
360
|
|
|
110
|
-
|
|
361
|
+
```typescript
|
|
362
|
+
// Card.styles.ts
|
|
363
|
+
const styles = createStyles(() => ({
|
|
364
|
+
card: {
|
|
365
|
+
padding: { base: 8, md: 16, lg: 24 }, // → 8px then 16px @md then 24px @lg
|
|
366
|
+
fontSize: { base: 14, lg: 18 },
|
|
367
|
+
gridTemplateColumns: { base: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
|
|
368
|
+
|
|
369
|
+
// Mix freely with non-responsive values
|
|
370
|
+
display: 'grid',
|
|
371
|
+
gap: 16,
|
|
372
|
+
},
|
|
373
|
+
}))
|
|
374
|
+
```
|
|
111
375
|
|
|
112
|
-
|
|
376
|
+
- **`base`** — unmediated rule (no media query, all screen sizes)
|
|
377
|
+
- Other keys must match a `theme.breakpoints` key
|
|
378
|
+
- Rules are emitted **mobile-first** in the order declared in `theme.breakpoints`
|
|
379
|
+
- `ThemeProvider` registers breakpoints automatically — no extra setup
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### `cx`
|
|
384
|
+
|
|
385
|
+
Conditionally joins class names. A zero-dependency replacement for `clsx` / `classnames`.
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { cx } from '@aurora-ds/theme'
|
|
389
|
+
|
|
390
|
+
// Simple join
|
|
391
|
+
cx('foo', 'bar') // → "foo bar"
|
|
392
|
+
|
|
393
|
+
// Conditional — falsy values are ignored
|
|
394
|
+
cx(styles.base, isActive && styles.active) // → "base" or "base active"
|
|
395
|
+
cx(styles.root, isLarge && styles.lg, props.className)
|
|
396
|
+
|
|
397
|
+
// With createVariants
|
|
398
|
+
<button className={cx(button({ size: 'lg' }), props.className)} />
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### `globalStyles`
|
|
404
|
+
|
|
405
|
+
Injects global CSS rules. Supports the same nested syntax as `createStyles`.
|
|
113
406
|
|
|
114
407
|
```typescript
|
|
115
|
-
|
|
116
|
-
|
|
408
|
+
import { globalStyles } from '@aurora-ds/theme'
|
|
409
|
+
|
|
410
|
+
// Call once at app bootstrap (e.g. in main.tsx)
|
|
411
|
+
globalStyles({
|
|
412
|
+
'html, body': {
|
|
413
|
+
margin: 0,
|
|
414
|
+
padding: 0,
|
|
415
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
416
|
+
},
|
|
417
|
+
'*': { boxSizing: 'border-box' },
|
|
418
|
+
'a': {
|
|
419
|
+
color: 'inherit',
|
|
420
|
+
':hover': { textDecoration: 'underline' },
|
|
421
|
+
},
|
|
422
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
423
|
+
'*': { animation: 'none', transition: 'none' },
|
|
424
|
+
},
|
|
425
|
+
})
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### `keyframes`
|
|
431
|
+
|
|
432
|
+
Creates a `@keyframes` rule and returns the animation name. Deduplicates identical definitions.
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import { createStyles, keyframes } from '@aurora-ds/theme'
|
|
436
|
+
|
|
437
|
+
const spin = keyframes({
|
|
438
|
+
from: { transform: 'rotate(0deg)' },
|
|
439
|
+
to: { transform: 'rotate(360deg)' },
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
const fadeIn = keyframes({
|
|
443
|
+
'0%': { opacity: 0, transform: 'translateY(8px)' },
|
|
444
|
+
'100%': { opacity: 1, transform: 'translateY(0)' },
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const styles = createStyles(() => ({
|
|
448
|
+
spinner: {
|
|
449
|
+
animation: `${spin} 0.6s linear infinite`,
|
|
450
|
+
},
|
|
451
|
+
card: {
|
|
452
|
+
animation: `${fadeIn} 0.3s ease`,
|
|
453
|
+
},
|
|
117
454
|
}))
|
|
118
|
-
// styles.root → "root-abc123"
|
|
119
455
|
```
|
|
120
456
|
|
|
121
|
-
|
|
457
|
+
---
|
|
122
458
|
|
|
123
|
-
|
|
459
|
+
### `fontFace`
|
|
460
|
+
|
|
461
|
+
Injects a `@font-face` rule and returns the font family name. Safe to call multiple times.
|
|
124
462
|
|
|
125
463
|
```typescript
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
464
|
+
import { fontFace } from '@aurora-ds/theme'
|
|
465
|
+
|
|
466
|
+
const inter = fontFace({
|
|
467
|
+
fontFamily: 'Inter',
|
|
468
|
+
src: 'url(/fonts/inter-variable.woff2) format("woff2")',
|
|
469
|
+
fontWeight: '100 900', // variable font range
|
|
470
|
+
fontDisplay: 'swap',
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
const styles = createStyles(() => ({
|
|
474
|
+
body: { fontFamily: inter }, // → 'Inter'
|
|
475
|
+
}))
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
| Option | Type | Default |
|
|
479
|
+
|---|---|---|
|
|
480
|
+
| `fontFamily` | `string` | required |
|
|
481
|
+
| `src` | `string` | required |
|
|
482
|
+
| `fontStyle` | `'normal'` \| `'italic'` \| `'oblique'` | `'normal'` |
|
|
483
|
+
| `fontWeight` | `number` \| `string` | `400` |
|
|
484
|
+
| `fontDisplay` | `'auto'` \| `'block'` \| `'swap'` \| `'fallback'` \| `'optional'` | `'swap'` |
|
|
485
|
+
| `unicodeRange` | `string` | — |
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
### CSS Variables helpers
|
|
490
|
+
|
|
491
|
+
#### `cssVar(path, fallback?)`
|
|
492
|
+
|
|
493
|
+
Returns a CSS variable reference string for any theme path.
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
import { cssVar } from '@aurora-ds/theme'
|
|
497
|
+
|
|
498
|
+
cssVar('colors.primary') // → "var(--theme-colors-primary)"
|
|
499
|
+
cssVar('spacing.md', '1rem') // → "var(--theme-spacing-md, 1rem)"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### `cssVariables(variables, options?)`
|
|
503
|
+
|
|
504
|
+
Creates CSS variable references from an object and optionally injects them on `:root`.
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { cssVariables } from '@aurora-ds/theme'
|
|
508
|
+
|
|
509
|
+
const vars = cssVariables(
|
|
510
|
+
{ primaryRgb: '99 102 241', shadowStrength: 0.1 },
|
|
511
|
+
{ prefix: 'app', inject: true }
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
vars.primaryRgb // → "var(--app-primary-rgb)"
|
|
515
|
+
vars.shadowStrength // → "var(--app-shadow-strength)"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### `injectCssVariables(theme, prefix?)`
|
|
519
|
+
|
|
520
|
+
Manually injects all theme tokens as CSS variables. Already called automatically by `ThemeProvider` — useful for non-React contexts or testing.
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { injectCssVariables } from '@aurora-ds/theme'
|
|
524
|
+
|
|
525
|
+
injectCssVariables(myTheme, 'theme')
|
|
526
|
+
// → injects --theme-colors-primary, --theme-spacing-md, etc. on :root
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
### SSR utilities
|
|
532
|
+
|
|
533
|
+
Aurora collects all CSS rules server-side in a buffer. Flush them into your HTML before hydration.
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import {
|
|
537
|
+
getSSRStyleTag,
|
|
538
|
+
clearSSRRules,
|
|
539
|
+
} from '@aurora-ds/theme'
|
|
540
|
+
|
|
541
|
+
// In your server render:
|
|
542
|
+
const html = renderToString(<App />)
|
|
543
|
+
const styleTag = getSSRStyleTag()
|
|
544
|
+
// → '<style id="aurora-styles">…all collected rules…</style>'
|
|
545
|
+
|
|
546
|
+
clearSSRRules() // Reset buffer between requests
|
|
547
|
+
|
|
548
|
+
res.send(`<!DOCTYPE html>
|
|
549
|
+
<html>
|
|
550
|
+
<head>${styleTag}</head>
|
|
551
|
+
<body><div id="root">${html}</div></body>
|
|
552
|
+
</html>`)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
| Function | Description |
|
|
556
|
+
|---|---|
|
|
557
|
+
| `getSSRStyles()` | Returns all collected CSS as a string |
|
|
558
|
+
| `getSSRStyleTag()` | Returns a ready-to-inject `<style>` tag |
|
|
559
|
+
| `getSSRRulesArray()` | Returns rules as an array of strings |
|
|
560
|
+
| `clearSSRRules()` | Resets the SSR buffer (call between requests) |
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Best practices
|
|
565
|
+
|
|
566
|
+
### Name your files `*.styles.ts`
|
|
567
|
+
|
|
568
|
+
Aurora uses this convention to generate stable development IDs and assign a dedicated `<style>` tag per module — makes HMR clean and DevTools readable.
|
|
569
|
+
|
|
570
|
+
### Always add an `id` in production
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// ✅ Guarantees stable class names across builds and SSR/CSR boundaries
|
|
574
|
+
export const styles = createStyles((theme) => ({ … }), { id: 'card' })
|
|
575
|
+
export const button = createVariants((theme) => ({ … }), { id: 'button' })
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Call `createStyles` at module top-level
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
// ✅ Called once when the module loads
|
|
582
|
+
const styles = createStyles(...)
|
|
583
|
+
|
|
584
|
+
function MyComponent() {
|
|
585
|
+
return <div className={styles.root} />
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ❌ Called on every render — don't do this
|
|
589
|
+
function MyComponent() {
|
|
590
|
+
const styles = createStyles(...)
|
|
591
|
+
return <div className={styles.root} />
|
|
129
592
|
}
|
|
130
593
|
```
|
|
131
594
|
|
|
132
|
-
|
|
595
|
+
---
|
|
133
596
|
|
|
134
|
-
|
|
597
|
+
## Complete example
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
// main.tsx
|
|
601
|
+
import { globalStyles } from '@aurora-ds/theme'
|
|
602
|
+
|
|
603
|
+
globalStyles({
|
|
604
|
+
'html, body': { margin: 0, padding: 0, fontFamily: 'system-ui, sans-serif' },
|
|
605
|
+
'*': { boxSizing: 'border-box' },
|
|
606
|
+
})
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
// Button.styles.ts
|
|
611
|
+
import { createVariants, keyframes } from '@aurora-ds/theme'
|
|
612
|
+
|
|
613
|
+
const spin = keyframes({ from: { transform: 'rotate(0deg)' }, to: { transform: 'rotate(360deg)' } })
|
|
614
|
+
|
|
615
|
+
export const button = createVariants((theme) => ({
|
|
616
|
+
base: {
|
|
617
|
+
display: 'inline-flex',
|
|
618
|
+
alignItems: 'center',
|
|
619
|
+
gap: theme.spacing.sm,
|
|
620
|
+
border: 'none',
|
|
621
|
+
cursor: 'pointer',
|
|
622
|
+
fontWeight: 600,
|
|
623
|
+
borderRadius: theme.radius.md,
|
|
624
|
+
// Responsive padding
|
|
625
|
+
padding: { base: `${theme.spacing.xs} ${theme.spacing.sm}`, md: `${theme.spacing.sm} ${theme.spacing.md}` },
|
|
626
|
+
':disabled': { opacity: 0.5, cursor: 'not-allowed' },
|
|
627
|
+
':hover:not(:disabled)': { transform: 'translateY(-1px)' },
|
|
628
|
+
},
|
|
629
|
+
variants: {
|
|
630
|
+
variant: {
|
|
631
|
+
primary: { backgroundColor: theme.colors.primary, color: 'white' },
|
|
632
|
+
ghost: { backgroundColor: 'transparent', color: theme.colors.text },
|
|
633
|
+
},
|
|
634
|
+
loading: { true: { color: 'transparent', position: 'relative' } },
|
|
635
|
+
},
|
|
636
|
+
defaultVariants: { variant: 'primary' },
|
|
637
|
+
}), { id: 'button' })
|
|
638
|
+
|
|
639
|
+
export const spinnerClass = `border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:${spin} 0.6s linear infinite;width:1em;height:1em;position:absolute`
|
|
640
|
+
```
|
|
135
641
|
|
|
136
642
|
```tsx
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
643
|
+
// Button.tsx
|
|
644
|
+
import { cx } from '@aurora-ds/theme'
|
|
645
|
+
import { button } from './Button.styles'
|
|
646
|
+
|
|
647
|
+
function Button({ variant, loading, disabled, className, children, ...props }) {
|
|
648
|
+
return (
|
|
649
|
+
<button
|
|
650
|
+
className={cx(
|
|
651
|
+
button({ variant, loading: loading ? 'true' : undefined }),
|
|
652
|
+
className
|
|
653
|
+
)}
|
|
654
|
+
disabled={disabled || loading}
|
|
655
|
+
{...props}
|
|
656
|
+
>
|
|
657
|
+
{children}
|
|
658
|
+
</button>
|
|
659
|
+
)
|
|
660
|
+
}
|
|
140
661
|
```
|
|
141
662
|
|
|
142
663
|
---
|
|
143
664
|
|
|
665
|
+
## API Reference
|
|
666
|
+
|
|
667
|
+
| Export | Type | Description |
|
|
668
|
+
|---|---|---|
|
|
669
|
+
| `createTheme` | `(values) => Theme` | Creates a typed theme |
|
|
670
|
+
| `ThemeProvider` | Component | Injects theme as CSS vars + React context |
|
|
671
|
+
| `useTheme` | Hook | Returns the current theme |
|
|
672
|
+
| `createStyles` | `(fn, opts?) => classes` | CSS-in-JS class factory |
|
|
673
|
+
| `createVariants` | `(config, opts?) => fn` | CVA-style variant builder |
|
|
674
|
+
| `cx` | `(...args) => string` | Conditional class name joiner |
|
|
675
|
+
| `globalStyles` | `(rules) => void` | Injects global CSS |
|
|
676
|
+
| `keyframes` | `(frames) => name` | Creates `@keyframes` rule |
|
|
677
|
+
| `fontFace` | `(options) => family` | Creates `@font-face` rule |
|
|
678
|
+
| `cssVar` | `(path, fallback?) => string` | Returns `var(--theme-…)` reference |
|
|
679
|
+
| `cssVariables` | `(vars, opts?) => refs` | Creates CSS variable references |
|
|
680
|
+
| `injectCssVariables` | `(theme, prefix?) => void` | Injects theme as CSS vars on `:root` |
|
|
681
|
+
| `getSSRStyles` | `() => string` | SSR: collected CSS string |
|
|
682
|
+
| `getSSRStyleTag` | `() => string` | SSR: ready `<style>` tag string |
|
|
683
|
+
| `getSSRRulesArray` | `() => string[]` | SSR: rules as array |
|
|
684
|
+
| `clearSSRRules` | `() => void` | SSR: reset buffer between requests |
|
|
685
|
+
| `colors` | object | Built-in color scales (19 palettes × 12 shades) |
|
|
686
|
+
| `ThemeRegistry` | interface | Module augmentation entry point |
|
|
687
|
+
| `StyleWithPseudos` | type | Style object with pseudo/at-rule support |
|
|
688
|
+
| `ResponsiveValue<T>` | type | `T \| { base?: T, [bp: string]: T }` |
|
|
689
|
+
| `FontFaceOptions` | type | Options for `fontFace()` |
|
|
690
|
+
| `VariantProps<V>` | type | Inferred variant props from `createVariants` |
|
|
691
|
+
|
|
692
|
+
---
|
|
144
693
|
|
|
145
694
|
## Color Scales
|
|
146
695
|
|
|
147
|
-
Aurora
|
|
696
|
+
Aurora ships built-in color scales, each with **12 shades** (25, 50, 100–900, 950).
|
|
148
697
|
|
|
149
698
|
```typescript
|
|
150
699
|
import { colors } from '@aurora-ds/theme'
|
|
@@ -156,17 +705,12 @@ colors.white // '#ffffff'
|
|
|
156
705
|
colors.black // '#000000'
|
|
157
706
|
```
|
|
158
707
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
All color scales with their hex values (25-950 shades):
|
|
162
|
-
|
|
163
|
-
#### Neutrals
|
|
708
|
+
Available scales: `gray`, `slate`, `stone`, `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`.
|
|
164
709
|
|
|
165
710
|
| Scale | 25 | 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|
|
166
|
-
|
|
711
|
+
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
167
712
|
| **gray** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
168
713
|
| **slate** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
169
|
-
| **stone** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
170
714
|
| **red** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
171
715
|
| **orange** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
172
716
|
| **amber** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
@@ -184,11 +728,28 @@ All color scales with their hex values (25-950 shades):
|
|
|
184
728
|
| **pink** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
185
729
|
| **rose** |  |  |  |  |  |  |  |  |  |  |  |  |
|
|
186
730
|
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
## TypeScript & autocomplete
|
|
734
|
+
|
|
735
|
+
Aurora uses **module augmentation** — declare your theme type once, get autocomplete everywhere.
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// src/theme.ts
|
|
739
|
+
declare module '@aurora-ds/theme' {
|
|
740
|
+
interface ThemeRegistry {
|
|
741
|
+
theme: typeof myTheme // inferred from createTheme()
|
|
742
|
+
}
|
|
743
|
+
}
|
|
187
744
|
```
|
|
188
|
-
|
|
745
|
+
|
|
746
|
+
After this:
|
|
747
|
+
- `createStyles((theme) => ...)` — `theme` is fully typed ✅
|
|
748
|
+
- `useTheme()` — returns your exact theme type ✅
|
|
749
|
+
- `createTheme(...)` — validates structure at compile time ✅
|
|
189
750
|
|
|
190
751
|
---
|
|
191
752
|
|
|
192
753
|
## License
|
|
193
754
|
|
|
194
|
-
MIT
|
|
755
|
+
MIT © [Lilian MARZET](https://github.com/LilianMrzt)
|