@tenphi/tasty 3.9.11 → 3.11.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 +399 -380
- package/dist/babel-types-BKDTT4--.d.ts +18 -0
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/build-config-5d8WlQbq.js +60 -0
- package/dist/chunks/build-config-5d8WlQbq.js.map +1 -0
- package/dist/chunks/{collector-D2ARGT2L.js → collector-CjCak4iE.js} +8 -6
- package/dist/chunks/collector-CjCak4iE.js.map +1 -0
- package/dist/chunks/{config-engine-DEmmPPdp.js → config-engine-uOFYZidB.js} +4 -4
- package/dist/chunks/{config-engine-DEmmPPdp.js.map → config-engine-uOFYZidB.js.map} +1 -1
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/{css-definitions-DotDkIXt.js → css-definitions-ubrHP97m.js} +130 -96
- package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
- package/dist/chunks/{debug-D8pg9RGI.js → debug-CJ31U_WE.js} +6 -6
- package/dist/chunks/{debug-D8pg9RGI.js.map → debug-CJ31U_WE.js.map} +1 -1
- package/dist/chunks/{dsl-DRhFWQzo.js → dsl-DYSJc1-T.js} +11 -6
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/{react-runtime-BpxlOTbO.js → react-runtime-CyyCI02N.js} +190 -63
- package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
- package/dist/chunks/{runtime-engine-CKFXUH-A.js → runtime-engine-BQmOu5-W.js} +69 -53
- package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
- package/dist/chunks/{runtime-state-BxaLhVdP.js → runtime-state-DEsyUMU-.js} +28 -3
- package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
- package/dist/chunks/{style-engine-BBbBuSkT.js → style-engine-BX2LshDZ.js} +309 -103
- package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
- package/dist/chunks/{zero-engine-gJrKBRiw.js → zero-engine-Dw0P4yGM.js} +62 -27
- package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
- package/dist/{collector-w4mBj5gd.d.ts → collector-CTU-nl0g.d.ts} +2 -2
- package/dist/{config-CZWCzBr6.d.ts → config-CcxqSZI2.d.ts} +35 -16
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +7 -7
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-G9JuwM9p.d.ts → index-xtey-KDu.d.ts} +43 -13
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/{merge-styles-VXwJ0Frr.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -2
- package/dist/ssr/astro.js +4 -4
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +3 -22
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +1 -1
- package/dist/ssr/next-config.js +13 -18
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +3 -3
- package/dist/static/index.d.ts +2 -2
- package/dist/{types-JEY8PVYP.d.ts → types-DJEM_BOJ.d.ts} +6 -6
- package/dist/zero/babel.d.ts +67 -2
- package/dist/zero/babel.js +47 -44
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +58 -3
- package/dist/zero/index.js +24 -2
- package/dist/zero/index.js.map +1 -0
- package/dist/zero/next.d.ts +2 -1
- package/dist/zero/next.js +3 -7
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +4 -1
- package/docs/adoption.md +25 -17
- package/docs/ai-agents.md +37 -34
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +174 -59
- package/docs/debug.md +3 -2
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +122 -81
- package/docs/getting-started.md +81 -24
- package/docs/injector.md +39 -40
- package/docs/methodology.md +45 -20
- package/docs/migration-v3.md +2 -2
- package/docs/pipeline.md +20 -17
- package/docs/plugins.md +20 -40
- package/docs/react-api.md +28 -8
- package/docs/runtime-benchmarks.md +4 -4
- package/docs/ssr.md +83 -63
- package/docs/styles.md +27 -17
- package/docs/tasty-static.md +142 -55
- package/package.json +24 -10
- package/dist/babel-rZcEwPLM.d.ts +0 -82
- package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
- package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
- package/dist/chunks/build-config-CL4eRu2z.js +0 -42
- package/dist/chunks/build-config-CL4eRu2z.js.map +0 -1
- package/dist/chunks/collector-D2ARGT2L.js.map +0 -1
- package/dist/chunks/css-definitions-DotDkIXt.js.map +0 -1
- package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
- package/dist/chunks/react-runtime-BpxlOTbO.js.map +0 -1
- package/dist/chunks/runtime-engine-CKFXUH-A.js.map +0 -1
- package/dist/chunks/runtime-state-BxaLhVdP.js.map +0 -1
- package/dist/chunks/style-engine-BBbBuSkT.js.map +0 -1
- package/dist/chunks/zero-engine-gJrKBRiw.js.map +0 -1
package/docs/configuration.md
CHANGED
|
@@ -23,9 +23,8 @@ configure({
|
|
|
23
23
|
|
|
24
24
|
// Custom units (merged with built-in units)
|
|
25
25
|
units: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
custom: (n) => `${n * 10}px`, // Function-based unit
|
|
26
|
+
gu: '4px', // `2gu` → `8px`
|
|
27
|
+
cols: (n) => `calc(${n} * var(--col-width))`, // function-based unit
|
|
29
28
|
},
|
|
30
29
|
|
|
31
30
|
// Custom functions — a single map for both flavors, discriminated by value type:
|
|
@@ -47,35 +46,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
|
|
|
47
46
|
|
|
48
47
|
## Options
|
|
49
48
|
|
|
50
|
-
| Option | Type | Default
|
|
51
|
-
| -------------------- | ------------------------------------------------ |
|
|
52
|
-
| `nonce` | `string` | -
|
|
53
|
-
| `maxRulesPerSheet` | `number` | `8192`
|
|
54
|
-
| `forceTextInjection` | `boolean` |
|
|
55
|
-
| `devMode` | `boolean` | auto
|
|
56
|
-
| `states` | `Record<string, string>` | -
|
|
57
|
-
| `parserCacheSize` | `number` | `1000`
|
|
58
|
-
| `units` | `Record<string, string \| UnitHandler>` | Built-in
|
|
59
|
-
| `functions` | `Record<string, FunctionDefinition \| Function>` | -
|
|
60
|
-
| `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in
|
|
61
|
-
| `propHandlers` | `Record<string, PropHandlerDefinition>` | -
|
|
62
|
-
| `baseStyleProps` | `readonly string[]` | -
|
|
63
|
-
| `tokens` | `Record<string, value \| stateMap>` | -
|
|
64
|
-
| `replaceTokens` | `Record<string, string \| number \| boolean>` |
|
|
65
|
-
| `keyframes` | `Record<string, KeyframesSteps>` | -
|
|
66
|
-
| `properties` | `Record<string, PropertyDefinition>` | -
|
|
67
|
-
| `fontFaces` | `Record<string, FontFaceInput>` | -
|
|
68
|
-
| `counterStyles` | `Record<string, CounterStyleDescriptors>` | -
|
|
69
|
-
| `polyfills` | `{ functions?: boolean }` | `{}`
|
|
70
|
-
| `autoPropertyTypes` | `boolean` | `true`
|
|
71
|
-
| `recipes` | `Record<string, RecipeStyles>` | -
|
|
72
|
-
| `presets` | `Record<string, TypographyPreset>` | -
|
|
73
|
-
| `globalStyles` | `Record<string, Styles>` | -
|
|
74
|
-
| `plugins` | `TastyPlugin[]` | -
|
|
75
|
-
| `gc` | `GCConfig` | -
|
|
76
|
-
| `batchInjection` | `boolean \| 'always'` | `false`
|
|
77
|
-
| `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | -
|
|
78
|
-
| `namePrefix` | `string` | `'t'` (`tasty`) / `'ts'` (build-time)
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `nonce` | `string` | - | CSP nonce for style elements |
|
|
52
|
+
| `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
|
|
53
|
+
| `forceTextInjection` | `boolean` | `false` (`true` in test environments only while `configure()` has never been called) | Force text-node CSS injection instead of constructable stylesheets. Set it explicitly in test setup |
|
|
54
|
+
| `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info. Does not turn on dev warnings; those follow `NODE_ENV` (or `localStorage.TASTY_DEBUG = 'true'` in a browser) |
|
|
55
|
+
| `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
|
|
56
|
+
| `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
|
|
57
|
+
| `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
|
|
58
|
+
| `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
|
|
59
|
+
| `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
|
|
60
|
+
| `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
|
|
61
|
+
| `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
|
|
62
|
+
| `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
|
|
63
|
+
| `replaceTokens` | `Record<string, string \| number \| boolean>` | `{ '#clear': 'transparent' }` | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
|
|
64
|
+
| `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
|
|
65
|
+
| `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
|
|
66
|
+
| `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
|
|
67
|
+
| `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
|
|
68
|
+
| `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
|
|
69
|
+
| `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
|
|
70
|
+
| `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
|
|
71
|
+
| `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
|
|
72
|
+
| `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
|
|
73
|
+
| `plugins` | `TastyPlugin[]` | - | Plugins that bundle `functions`, `units`, `states`, `handlers`, `propHandlers`, `baseStyleProps`, `tokens`, `replaceTokens`, `recipes`, `presets`, `globalStyles`, `properties`, `keyframes`, `fontFaces`, and `counterStyles` (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
|
|
74
|
+
| `gc` | `GCConfig` | - | Turns on automatic collection of unused styles: `{ touchInterval?, capacity?, grace? }` (defaults `1000`, `1000`, `10000` ms). Without it nothing is collected automatically; call `gc()` yourself |
|
|
75
|
+
| `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
|
|
76
|
+
| `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
|
|
77
|
+
| `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). |
|
|
79
78
|
|
|
80
79
|
---
|
|
81
80
|
|
|
@@ -334,8 +333,7 @@ Tokens support state maps for responsive or theme-aware values:
|
|
|
334
333
|
```jsx
|
|
335
334
|
configure({
|
|
336
335
|
tokens: {
|
|
337
|
-
$
|
|
338
|
-
$radius: '6px',
|
|
336
|
+
'$sidebar-width': '280px',
|
|
339
337
|
'#primary': {
|
|
340
338
|
'': '#purple',
|
|
341
339
|
'@dark': '#light-purple',
|
|
@@ -350,9 +348,10 @@ configure({
|
|
|
350
348
|
|
|
351
349
|
- `$name` keys become `--name` CSS custom properties
|
|
352
350
|
- `#name` keys become `--name-color` custom properties
|
|
351
|
+
- Tokens do not change units: `2x` stays `16px` whatever `$gap` is. To drive a unit from a token, point the unit at it: `units: { x: 'var(--gap)' }` makes `2x` compile to `calc(2 * var(--gap))`
|
|
353
352
|
- 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.
|
|
354
353
|
|
|
355
|
-
Tokens are
|
|
354
|
+
Tokens are emitted by the client injector, by the SSR collector (`TastyRegistry`, `tastyIntegration()`, `runWithCollector()`), by the `withTastyNext()` shared stylesheet, and by the Babel plugin. The inline React Server Component path without a collector does not emit them — see [Server-Side Rendering](ssr.md).
|
|
356
355
|
|
|
357
356
|
---
|
|
358
357
|
|
|
@@ -375,6 +374,17 @@ configure({
|
|
|
375
374
|
|
|
376
375
|
When a component uses `padding: '$card-padding'`, the parser replaces it with `'4x'` before generating CSS. When a component uses `fill: '#accent'`, it is replaced with `'#purple'`, which in turn resolves to `var(--purple-color)`.
|
|
377
376
|
|
|
377
|
+
Tasty includes `'#clear': 'transparent'` as a default replace token, available
|
|
378
|
+
without calling `configure()`. It survives `resetConfig()`, and explicit
|
|
379
|
+
`replaceTokens` entries can override it. A replace token resolving to the literal
|
|
380
|
+
`transparent` ignores numeric and custom-property opacity suffixes:
|
|
381
|
+
`#clear`, `#clear.5`, and `#clear.$opacity` all emit `transparent`.
|
|
382
|
+
|
|
383
|
+
`#clear` no longer reads `--clear-color`, and Tasty no longer registers that
|
|
384
|
+
property by default. If you previously customized `#clear` through `tokens`,
|
|
385
|
+
local token definitions, or CSS, use a different color token or explicitly
|
|
386
|
+
override `replaceTokens['#clear']` instead.
|
|
387
|
+
|
|
378
388
|
See [Replace Tokens](dsl.md#replace-tokens) in the Style DSL reference.
|
|
379
389
|
|
|
380
390
|
---
|
|
@@ -471,7 +481,7 @@ See [Functions (`@function`)](dsl.md#functions-function) for inline usage inside
|
|
|
471
481
|
|
|
472
482
|
### Custom color functions
|
|
473
483
|
|
|
474
|
-
A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `#token.alpha` opacity
|
|
484
|
+
A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `parseColor`, and the `#token.alpha` opacity suffix, which calls the function with a `/ alpha` part appended (read `groups[0].parts[1]`, as below) — with no extra registration. This is the same mechanism the built-in `okhsl`/`okhst` plugins use; they are ordinary plugins registered by default.
|
|
475
485
|
|
|
476
486
|
```ts
|
|
477
487
|
import { configure, createColorFunc } from '@tenphi/tasty';
|
|
@@ -482,8 +492,12 @@ const myColorPlugin = () => ({
|
|
|
482
492
|
functions: {
|
|
483
493
|
// Hand-written parse function:
|
|
484
494
|
mycolor: (groups) => {
|
|
485
|
-
|
|
486
|
-
|
|
495
|
+
// `mycolor(255 0 0 / .5)` and `#brand.5` both arrive as two parts
|
|
496
|
+
const [main, alpha] = groups[0].parts;
|
|
497
|
+
const [r, g, b] = main.all;
|
|
498
|
+
return alpha?.all.length
|
|
499
|
+
? `rgb(${r} ${g} ${b} / ${alpha.output})`
|
|
500
|
+
: `rgb(${r} ${g} ${b})`;
|
|
487
501
|
},
|
|
488
502
|
},
|
|
489
503
|
});
|
|
@@ -631,7 +645,7 @@ configure({
|
|
|
631
645
|
});
|
|
632
646
|
```
|
|
633
647
|
|
|
634
|
-
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.
|
|
648
|
+
Each key is a CSS selector (a list such as `'.uni-checkbox, uni-checkbox'` styles every item), and each value is a Tasty `Styles` object supporting the full style syntax including style properties, tokens, state maps, `@keyframes`, 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.
|
|
635
649
|
|
|
636
650
|
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).
|
|
637
651
|
|
|
@@ -690,10 +704,16 @@ Override or extend the built-in style property handlers. A handler definition ca
|
|
|
690
704
|
| Single dep | `['styleName', handler]` | Triggered by the specified style property |
|
|
691
705
|
| Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
|
|
692
706
|
|
|
693
|
-
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`.
|
|
707
|
+
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). A handler whose dependencies include a built-in style takes over that style's whole group — see [Replacing a built-in handler](#replacing-a-built-in-handler). 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`.
|
|
694
708
|
|
|
695
709
|
```jsx
|
|
696
|
-
import {
|
|
710
|
+
import {
|
|
711
|
+
configure,
|
|
712
|
+
defineHandler,
|
|
713
|
+
parseColor,
|
|
714
|
+
parseStyle,
|
|
715
|
+
styleHandlers,
|
|
716
|
+
} from '@tenphi/tasty';
|
|
697
717
|
|
|
698
718
|
configure({
|
|
699
719
|
handlers: {
|
|
@@ -717,6 +737,10 @@ configure({
|
|
|
717
737
|
'backgroundPosition',
|
|
718
738
|
'backgroundSize',
|
|
719
739
|
'backgroundRepeat',
|
|
740
|
+
'backgroundAttachment',
|
|
741
|
+
'backgroundOrigin',
|
|
742
|
+
'backgroundClip',
|
|
743
|
+
'background',
|
|
720
744
|
],
|
|
721
745
|
(props) => {
|
|
722
746
|
if (
|
|
@@ -729,12 +753,18 @@ configure({
|
|
|
729
753
|
},
|
|
730
754
|
),
|
|
731
755
|
|
|
732
|
-
// Multi dep —
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
756
|
+
// Multi dep — triggered by either name, receives both
|
|
757
|
+
stripe: defineHandler(
|
|
758
|
+
['stripe', 'stripeColor'],
|
|
759
|
+
({ stripe, stripeColor }) => {
|
|
760
|
+
if (!stripe) return;
|
|
761
|
+
const width = parseStyle(String(stripe)).output;
|
|
762
|
+
const color = stripeColor
|
|
763
|
+
? parseColor(String(stripeColor)).color
|
|
764
|
+
: 'currentcolor';
|
|
765
|
+
return { 'border-left': `${width} solid ${color}` };
|
|
766
|
+
},
|
|
767
|
+
),
|
|
738
768
|
},
|
|
739
769
|
});
|
|
740
770
|
```
|
|
@@ -756,12 +786,17 @@ Built-in handlers are shared across several style names. Registering a handler f
|
|
|
756
786
|
|
|
757
787
|
The shared groups worth knowing:
|
|
758
788
|
|
|
759
|
-
| Registering a handler for
|
|
760
|
-
|
|
|
761
|
-
| `fill`
|
|
762
|
-
| `display`
|
|
763
|
-
| `
|
|
764
|
-
| `
|
|
789
|
+
| Registering a handler for | Also takes over |
|
|
790
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
791
|
+
| `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat`, `backgroundAttachment`, `backgroundOrigin`, `backgroundClip`, `background` |
|
|
792
|
+
| `display` | `hide`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
|
|
793
|
+
| `flow` | `gap` |
|
|
794
|
+
| `preset` | `font`, `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
|
|
795
|
+
| `padding` / `margin` | their `Top`/`Right`/`Bottom`/`Left` longhands (`paddingTop`, `marginLeft`, …) |
|
|
796
|
+
| `inset` | `top`, `right`, `bottom`, `left` |
|
|
797
|
+
| `width` / `height` | `minWidth`/`maxWidth`, `minHeight`/`maxHeight` |
|
|
798
|
+
|
|
799
|
+
A multi-dep handler takes over every group its dependencies touch: `defineHandler(['display', 'flow', 'gap'], …)` takes over `display`, `flow`, and `gap` together, so it must emit `display` and `flex-flow` itself.
|
|
765
800
|
|
|
766
801
|
Either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a name that isn't shared.
|
|
767
802
|
|
|
@@ -822,18 +857,98 @@ Each name costs one property check per render of every component, and the effect
|
|
|
822
857
|
|
|
823
858
|
---
|
|
824
859
|
|
|
825
|
-
##
|
|
860
|
+
## TypeScript
|
|
826
861
|
|
|
827
|
-
|
|
862
|
+
Component props are typed from the `tasty()` definition with no setup. Module augmentation adds the names your design system defines and the extensions your plugins add.
|
|
863
|
+
|
|
864
|
+
### Component props
|
|
865
|
+
|
|
866
|
+
| In `tasty({ … })` | Adds the props |
|
|
867
|
+
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
868
|
+
| `styleProps: ['gap', 'padding'] as const` | `gap`, `padding`, each typed like the style: a value or a state map |
|
|
869
|
+
| `modProps: { isLoading: Boolean, size: ['sm', 'md', 'lg'] as const }` | `isLoading?: boolean`, `size?: 'sm' \| 'md' \| 'lg'` (`String` gives `string`, `Number` gives `number`) |
|
|
870
|
+
| `modProps: ['isLoading'] as const` | `isLoading?: ModValue` |
|
|
871
|
+
| `tokenProps: ['progress', 'accentColor'] as const` | `progress?: TokenValue`, `accentColor?: TokenValue` |
|
|
872
|
+
| `variants: { default: {…}, danger: {…} }` | `variant?: 'default' \| 'danger'` |
|
|
873
|
+
| `elements: { Title: 'h3' }` | `Card.Title`, with the `h3` props plus `qa`, `qaVal`, `mods`, `tokens`, `isDisabled`, `isHidden`, `isChecked`, and a `ref` typed as `HTMLHeadingElement`; no `styles` |
|
|
874
|
+
| `as: SomeComponent` | `SomeComponent`'s own props, required ones still required |
|
|
875
|
+
|
|
876
|
+
Write the array forms with `as const`. Without it, `modProps: ['isLoading']` widens to `string[]` and every prop on the component is typed `ModValue`, so an `onClick` handler no longer type-checks. A `modProps` value list without `as const` widens to `string`.
|
|
877
|
+
|
|
878
|
+
`tasty(Base, { … })` keeps `Base`'s prop types and adds none for the options above, which that form ignores — see [Extend an existing component](react-api.md#extend-an-existing-component).
|
|
879
|
+
|
|
880
|
+
### Registering your names and extensions
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
// tasty.d.ts — keep the import: it makes this file a module, so the block
|
|
884
|
+
// below extends '@tenphi/tasty' instead of replacing it.
|
|
885
|
+
import '@tenphi/tasty';
|
|
828
886
|
|
|
829
|
-
```tsx
|
|
830
|
-
// tasty.d.ts
|
|
831
887
|
declare module '@tenphi/tasty' {
|
|
888
|
+
interface TastyNamedColors {
|
|
889
|
+
surface: true;
|
|
890
|
+
text: true;
|
|
891
|
+
primary: true;
|
|
892
|
+
}
|
|
893
|
+
interface TastyPresetNames {
|
|
894
|
+
h1: true;
|
|
895
|
+
t2: true;
|
|
896
|
+
}
|
|
897
|
+
interface TastyThemeNames {
|
|
898
|
+
danger: true;
|
|
899
|
+
success: true;
|
|
900
|
+
}
|
|
832
901
|
interface StylesInterface {
|
|
833
|
-
|
|
834
|
-
|
|
902
|
+
stripe?: string;
|
|
903
|
+
}
|
|
904
|
+
interface TastyCustomProps {
|
|
905
|
+
glaze: 'soft' | 'strong' | { tone: string; intensity?: number };
|
|
906
|
+
}
|
|
907
|
+
interface TastyBaseStylePropNames {
|
|
908
|
+
radius: true;
|
|
909
|
+
stripe: true;
|
|
835
910
|
}
|
|
836
911
|
}
|
|
837
912
|
```
|
|
838
913
|
|
|
914
|
+
| Interface | Register | What it types |
|
|
915
|
+
| ------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
916
|
+
| `TastyNamedColors` | Color token names from `tokens` and `replaceTokens`, without the `#` | Suggestions for `#name` and `#name.50` in `fill`, `color`, and `svgFill` |
|
|
917
|
+
| `TastyPresetNames` | `presets` names | The `preset` style and prop |
|
|
918
|
+
| `TastyThemeNames` | Theme names | The `theme` prop (rendered as `data-theme`) |
|
|
919
|
+
| `StylesInterface` | Style properties added with `handlers` | Their value type in `styles`, `styleProps`, and base style props |
|
|
920
|
+
| `TastyCustomProps` | Props added with `propHandlers` | An optional prop on every `tasty()` component (not on sub-elements) |
|
|
921
|
+
| `TastyBaseStylePropNames` | Names passed to `baseStyleProps` | A prop on every component, typed like the style it names |
|
|
922
|
+
|
|
923
|
+
- The first three add autocomplete, not checking: any other string still type-checks (`preset: 'h9'` compiles). The ESLint plugin is what reports unknown names.
|
|
924
|
+
- In `StylesInterface`, declare the plain value type (`stripe?: string`). Tasty adds state-map support itself, as it does for built-in styles.
|
|
925
|
+
- A custom style name in `styles` type-checks without augmentation, but `styleProps` and `TastyBaseStylePropNames` accept only names in `StylesInterface`. A base style prop name missing from it adds no prop.
|
|
926
|
+
- `TastyCustomProps` types the component prop, not the handler. A prop handler receives `Record<string, unknown>`, so cast inside it (`rest.styles as Styles`).
|
|
927
|
+
- State keys, `$` value tokens, units, and recipe names have no TypeScript types. The ESLint plugin checks them against `tasty.config.ts`.
|
|
928
|
+
|
|
929
|
+
### `tasty.config.ts`
|
|
930
|
+
|
|
931
|
+
TypeScript augmentation does not reach the linter or the editor. [`@tenphi/eslint-plugin-tasty`](https://github.com/tenphi/eslint-plugin-tasty) and the Tasty VS Code extension read a `tasty.config.ts` (or `.js`, `.json`) at the project root instead. It only lists names: it is not passed to `configure()`, and it is unrelated to the Babel plugin's `configFile` (such as `tasty-zero.config.ts`).
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
// tasty.config.ts
|
|
935
|
+
import type { TastyExtensionConfig } from '@tenphi/tasty';
|
|
936
|
+
import type { TastyValidationConfig } from '@tenphi/eslint-plugin-tasty';
|
|
937
|
+
|
|
938
|
+
export default {
|
|
939
|
+
tokens: ['#surface', '#text', '#primary', '$sidebar-width'],
|
|
940
|
+
units: ['cols'],
|
|
941
|
+
states: ['@mobile', '@dark'],
|
|
942
|
+
presets: ['h1', 't2'],
|
|
943
|
+
recipes: ['card', 'elevated'],
|
|
944
|
+
styles: ['stripe'],
|
|
945
|
+
importSources: ['@my-org/ui'],
|
|
946
|
+
presetDescriptions: { h1: 'Heading 1 (32px, bold)' },
|
|
947
|
+
} satisfies TastyExtensionConfig & TastyValidationConfig;
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
`TastyExtensionConfig` from `@tenphi/tasty` types the fields the VS Code extension reads: `extends`, `tokens`, `units`, `functions`, `states`, `presets`, `styles`, `presetDescriptions`, and `stateDescriptions`. It has no `recipes`, `importSources`, `ownedSources`, or `styleFunctions`, so annotating the file with it alone rejects those ESLint fields; intersect it with the plugin's `TastyValidationConfig`, as above. An omitted list is not checked; `tokens: false`, `units: false`, or `functions: false` turns a check off even when an `extends` parent sets it.
|
|
951
|
+
|
|
952
|
+
Keep it in step with `configure()`: token names with their `#` or `$`, custom units, parse-function names, state aliases, presets, recipes, and custom style names.
|
|
953
|
+
|
|
839
954
|
See [Style DSL](dsl.md) for state maps, tokens, units, and extending semantics, and [React API](react-api.md) for `tasty()`, style functions, and component props.
|
package/docs/debug.md
CHANGED
|
@@ -78,7 +78,7 @@ Retrieves CSS text for a given target. Logs the result with rule count and size.
|
|
|
78
78
|
|---|---|
|
|
79
79
|
| `'all'` | All tasty CSS (component + global + raw) |
|
|
80
80
|
| `'active'` | CSS for classes currently in the DOM |
|
|
81
|
-
| `'unused'` | CSS for
|
|
81
|
+
| `'unused'` | CSS for classes `gc({ force: true })` would delete now — not rendered, not pinned, past the `gc.grace` window |
|
|
82
82
|
| `'global'` | Only global CSS (from `injectGlobal`) |
|
|
83
83
|
| `'page'` | All CSS on the page (including non-tasty) |
|
|
84
84
|
| `'t3a5f'` | CSS for a specific tasty class (class names are `t` + base36 hash) |
|
|
@@ -154,7 +154,8 @@ One-shot overview of the entire Tasty CSS state. Logs a compact report.
|
|
|
154
154
|
```typescript
|
|
155
155
|
interface Summary {
|
|
156
156
|
activeClasses: string[];
|
|
157
|
-
unusedClasses: string[];
|
|
157
|
+
unusedClasses: string[]; // classes gc({ force: true }) would delete now
|
|
158
|
+
hotClasses: string[]; // not rendered, but pinned or inside gc.grace — not yet collectable
|
|
158
159
|
totalStyledClasses: string[];
|
|
159
160
|
|
|
160
161
|
activeCSSSize: number;
|
package/docs/design-system.md
CHANGED
|
@@ -42,15 +42,25 @@ configure({
|
|
|
42
42
|
// Borders
|
|
43
43
|
'#border': '#e0e0e0',
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// Spacing and shape scale
|
|
46
46
|
$gap: '8px',
|
|
47
47
|
$radius: '4px',
|
|
48
48
|
'$border-width': '1px',
|
|
49
49
|
'$outline-width': '2px',
|
|
50
50
|
},
|
|
51
|
+
// Built-in units compile to fixed values by default. Pointing them at the
|
|
52
|
+
// tokens above lets a theme or density mode change the scale at runtime.
|
|
53
|
+
units: {
|
|
54
|
+
x: 'var(--gap)', // 2x → calc(2 * var(--gap))
|
|
55
|
+
r: 'var(--radius)',
|
|
56
|
+
bw: 'var(--border-width)',
|
|
57
|
+
ow: 'var(--outline-width)',
|
|
58
|
+
},
|
|
51
59
|
});
|
|
52
60
|
```
|
|
53
61
|
|
|
62
|
+
Leave out the `units` block to keep the built-in fixed scale (`x` = `8px`, `r` = `6px`, `bw` = `1px`, `ow` = `3px`), or set fixed values there instead (`units: { x: '8px', r: '4px' }`). Define the token a unit points at; until you do, `--gap`, `--radius`, `--border-width`, and `--outline-width` start at the built-in unit values. Boolean shorthands follow the same scale: `radius: true` is `1r`, `border: true` is `1bw`, and `outline: true` is `1ow`. See [Built-in Units](dsl.md#built-in-units).
|
|
63
|
+
|
|
54
64
|
Tokens are injected as CSS custom properties on `:root` when the first style is rendered. They support state maps for theme-aware values:
|
|
55
65
|
|
|
56
66
|
```tsx
|
|
@@ -66,7 +76,7 @@ configure({
|
|
|
66
76
|
|
|
67
77
|
The `#on-*` convention names the text color that sits on top of a fill — `#on-primary` is the text color on a `#primary` background. This makes state maps self-documenting: `fill: '#primary'` and `color: '#on-primary'` clearly belong together.
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
To generate the whole palette instead of hand-picking values, use [Glaze](https://github.com/tenphi/glaze): it builds light, dark, and high-contrast variants from one hue in the OKHST color space, solves WCAG and APCA contrast, and exports Tasty tokens you can pass straight to `configure({ tokens: theme.tasty() })`.
|
|
70
80
|
|
|
71
81
|
### Replace tokens for value aliases
|
|
72
82
|
|
|
@@ -176,11 +186,13 @@ const Card = tasty({
|
|
|
176
186
|
styles: {
|
|
177
187
|
padding: { '': '4x', '@mobile': '2x' },
|
|
178
188
|
flow: { '': 'row', '@mobile': 'column' },
|
|
179
|
-
|
|
189
|
+
radius: { '': '1r', '@mobile': '0' },
|
|
180
190
|
},
|
|
181
191
|
});
|
|
182
192
|
```
|
|
183
193
|
|
|
194
|
+
Theme switching belongs in tokens (`'#surface': { '': '#fff', '@dark': '#1c1c1e' }`) rather than in component state maps, so components stay theme-agnostic.
|
|
195
|
+
|
|
184
196
|
Without aliases, the same component would need the full media query expression inlined in every state map — across every property, in every component. Aliases eliminate that duplication.
|
|
185
197
|
|
|
186
198
|
---
|
|
@@ -279,7 +291,7 @@ const Grid = tasty({
|
|
|
279
291
|
display: 'grid',
|
|
280
292
|
gap: '1x',
|
|
281
293
|
},
|
|
282
|
-
styleProps:
|
|
294
|
+
styleProps: FLOW_STYLES, // includes gridColumns, gridRows, gridAreas
|
|
283
295
|
});
|
|
284
296
|
```
|
|
285
297
|
|
|
@@ -298,18 +310,54 @@ Product engineers use these to compose layouts without writing CSS:
|
|
|
298
310
|
</Grid>
|
|
299
311
|
```
|
|
300
312
|
|
|
313
|
+
### Interactive components with variants
|
|
314
|
+
|
|
315
|
+
Variants are named looks that replace each other. Combine them with `modProps` for states that stack, such as size or loading:
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
const Button = tasty({
|
|
319
|
+
as: 'button',
|
|
320
|
+
modProps: { size: ['sm', 'md', 'lg'] as const },
|
|
321
|
+
styles: {
|
|
322
|
+
recipe: 'interactive',
|
|
323
|
+
padding: { '': '1.5x 3x', 'size=sm': '1x 2x', 'size=lg': '2x 4x' },
|
|
324
|
+
radius: '1r',
|
|
325
|
+
},
|
|
326
|
+
variants: {
|
|
327
|
+
default: {
|
|
328
|
+
fill: {
|
|
329
|
+
'': '#primary',
|
|
330
|
+
':hover': '#primary-hover',
|
|
331
|
+
disabled: '#surface',
|
|
332
|
+
},
|
|
333
|
+
color: '#on-primary',
|
|
334
|
+
},
|
|
335
|
+
danger: {
|
|
336
|
+
fill: { '': '#danger', ':hover': '#danger-hover', disabled: '#surface' },
|
|
337
|
+
color: '#on-danger',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
<Button variant="danger" size="lg">
|
|
343
|
+
Delete
|
|
344
|
+
</Button>;
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`variant` is typed from the keys, the `default` variant applies when no `variant` prop is passed, and only the variants that actually render emit CSS. See [React API — Variants](react-api.md#variants).
|
|
348
|
+
|
|
301
349
|
### Which styleProps to expose
|
|
302
350
|
|
|
303
351
|
Match the prop set to the component's role:
|
|
304
352
|
|
|
305
|
-
| Component category | Recommended styleProps
|
|
306
|
-
| ------------------------------------------ |
|
|
307
|
-
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align
|
|
308
|
-
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order
|
|
309
|
-
| Text elements | `['
|
|
310
|
-
| Compound components | Typically none — styling happens via sub-elements and wrapping
|
|
353
|
+
| Component category | Recommended styleProps |
|
|
354
|
+
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align/justify/place, grid templates (add `padding` or `fill` explicitly) |
|
|
356
|
+
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
|
|
357
|
+
| Text elements | `['color', ...TEXT_STYLES]` (`preset` is always available) |
|
|
358
|
+
| Compound components | Typically none — styling happens via sub-elements and wrapping |
|
|
311
359
|
|
|
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.
|
|
360
|
+
`BASE_STYLES` (`display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, `transition`) are accepted as props on every component without being listed. 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.
|
|
313
361
|
|
|
314
362
|
---
|
|
315
363
|
|
|
@@ -369,14 +417,14 @@ For sub-element syntax details (selector affix `$`, `@own()`, `elements` config)
|
|
|
369
417
|
|
|
370
418
|
## Defining the override contract
|
|
371
419
|
|
|
372
|
-
A design system works best when the rules for customization are explicit. Tasty
|
|
420
|
+
A design system works best when the rules for customization are explicit. Tasty gives product engineers four sanctioned ways to customize a component, and a few to discourage:
|
|
373
421
|
|
|
374
422
|
### What product engineers can do
|
|
375
423
|
|
|
376
424
|
1. **Use styleProps** — adjust layout, spacing, positioning through the props the component exposes:
|
|
377
425
|
|
|
378
426
|
```tsx
|
|
379
|
-
<Space flow="row" gap="3x"
|
|
427
|
+
<Space flow="row" gap="3x" placeItems="center">
|
|
380
428
|
```
|
|
381
429
|
|
|
382
430
|
2. **Use modProps** — control component states through typed props instead of `mods`:
|
|
@@ -398,7 +446,12 @@ A design system works best when the rules for customization are explicit. Tasty
|
|
|
398
446
|
```tsx
|
|
399
447
|
const DangerButton = tasty(Button, {
|
|
400
448
|
styles: {
|
|
401
|
-
fill: {
|
|
449
|
+
fill: {
|
|
450
|
+
'': '#danger',
|
|
451
|
+
':hover': '#danger-hover',
|
|
452
|
+
':active': '#danger-pressed',
|
|
453
|
+
disabled: '@inherit', // a map with '' replaces the parent's; keep its disabled branch
|
|
454
|
+
},
|
|
402
455
|
color: '#on-danger',
|
|
403
456
|
},
|
|
404
457
|
});
|
|
@@ -414,6 +467,53 @@ See [Methodology — styles prop vs style prop](methodology.md#styles-prop-vs-st
|
|
|
414
467
|
|
|
415
468
|
---
|
|
416
469
|
|
|
470
|
+
## Guardrails: linting and types
|
|
471
|
+
|
|
472
|
+
A vocabulary only holds if mistakes surface early. Register the same names you pass to `configure()` with the ESLint plugin and with TypeScript:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
// tasty.config.ts — read by @tenphi/eslint-plugin-tasty
|
|
476
|
+
export default {
|
|
477
|
+
tokens: ['#surface', '#text', '#primary', '#danger', '$gap', '$radius'],
|
|
478
|
+
states: ['@mobile', '@tablet', '@desktop', '@dark', '@reduced-motion'],
|
|
479
|
+
presets: ['h1', 'h2', 'h3', 't2', 't3'],
|
|
480
|
+
recipes: ['card', 'elevated', 'input-reset', 'interactive'],
|
|
481
|
+
importSources: ['@my-org/ds'], // where product code imports tasty() from
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
// eslint.config.js
|
|
487
|
+
import tasty from '@tenphi/eslint-plugin-tasty';
|
|
488
|
+
|
|
489
|
+
export default [tasty.configs.recommended]; // or tasty.configs.strict
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
// tasty.d.ts
|
|
494
|
+
import '@tenphi/tasty';
|
|
495
|
+
|
|
496
|
+
declare module '@tenphi/tasty' {
|
|
497
|
+
interface TastyNamedColors {
|
|
498
|
+
surface: true;
|
|
499
|
+
text: true;
|
|
500
|
+
primary: true;
|
|
501
|
+
danger: true;
|
|
502
|
+
}
|
|
503
|
+
interface TastyPresetNames {
|
|
504
|
+
h1: true;
|
|
505
|
+
h2: true;
|
|
506
|
+
h3: true;
|
|
507
|
+
t2: true;
|
|
508
|
+
t3: true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Unknown colors, presets, and recipes then fail lint (`valid-color-token`, `valid-preset`, `valid-recipe`), and `strict` adds checks for unknown `$` tokens and state aliases (`valid-custom-property`, `no-unknown-state-alias`). The same names autocomplete in editors. Deriving both lists from the same token and recipe modules that `config.ts` imports keeps all three in sync.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
417
517
|
## Project structure
|
|
418
518
|
|
|
419
519
|
A recommended structure for a design system built on Tasty:
|
|
@@ -421,6 +521,8 @@ A recommended structure for a design system built on Tasty:
|
|
|
421
521
|
```
|
|
422
522
|
ds/
|
|
423
523
|
config.ts # configure() — tokens, units, states, recipes
|
|
524
|
+
tasty.config.ts # Names the ESLint plugin validates against
|
|
525
|
+
tasty.d.ts # TypeScript augmentation for autocomplete
|
|
424
526
|
primitives/
|
|
425
527
|
Space.tsx # Layout: flex container with FLOW_STYLES
|
|
426
528
|
Box.tsx # Generic container
|
|
@@ -436,7 +538,7 @@ ds/
|
|
|
436
538
|
tokens/
|
|
437
539
|
colors.ts # Color token definitions
|
|
438
540
|
typography.ts # Typography presets for configure({ presets })
|
|
439
|
-
|
|
541
|
+
units.ts # Spacing and shape scale for configure({ units })
|
|
440
542
|
index.ts # Public API: re-exports components + configure
|
|
441
543
|
```
|
|
442
544
|
|