@tenphi/tasty 3.4.0 → 3.6.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 +147 -129
- package/dist/astro-CzY4LCpr.js +437 -0
- package/dist/astro-CzY4LCpr.js.map +1 -0
- package/dist/{babel-BUQGeOXA.d.ts → babel-DWCsafYw.d.ts} +2 -2
- package/dist/{collector-DUaHCcTS.d.ts → collector-BTVXj2hS.d.ts} +21 -3
- package/dist/{collector-DTahQUiV.js → collector-C6TtL8HJ.js} +36 -13
- package/dist/collector-C6TtL8HJ.js.map +1 -0
- package/dist/{config-B5kHzuNz.js → config-B3gPdCqd.js} +305 -153
- package/dist/config-B3gPdCqd.js.map +1 -0
- package/dist/{config-YsxGv4tq.d.ts → config-LfIDmVHx.d.ts} +132 -32
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +6 -6
- package/dist/{core-Dr4u1NVD.js → core-Bq7w2kti.js} +215 -95
- package/dist/core-Bq7w2kti.js.map +1 -0
- package/dist/{css-writer-B-J87ncv.js → css-writer-Bgowobbx.js} +3 -3
- package/dist/{css-writer-B-J87ncv.js.map → css-writer-Bgowobbx.js.map} +1 -1
- package/dist/{format-rules-DKOA-6qu.js → format-rules-rCZ37rqY.js} +2 -2
- package/dist/format-rules-rCZ37rqY.js.map +1 -0
- package/dist/{hydrate-OeMX99We.js → hydrate-CNOmZprz.js} +3 -3
- package/dist/{hydrate-OeMX99We.js.map → hydrate-CNOmZprz.js.map} +1 -1
- package/dist/{index-PqN-DIpn.d.ts → index-Bm9ltvdH.d.ts} +38 -16
- package/dist/{index-Cd45t5NM.d.ts → index-BmogEzTo.d.ts} +69 -15
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/{keyframes-CV8azJf3.js → keyframes-DE-OE76F.js} +45 -3
- package/dist/keyframes-DE-OE76F.js.map +1 -0
- package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BcLl1u_d.d.ts} +2 -2
- package/dist/{merge-styles-oklji0KB.js → merge-styles-DuoZEsm9.js} +2 -2
- package/dist/{merge-styles-oklji0KB.js.map → merge-styles-DuoZEsm9.js.map} +1 -1
- package/dist/{resolve-recipes-DTG81rzl.js → resolve-recipes-H9NqOQuP.js} +3 -3
- package/dist/{resolve-recipes-DTG81rzl.js.map → resolve-recipes-H9NqOQuP.js.map} +1 -1
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract-static.js +9 -0
- package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
- package/dist/ssr/astro-middleware-extract.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract.js +9 -0
- package/dist/ssr/astro-middleware-extract.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +3 -1
- package/dist/ssr/astro-middleware-static.js +1 -1
- package/dist/ssr/astro-middleware.d.ts +3 -1
- package/dist/ssr/astro-middleware.js +1 -1
- package/dist/ssr/astro.d.ts +45 -3
- package/dist/ssr/astro.js +1 -156
- package/dist/ssr/index.d.ts +2 -2
- package/dist/ssr/index.js +3 -3
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +4 -4
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +4 -4
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/docs/README.md +13 -8
- package/docs/adoption.md +56 -31
- package/docs/ai-agents.md +78 -77
- package/docs/comparison.md +69 -40
- package/docs/configuration.md +97 -87
- package/docs/debug.md +4 -2
- package/docs/design-system.md +41 -22
- package/docs/dsl.md +225 -195
- package/docs/getting-started.md +60 -39
- package/docs/injector.md +63 -26
- package/docs/methodology.md +42 -36
- package/docs/migration-v3.md +49 -49
- package/docs/plugins.md +37 -33
- package/docs/react-api.md +65 -44
- package/docs/runtime-benchmarks.md +178 -0
- package/docs/ssr.md +124 -61
- package/docs/styles.md +1 -1
- package/docs/tasty-static.md +137 -104
- package/package.json +26 -8
- package/dist/collector-DTahQUiV.js.map +0 -1
- package/dist/config-B5kHzuNz.js.map +0 -1
- package/dist/core-Dr4u1NVD.js.map +0 -1
- package/dist/format-rules-DKOA-6qu.js.map +0 -1
- package/dist/keyframes-CV8azJf3.js.map +0 -1
- package/dist/ssr/astro.js.map +0 -1
package/docs/configuration.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Configuration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tasty works without upfront configuration. Use `configure()` when your app or design system is ready to define shared tokens, state aliases, units, recipes, presets, or extension points.
|
|
4
|
+
|
|
5
|
+
Call it **before any styles are generated** (before first render). For a guided setup, see [Getting Started](getting-started.md). For the higher-level docs map, see the [Docs Hub](README.md).
|
|
4
6
|
|
|
5
7
|
```jsx
|
|
6
8
|
import { configure } from '@tenphi/tasty';
|
|
@@ -45,35 +47,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
|
|
|
45
47
|
|
|
46
48
|
## Options
|
|
47
49
|
|
|
48
|
-
| Option
|
|
49
|
-
|
|
50
|
-
| `nonce`
|
|
51
|
-
| `maxRulesPerSheet`
|
|
52
|
-
| `forceTextInjection` | `boolean`
|
|
53
|
-
| `devMode`
|
|
54
|
-
| `states`
|
|
55
|
-
| `parserCacheSize`
|
|
56
|
-
| `units`
|
|
57
|
-
| `functions`
|
|
58
|
-
| `handlers`
|
|
59
|
-
| `propHandlers`
|
|
60
|
-
| `baseStyleProps`
|
|
61
|
-
| `tokens`
|
|
62
|
-
| `replaceTokens`
|
|
63
|
-
| `keyframes`
|
|
64
|
-
| `properties`
|
|
65
|
-
| `fontFaces`
|
|
66
|
-
| `counterStyles`
|
|
67
|
-
| `polyfills`
|
|
68
|
-
| `autoPropertyTypes`
|
|
69
|
-
| `recipes`
|
|
70
|
-
| `presets`
|
|
71
|
-
| `globalStyles`
|
|
72
|
-
| `plugins`
|
|
73
|
-
| `gc`
|
|
74
|
-
| `batchInjection`
|
|
75
|
-
| `colorSpace`
|
|
76
|
-
| `namePrefix`
|
|
50
|
+
| Option | Type | Default | Description |
|
|
51
|
+
| -------------------- | ------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `nonce` | `string` | - | CSP nonce for style elements |
|
|
53
|
+
| `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
|
|
54
|
+
| `forceTextInjection` | `boolean` | auto (`true` in test envs) | Force text-node CSS injection instead of constructable stylesheets |
|
|
55
|
+
| `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info |
|
|
56
|
+
| `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
|
|
57
|
+
| `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
|
|
58
|
+
| `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
|
|
59
|
+
| `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
|
|
60
|
+
| `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
|
|
61
|
+
| `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
|
|
62
|
+
| `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
|
|
63
|
+
| `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
|
|
64
|
+
| `replaceTokens` | `Record<string, string \| number \| boolean>` | - | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
|
|
65
|
+
| `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
|
|
66
|
+
| `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
|
|
67
|
+
| `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
|
|
68
|
+
| `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
|
|
69
|
+
| `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
|
|
70
|
+
| `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
|
|
71
|
+
| `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
|
|
72
|
+
| `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
|
|
73
|
+
| `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
|
|
74
|
+
| `plugins` | `TastyPlugin[]` | - | Plugins that bundle any of the above (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
|
|
75
|
+
| `gc` | `GCConfig` | - | Garbage-collection tuning for unused styles (`{ touchInterval, capacity }`) |
|
|
76
|
+
| `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
|
|
77
|
+
| `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
|
|
78
|
+
| `namePrefix` | `string` | `'t'` (`tasty`) / `'ts'` (build-time) | Prefix prepended to every generated identifier (class, keyframe, counter-style names). Must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$`. See [Name prefix](#name-prefix). |
|
|
77
79
|
|
|
78
80
|
---
|
|
79
81
|
|
|
@@ -107,10 +109,10 @@ createRoot(el).render(
|
|
|
107
109
|
### Why the provider is required
|
|
108
110
|
|
|
109
111
|
Deferring a write past React's layout phase would let a `useLayoutEffect`
|
|
110
|
-
measure an element whose rules are not in the sheet yet and read its
|
|
112
|
+
measure an element whose rules are not in the sheet yet and read its _unstyled_
|
|
111
113
|
box — a wrong number, not a stale one, so it never self-corrects.
|
|
112
114
|
|
|
113
|
-
`TastyBatchProvider` closes that hole. It opens a
|
|
115
|
+
`TastyBatchProvider` closes that hole. It opens a _batch window_ during its
|
|
114
116
|
render and closes it — flushing — in `useInsertionEffect`, which React runs in
|
|
115
117
|
the mutation phase, before any layout effect:
|
|
116
118
|
|
|
@@ -135,11 +137,11 @@ commits it takes part in.
|
|
|
135
137
|
|
|
136
138
|
### Modes
|
|
137
139
|
|
|
138
|
-
| Value
|
|
139
|
-
|
|
140
|
-
| `false` (default) | One `insertRule()` per component, synchronously.
|
|
141
|
-
| `true`
|
|
142
|
-
| `'always'`
|
|
140
|
+
| Value | Behaviour |
|
|
141
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `false` (default) | One `insertRule()` per component, synchronously. |
|
|
143
|
+
| `true` | Batch inside a provider window only. Cannot affect measurement. Without the provider nothing is batched, and dev mode says so once. |
|
|
144
|
+
| `'always'` | Batch every injection, flushing on a microtask when no window is open. Covers more commits; a `useLayoutEffect` measuring a freshly mounted element can read its unstyled box. Paint is unaffected — microtasks always drain before the browser paints. |
|
|
143
145
|
|
|
144
146
|
### Ordering
|
|
145
147
|
|
|
@@ -148,7 +150,7 @@ All writes share one FIFO queue — component rules, global rules, raw CSS,
|
|
|
148
150
|
Draining it in insertion order keeps the sheet byte-identical to unbatched
|
|
149
151
|
output, which matters because equal-specificity rules resolve by document order.
|
|
150
152
|
|
|
151
|
-
###
|
|
153
|
+
### Client rendering, server rendering, and build-time extraction
|
|
152
154
|
|
|
153
155
|
**Server render — nothing to batch, safe to leave enabled.** SSR and RSC collect
|
|
154
156
|
CSS as text through `ServerStyleCollector`; the runtime injector never runs
|
|
@@ -173,12 +175,12 @@ tasty components to be worth it. `'always'` needs no provider and covers every
|
|
|
173
175
|
island, at the cost of the measurement hazard above. Either way, an island that
|
|
174
176
|
only re-hydrates server-rendered classes has nothing to batch.
|
|
175
177
|
|
|
176
|
-
**
|
|
178
|
+
**Build-time extraction (`tastyStatic`) — unaffected.** The Babel plugin never
|
|
177
179
|
touches the injector: the babel plugin emits either a CSS file import or an
|
|
178
180
|
`injectCSS()` call from `@tenphi/tasty/static/inject`, which appends text to a
|
|
179
181
|
single `<style data-tasty-static>` element. `batchInjection` only defers CSSOM
|
|
180
|
-
writes made by the
|
|
181
|
-
that mixes both, it still applies to the
|
|
182
|
+
writes made by the browser injector, so extracted styles are unchanged. In an app
|
|
183
|
+
that mixes both, it still applies to the `tasty()` half.
|
|
182
184
|
|
|
183
185
|
### `flushStyles()`
|
|
184
186
|
|
|
@@ -282,7 +284,7 @@ color syntax works on every `<color>`, including a `color-mix()`, a
|
|
|
282
284
|
|
|
283
285
|
## Name Prefix
|
|
284
286
|
|
|
285
|
-
Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The
|
|
287
|
+
Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The `tasty()` client, SSR, and RSC paths default to `'t'`; the build-time extraction path (`tastyStatic` via the Babel plugin) defaults to `'ts'` so extracted classes can never collide with `tasty()` classes when both are loaded on the same page.
|
|
286
288
|
|
|
287
289
|
```jsx
|
|
288
290
|
configure({
|
|
@@ -292,12 +294,12 @@ configure({
|
|
|
292
294
|
|
|
293
295
|
The prefix is prepended verbatim to the hash, so include any separator inside the prefix string itself:
|
|
294
296
|
|
|
295
|
-
| Setting
|
|
296
|
-
|
|
297
|
-
| `'t'` (
|
|
298
|
-
| `'ts'` (
|
|
299
|
-
| `'mb'`
|
|
300
|
-
| `'myapp-'`
|
|
297
|
+
| Setting | Class | Keyframe | Counter-style |
|
|
298
|
+
| --------------------------- | ------------- | -------------- | -------------- |
|
|
299
|
+
| `'t'` (`tasty` default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
|
|
300
|
+
| `'ts'` (build-time default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
|
|
301
|
+
| `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
|
|
302
|
+
| `'myapp-'` | `myapp-1a2b3` | `myapp-k1a2b3` | `myapp-c1a2b3` |
|
|
301
303
|
|
|
302
304
|
The single-letter discriminators (`k` for keyframes, `c` for counter-styles) keep the three kinds visually distinct in devtools — they are not required for correctness because CSS keeps these in separate namespaces.
|
|
303
305
|
|
|
@@ -307,12 +309,12 @@ The single-letter discriminators (`k` for keyframes, `c` for counter-styles) kee
|
|
|
307
309
|
- Validated at `configure()` time; an invalid prefix throws synchronously rather than silently producing broken hydration.
|
|
308
310
|
- Locked once styles have been generated, like all other config.
|
|
309
311
|
|
|
310
|
-
### Coexistence with
|
|
312
|
+
### Coexistence with build-time extraction
|
|
311
313
|
|
|
312
|
-
The
|
|
314
|
+
The `tasty()` and `tastyStatic()` paths **must use different prefixes** when both are loaded on the same page. Defaults already guarantee this; if you customize one, customize the other accordingly:
|
|
313
315
|
|
|
314
316
|
```jsx
|
|
315
|
-
// app config (
|
|
317
|
+
// app config (client / SSR / RSC)
|
|
316
318
|
configure({ namePrefix: 'mb' });
|
|
317
319
|
|
|
318
320
|
// tasty-zero.config.ts (Babel plugin)
|
|
@@ -332,8 +334,8 @@ Tokens support state maps for responsive or theme-aware values:
|
|
|
332
334
|
```jsx
|
|
333
335
|
configure({
|
|
334
336
|
tokens: {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
+
$gap: '4px',
|
|
338
|
+
$radius: '6px',
|
|
337
339
|
'#primary': {
|
|
338
340
|
'': '#purple',
|
|
339
341
|
'@dark': '#light-purple',
|
|
@@ -350,7 +352,7 @@ configure({
|
|
|
350
352
|
- `#name` keys become `--name-color` custom properties
|
|
351
353
|
- Names keep their case, since CSS custom properties are case-sensitive: `$myVar` is `--myVar` and is referenced as `$myVar`. A leading capital is not supported and folds — `$Foo` and `#Purple` become `--foo` and `--purple-color` — so start names lowercase. Kebab-case (`$my-var`) remains the convention.
|
|
352
354
|
|
|
353
|
-
Tokens are automatically emitted in all
|
|
355
|
+
Tokens are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin.
|
|
354
356
|
|
|
355
357
|
---
|
|
356
358
|
|
|
@@ -404,7 +406,7 @@ configure({
|
|
|
404
406
|
});
|
|
405
407
|
```
|
|
406
408
|
|
|
407
|
-
Now any component can use `
|
|
409
|
+
Now any component can use `font: '"Brand Sans", sans-serif'` and the browser will already have the `@font-face` rules in the stylesheet.
|
|
408
410
|
|
|
409
411
|
See [Font Face (`@font-face`)](dsl.md#font-face-font-face) for inline usage inside component styles and the full list of supported descriptors.
|
|
410
412
|
|
|
@@ -461,7 +463,7 @@ configure({
|
|
|
461
463
|
});
|
|
462
464
|
```
|
|
463
465
|
|
|
464
|
-
Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `
|
|
466
|
+
Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `margin: '$$negative(10px) top'`.
|
|
465
467
|
|
|
466
468
|
> A key whose prefix doesn't match its value type (an object under a bare key, or a function under a `$$` key) is **ignored with a dev-mode warning**.
|
|
467
469
|
|
|
@@ -511,7 +513,7 @@ configure({
|
|
|
511
513
|
},
|
|
512
514
|
});
|
|
513
515
|
|
|
514
|
-
// Now `
|
|
516
|
+
// Now `margin: '$$negative(10px) top'` renders `margin: calc(-1 * 10px) 0 0 0`
|
|
515
517
|
// — no native @function rule is emitted.
|
|
516
518
|
```
|
|
517
519
|
|
|
@@ -604,7 +606,7 @@ configure({
|
|
|
604
606
|
color: '#text',
|
|
605
607
|
preset: 't2',
|
|
606
608
|
margin: 0,
|
|
607
|
-
|
|
609
|
+
font: 'system-ui, sans-serif',
|
|
608
610
|
},
|
|
609
611
|
html: {
|
|
610
612
|
overflow: 'hidden',
|
|
@@ -615,7 +617,7 @@ configure({
|
|
|
615
617
|
|
|
616
618
|
Each key is a CSS selector, and each value is a Tasty `Styles` object supporting the full style syntax including style properties, tokens, state maps, and selector-based sub-styling (e.g. `$: '> .app'` for elements outside React scope). Global styles are injected alongside `:root` tokens when the first style is rendered.
|
|
617
619
|
|
|
618
|
-
Global styles are automatically emitted in all
|
|
620
|
+
Global styles are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin. Plugins can also provide `globalStyles`; they are merged per selector with config global styles (config wins on conflict).
|
|
619
621
|
|
|
620
622
|
---
|
|
621
623
|
|
|
@@ -623,18 +625,18 @@ Global styles are automatically emitted in all rendering modes: runtime (client)
|
|
|
623
625
|
|
|
624
626
|
CSS cannot transition or animate custom properties unless their type is declared via [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/@property). Tasty handles this automatically — when a custom property is assigned a concrete value (e.g. `'$scale': 1`, `'$gap': '10px'`, `'#accent': 'purple'`), the type is inferred and a `@property` rule is registered.
|
|
625
627
|
|
|
626
|
-
This works across all declaration contexts: component styles, `@keyframes`, global config, and the
|
|
628
|
+
This works across all declaration contexts: component styles, `@keyframes`, global config, and build-time extraction with the Babel plugin. It also resolves `var()` chains — if `$a` references `var(--b)`, the type propagates once `--b` is resolved.
|
|
627
629
|
|
|
628
630
|
Supported types:
|
|
629
631
|
|
|
630
|
-
| Detection
|
|
631
|
-
|
|
632
|
-
| `1`, `0.5`, `-3` (bare numbers)
|
|
633
|
-
| `10px`, `2rem`, `100vw` (length units) | `<length>`
|
|
634
|
-
| `50%`
|
|
635
|
-
| `45deg`, `0.5turn` (angle units)
|
|
636
|
-
| `300ms`, `1s` (time units)
|
|
637
|
-
| `#name` tokens (by naming convention)
|
|
632
|
+
| Detection | Inferred syntax |
|
|
633
|
+
| -------------------------------------- | --------------- |
|
|
634
|
+
| `1`, `0.5`, `-3` (bare numbers) | `<number>` |
|
|
635
|
+
| `10px`, `2rem`, `100vw` (length units) | `<length>` |
|
|
636
|
+
| `50%` | `<percentage>` |
|
|
637
|
+
| `45deg`, `0.5turn` (angle units) | `<angle>` |
|
|
638
|
+
| `300ms`, `1s` (time units) | `<time>` |
|
|
639
|
+
| `#name` tokens (by naming convention) | `<color>` |
|
|
638
640
|
|
|
639
641
|
Auto-inferred properties use `inherits: true` (the CSS default). Use explicit `@property` when you need different settings:
|
|
640
642
|
|
|
@@ -666,11 +668,11 @@ configure({ autoPropertyTypes: false });
|
|
|
666
668
|
|
|
667
669
|
Override or extend the built-in style property handlers. A handler definition can take three forms:
|
|
668
670
|
|
|
669
|
-
| Form
|
|
670
|
-
|
|
671
|
-
| Function only | `handler`
|
|
672
|
-
| Single dep
|
|
673
|
-
| Multi dep
|
|
671
|
+
| Form | Syntax | Description |
|
|
672
|
+
| ------------- | ---------------------------------- | --------------------------------------------------------------- |
|
|
673
|
+
| Function only | `handler` | Triggered by its key name; receives only that property |
|
|
674
|
+
| Single dep | `['styleName', handler]` | Triggered by the specified style property |
|
|
675
|
+
| Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
|
|
674
676
|
|
|
675
677
|
The multi-dep form is useful when output depends on several style properties together (e.g., `gap` needs to know `display` and `flow` to decide the CSS strategy). Use `defineHandler` for it and the dependency types are inferred from the dependency list, so a typo in the destructure is a type error instead of a silent `undefined`.
|
|
676
678
|
|
|
@@ -692,11 +694,19 @@ configure({
|
|
|
692
694
|
// them all through, or the ones you leave out stop working (see below).
|
|
693
695
|
fill: defineHandler(
|
|
694
696
|
[
|
|
695
|
-
'fill',
|
|
696
|
-
'
|
|
697
|
+
'fill',
|
|
698
|
+
'backgroundColor',
|
|
699
|
+
'image',
|
|
700
|
+
'backgroundImage',
|
|
701
|
+
'backgroundPosition',
|
|
702
|
+
'backgroundSize',
|
|
703
|
+
'backgroundRepeat',
|
|
697
704
|
],
|
|
698
705
|
(props) => {
|
|
699
|
-
if (
|
|
706
|
+
if (
|
|
707
|
+
typeof props.fill === 'string' &&
|
|
708
|
+
props.fill.startsWith('gradient:')
|
|
709
|
+
) {
|
|
700
710
|
return { background: props.fill.slice(9) };
|
|
701
711
|
}
|
|
702
712
|
return styleHandlers.fill(props);
|
|
@@ -730,18 +740,18 @@ Built-in handlers are shared across several style names. Registering a handler f
|
|
|
730
740
|
|
|
731
741
|
The shared groups worth knowing:
|
|
732
742
|
|
|
733
|
-
| Registering a handler for
|
|
734
|
-
|
|
735
|
-
| `fill`
|
|
736
|
-
| `display`
|
|
737
|
-
| `preset`
|
|
738
|
-
| `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands
|
|
743
|
+
| Registering a handler for | Also takes over |
|
|
744
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
745
|
+
| `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
|
|
746
|
+
| `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
|
|
747
|
+
| `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
|
|
748
|
+
| `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
|
|
739
749
|
|
|
740
750
|
Either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a name that isn't shared.
|
|
741
751
|
|
|
742
752
|
### Chunk membership
|
|
743
753
|
|
|
744
|
-
Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two
|
|
754
|
+
Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two _built-in_ chunks (say `fill` and `padding`) warns, because it would be invoked once per chunk with a subset of its inputs.
|
|
745
755
|
|
|
746
756
|
`configure({ handlers })` must run before the first render, like every other config option.
|
|
747
757
|
|
|
@@ -749,7 +759,7 @@ Tasty renders and caches CSS in independent chunks, and a chunk's cache key cove
|
|
|
749
759
|
|
|
750
760
|
## Props Middleware
|
|
751
761
|
|
|
752
|
-
`propHandlers` are middleware over a component's props — props in, props out. Where a style
|
|
762
|
+
`propHandlers` are middleware over a component's props — props in, props out. Where a style _handler_ turns a style property into CSS declarations, a prop handler turns a **component prop** into other props, including `styles`. It is the extension point for props whose value isn't a style value.
|
|
753
763
|
|
|
754
764
|
```jsx
|
|
755
765
|
import { configure, mergeStyles } from '@tenphi/tasty';
|
|
@@ -765,7 +775,7 @@ configure({
|
|
|
765
775
|
},
|
|
766
776
|
});
|
|
767
777
|
|
|
768
|
-
<Element glaze="purple"
|
|
778
|
+
<Element glaze="purple" />;
|
|
769
779
|
```
|
|
770
780
|
|
|
771
781
|
The map key is the handler's name and, by default, the prop that triggers it, so an absent prop costs one property check rather than a call. A tuple overrides that: `['glaze', fn]`, `[['glaze', 'tint'], fn]`, or `['*', fn]` for unconditional.
|
|
@@ -774,7 +784,7 @@ Handlers run at the very top of every component's render, before any prop is des
|
|
|
774
784
|
|
|
775
785
|
Handlers must be **pure** and must not mutate their input, and should memoize the styles they build per input value: style values are cached by object identity, so mutating one in place yields stale CSS, and a reference-stable object avoids re-serializing on every render.
|
|
776
786
|
|
|
777
|
-
Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to
|
|
787
|
+
Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to build-time `tastyStatic()`, which has no props.
|
|
778
788
|
|
|
779
789
|
See [Plugins → Props middleware](plugins.md#props-middleware) for the full contract and a worked example.
|
|
780
790
|
|
|
@@ -787,7 +797,7 @@ See [Plugins → Props middleware](plugins.md#props-middleware) for the full con
|
|
|
787
797
|
```jsx
|
|
788
798
|
configure({ baseStyleProps: ['radius', 'shadow'] });
|
|
789
799
|
|
|
790
|
-
<Card radius="1r" shadow
|
|
800
|
+
<Card radius="1r" shadow />;
|
|
791
801
|
```
|
|
792
802
|
|
|
793
803
|
`configure()` may run after your components are defined — each factory resolves its prop list lazily.
|
package/docs/debug.md
CHANGED
|
@@ -68,7 +68,7 @@ Retrieves CSS text for a given target. Logs the result with rule count and size.
|
|
|
68
68
|
|---|---|
|
|
69
69
|
| `'all'` | All tasty CSS (component + global + raw) |
|
|
70
70
|
| `'active'` | CSS for classes currently in the DOM |
|
|
71
|
-
| `'unused'` | CSS
|
|
71
|
+
| `'unused'` | CSS for injected classes no element carries (cached but not rendered) |
|
|
72
72
|
| `'global'` | Only global CSS (from `injectGlobal`) |
|
|
73
73
|
| `'page'` | All CSS on the page (including non-tasty) |
|
|
74
74
|
| `'t3a5f'` | CSS for a specific tasty class (class names are `t` + base36 hash) |
|
|
@@ -241,7 +241,9 @@ tastyDebug.cache();
|
|
|
241
241
|
|
|
242
242
|
### `cleanup(opts?): void`
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
Removes every injected style that is neither carried by an element nor pinned by
|
|
245
|
+
an outstanding `inject()` handle — the same set `summary()` reports as unused.
|
|
246
|
+
Equivalent to `gc({ force: true })`.
|
|
245
247
|
|
|
246
248
|
```typescript
|
|
247
249
|
tastyDebug.cleanup();
|
package/docs/design-system.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Building a Design System
|
|
2
2
|
|
|
3
|
-
This guide
|
|
3
|
+
This guide shows how to build shared React components whose styles stay predictable as states, variants, themes, and responsive rules accumulate. You will define tokens, state aliases, recipes, primitive APIs, and compound components with sub-elements.
|
|
4
4
|
|
|
5
|
-
It assumes you have already decided to adopt Tasty. The goal is not
|
|
5
|
+
It assumes you have already decided to adopt Tasty. The goal is not only to centralize values, but to give your design system one state model and a governed component-facing styling API. For evaluation criteria and incremental rollout, see the [Adoption Guide](adoption.md). For the recommended component architecture, see [Methodology](methodology.md).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -43,8 +43,8 @@ configure({
|
|
|
43
43
|
'#border': '#e0e0e0',
|
|
44
44
|
|
|
45
45
|
// Unit values
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
$gap: '8px',
|
|
47
|
+
$radius: '4px',
|
|
48
48
|
'$border-width': '1px',
|
|
49
49
|
'$outline-width': '2px',
|
|
50
50
|
},
|
|
@@ -91,8 +91,18 @@ import { configure } from '@tenphi/tasty';
|
|
|
91
91
|
|
|
92
92
|
configure({
|
|
93
93
|
presets: {
|
|
94
|
-
h1: {
|
|
95
|
-
|
|
94
|
+
h1: {
|
|
95
|
+
fontSize: '2rem',
|
|
96
|
+
lineHeight: '1.2',
|
|
97
|
+
letterSpacing: '-0.02em',
|
|
98
|
+
fontWeight: 700,
|
|
99
|
+
},
|
|
100
|
+
t2: {
|
|
101
|
+
fontSize: '0.875rem',
|
|
102
|
+
lineHeight: '1.5',
|
|
103
|
+
letterSpacing: 'normal',
|
|
104
|
+
fontWeight: 400,
|
|
105
|
+
},
|
|
96
106
|
},
|
|
97
107
|
});
|
|
98
108
|
```
|
|
@@ -123,8 +133,16 @@ Register your design system's custom fonts via `configure({ fontFaces })` so eve
|
|
|
123
133
|
configure({
|
|
124
134
|
fontFaces: {
|
|
125
135
|
'Brand Sans': [
|
|
126
|
-
{
|
|
127
|
-
|
|
136
|
+
{
|
|
137
|
+
src: 'url("/fonts/brand-regular.woff2") format("woff2")',
|
|
138
|
+
fontWeight: 400,
|
|
139
|
+
fontDisplay: 'swap',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
src: 'url("/fonts/brand-bold.woff2") format("woff2")',
|
|
143
|
+
fontWeight: 700,
|
|
144
|
+
fontDisplay: 'swap',
|
|
145
|
+
},
|
|
128
146
|
],
|
|
129
147
|
},
|
|
130
148
|
});
|
|
@@ -144,7 +162,8 @@ configure({
|
|
|
144
162
|
'@mobile': '@media(w < 768px)',
|
|
145
163
|
'@tablet': '@media(768px <= w < 1024px)',
|
|
146
164
|
'@desktop': '@media(w >= 1024px)',
|
|
147
|
-
'@dark':
|
|
165
|
+
'@dark':
|
|
166
|
+
'@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
|
|
148
167
|
'@reduced-motion': '@media(prefers-reduced-motion: reduce)',
|
|
149
168
|
},
|
|
150
169
|
});
|
|
@@ -185,7 +204,7 @@ configure({
|
|
|
185
204
|
'input-reset': {
|
|
186
205
|
border: 'none',
|
|
187
206
|
outline: 'none',
|
|
188
|
-
fill: '
|
|
207
|
+
fill: '#clear',
|
|
189
208
|
font: true,
|
|
190
209
|
preset: 't3',
|
|
191
210
|
},
|
|
@@ -211,7 +230,7 @@ const ProfileCard = tasty({
|
|
|
211
230
|
});
|
|
212
231
|
```
|
|
213
232
|
|
|
214
|
-
Use the `/` separator when a recipe should be applied
|
|
233
|
+
Use the `/` separator when a recipe should be applied _after_ local styles (post-merge), so recipe states take priority:
|
|
215
234
|
|
|
216
235
|
```tsx
|
|
217
236
|
const Input = tasty({
|
|
@@ -283,12 +302,12 @@ Product engineers use these to compose layouts without writing CSS:
|
|
|
283
302
|
|
|
284
303
|
Match the prop set to the component's role:
|
|
285
304
|
|
|
286
|
-
| Component category
|
|
287
|
-
|
|
288
|
-
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align, justify, padding, fill
|
|
289
|
-
| Positioned elements (`Button`, `Badge`)
|
|
290
|
-
| Text elements
|
|
291
|
-
| Compound components
|
|
305
|
+
| Component category | Recommended styleProps |
|
|
306
|
+
| ------------------------------------------ | -------------------------------------------------------------- |
|
|
307
|
+
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align, justify, padding, fill |
|
|
308
|
+
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
|
|
309
|
+
| Text elements | `['preset', 'color']` or a custom subset |
|
|
310
|
+
| Compound components | Typically none — styling happens via sub-elements and wrapping |
|
|
292
311
|
|
|
293
312
|
Exposing too many props weakens the design system's constraints. See [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api) for the rationale.
|
|
294
313
|
|
|
@@ -335,9 +354,7 @@ Usage:
|
|
|
335
354
|
```tsx
|
|
336
355
|
<Card>
|
|
337
356
|
<Card.Title>Monthly Revenue</Card.Title>
|
|
338
|
-
<Card.Content>
|
|
339
|
-
$1.2M — up 12% from last month.
|
|
340
|
-
</Card.Content>
|
|
357
|
+
<Card.Content>$1.2M — up 12% from last month.</Card.Content>
|
|
341
358
|
<Card.Footer>
|
|
342
359
|
<Button>Details</Button>
|
|
343
360
|
</Card.Footer>
|
|
@@ -365,13 +382,15 @@ A design system works best when the rules for customization are explicit. Tasty
|
|
|
365
382
|
2. **Use modProps** — control component states through typed props instead of `mods`:
|
|
366
383
|
|
|
367
384
|
```tsx
|
|
368
|
-
<Button isLoading size="large">
|
|
385
|
+
<Button isLoading size="large">
|
|
386
|
+
Submit
|
|
387
|
+
</Button>
|
|
369
388
|
```
|
|
370
389
|
|
|
371
390
|
3. **Pass tokens** — inject runtime values through the `tokens` prop for per-instance customization:
|
|
372
391
|
|
|
373
392
|
```tsx
|
|
374
|
-
<ProgressBar tokens={{
|
|
393
|
+
<ProgressBar tokens={{ $progress: `${percent}%` }} />
|
|
375
394
|
```
|
|
376
395
|
|
|
377
396
|
4. **Create styled wrappers** — extend a component's styles with `tasty(Base, { styles })`:
|