@tenphi/tasty 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,291 @@
1
+ # Runtime API
2
+
3
+ The React-specific `tasty()` component factory, component props, and hooks. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md).
4
+
5
+ ---
6
+
7
+ ## Component Creation
8
+
9
+ ### Create a new component
10
+
11
+ ```jsx
12
+ import { tasty } from '@tenphi/tasty';
13
+
14
+ const Card = tasty({
15
+ as: 'div',
16
+ styles: {
17
+ padding: '4x',
18
+ fill: '#white',
19
+ border: true,
20
+ radius: true,
21
+ },
22
+ styleProps: ['padding', 'fill'],
23
+ });
24
+
25
+ <Card>Hello World</Card>
26
+ <Card padding="6x" fill="#gray.05">Custom Card</Card>
27
+ ```
28
+
29
+ ### Extend an existing component
30
+
31
+ ```jsx
32
+ const PrimaryButton = tasty(Button, {
33
+ styles: {
34
+ fill: '#purple',
35
+ color: '#white',
36
+ padding: '2x 4x',
37
+ },
38
+ });
39
+ ```
40
+
41
+ Style maps merge intelligently — see [Style DSL — Extending vs. Replacing State Maps](dsl.md#extending-vs-replacing-state-maps) for extend mode, replace mode, `@inherit`, `null`, and `false` tombstones.
42
+
43
+ ---
44
+
45
+ ## Style Props
46
+
47
+ Use `styleProps` to expose style properties as direct component props:
48
+
49
+ ```jsx
50
+ const FlexibleBox = tasty({
51
+ as: 'div',
52
+ styles: {
53
+ display: 'flex',
54
+ padding: '2x',
55
+ },
56
+ styleProps: ['gap', 'align', 'placeContent', 'fill'],
57
+ });
58
+
59
+ <FlexibleBox gap="2x" align="center" fill="#surface">
60
+ Content
61
+ </FlexibleBox>
62
+ ```
63
+
64
+ Style props accept state maps, so responsive values work through the same API:
65
+
66
+ ```jsx
67
+ <FlexibleBox
68
+ gap={{ '': '2x', '@tablet': '4x' }}
69
+ fill={{ '': '#surface', '@dark': '#surface-dark' }}
70
+ >
71
+ ```
72
+
73
+ For predefined style prop lists (`FLOW_STYLES`, `POSITION_STYLES`, `DIMENSION_STYLES`, etc.) and guidance on which props to expose per component category, see [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api).
74
+
75
+ ---
76
+
77
+ ## Variants
78
+
79
+ Define named style variations. Only CSS for variants actually used at runtime is injected:
80
+
81
+ ```jsx
82
+ const Button = tasty({
83
+ styles: {
84
+ padding: '2x 4x',
85
+ border: true,
86
+ },
87
+ variants: {
88
+ default: { fill: '#blue', color: '#white' },
89
+ danger: { fill: '#red', color: '#white' },
90
+ outline: { fill: 'transparent', color: '#blue', border: '1bw solid #blue' },
91
+ },
92
+ });
93
+
94
+ <Button variant="danger">Delete</Button>
95
+ ```
96
+
97
+ ### Extending Variants with Base State Maps
98
+
99
+ When base `styles` contain an extend-mode state map (an object **without** a `''` key), it is applied **after** the variant merge. This lets you add or override states across all variants without repeating yourself:
100
+
101
+ ```jsx
102
+ const Badge = tasty({
103
+ styles: {
104
+ padding: '1x 2x',
105
+ border: {
106
+ 'type=primary': '#clear',
107
+ },
108
+ },
109
+ variants: {
110
+ primary: {
111
+ border: { '': '#white.2', pressed: '#primary-text', disabled: '#clear' },
112
+ fill: { '': '#white #primary', hovered: '#white #primary-text' },
113
+ },
114
+ secondary: {
115
+ border: { '': '#primary.15', pressed: '#primary.3' },
116
+ fill: '#primary.10',
117
+ },
118
+ },
119
+ });
120
+
121
+ // Both variants get 'type=primary': '#clear' appended to their border map
122
+ ```
123
+
124
+ Properties that are **not** extend-mode (simple values, state maps with `''`, `null`, `false`, selectors, sub-elements) merge with variants as before — the variant can fully replace them.
125
+
126
+ ---
127
+
128
+ ## Sub-element Styling
129
+
130
+ Sub-elements are inner parts of a compound component, styled via capitalized keys in `styles` and identified by `data-element` attributes in the DOM.
131
+
132
+ > Use the `elements` prop to declare sub-element components. This gives you typed, reusable sub-components (`Card.Title`, `Card.Content`) instead of manually writing `data-element` attributes.
133
+
134
+ ```jsx
135
+ const Card = tasty({
136
+ styles: {
137
+ padding: '4x',
138
+ Title: { preset: 'h3', color: '#primary' },
139
+ Content: { color: '#text' },
140
+ },
141
+ elements: {
142
+ Title: 'h3',
143
+ Content: 'div',
144
+ },
145
+ });
146
+
147
+ <Card>
148
+ <Card.Title>Card Title</Card.Title>
149
+ <Card.Content>Card content</Card.Content>
150
+ </Card>
151
+ ```
152
+
153
+ Each entry in `elements` can be a tag name string or a config object:
154
+
155
+ ```jsx
156
+ elements: {
157
+ Title: 'h3', // shorthand: tag name only
158
+ Icon: { as: 'span', qa: 'card-icon' }, // full form: tag + QA attribute
159
+ }
160
+ ```
161
+
162
+ The sub-components produced by `elements` support `mods`, `tokens`, `isDisabled`, `isHidden`, and `isChecked` props — the same modifier interface as the root component.
163
+
164
+ If you don't need sub-components (e.g., the inner elements are already rendered by a third-party library), you can still style them by key alone — just omit `elements` and apply `data-element` manually:
165
+
166
+ ```jsx
167
+ const Card = tasty({
168
+ styles: {
169
+ padding: '4x',
170
+ Title: { preset: 'h3', color: '#primary' },
171
+ },
172
+ });
173
+
174
+ <Card>
175
+ <div data-element="Title">Card Title</div>
176
+ </Card>
177
+ ```
178
+
179
+ ### Selector Affix (`$`)
180
+
181
+ Control how a sub-element selector attaches to the root selector using the `$` property inside the sub-element's styles:
182
+
183
+ | Pattern | Result | Description |
184
+ |---------|--------|-------------|
185
+ | *(none)* | ` [el]` | Descendant (default) |
186
+ | `>` | `> [el]` | Direct child |
187
+ | `>Body>Row>` | `> [Body] > [Row] > [el]` | Chained elements |
188
+ | `::before` | `::before` | Root pseudo (no key) |
189
+ | `@::before` | `[el]::before` | Pseudo on the sub-element |
190
+ | `>@:hover` | `> [el]:hover` | Pseudo-class on the sub-element |
191
+ | `>@.active` | `> [el].active` | Class on the sub-element |
192
+
193
+ The `@` placeholder marks exactly where the `[data-element="..."]` selector is injected, allowing you to attach pseudo-classes, pseudo-elements, or class selectors directly to the sub-element instead of the root:
194
+
195
+ ```jsx
196
+ const List = tasty({
197
+ styles: {
198
+ Item: {
199
+ $: '>@:last-child',
200
+ border: 'none',
201
+ },
202
+ },
203
+ });
204
+ // → .t0 > [data-element="Item"]:last-child { border: none }
205
+ ```
206
+
207
+ For the mental model behind sub-elements — how they share root state context and how this differs from BEM — see [Methodology — Component architecture](methodology.md#component-architecture-root--sub-elements).
208
+
209
+ ---
210
+
211
+ ## Hooks
212
+
213
+ ### useStyles
214
+
215
+ Generate a className from a style object:
216
+
217
+ ```tsx
218
+ import { useStyles } from '@tenphi/tasty';
219
+
220
+ function MyComponent() {
221
+ const { className } = useStyles({
222
+ padding: '2x',
223
+ fill: '#surface',
224
+ radius: '1r',
225
+ });
226
+
227
+ return <div className={className}>Styled content</div>;
228
+ }
229
+ ```
230
+
231
+ ### useGlobalStyles
232
+
233
+ Inject global styles for a CSS selector:
234
+
235
+ ```tsx
236
+ import { useGlobalStyles } from '@tenphi/tasty';
237
+
238
+ function ThemeStyles() {
239
+ useGlobalStyles('.card', {
240
+ padding: '4x',
241
+ fill: '#surface',
242
+ radius: '1r',
243
+ });
244
+
245
+ return null;
246
+ }
247
+ ```
248
+
249
+ ### useRawCSS
250
+
251
+ Inject raw CSS strings:
252
+
253
+ ```tsx
254
+ import { useRawCSS } from '@tenphi/tasty';
255
+
256
+ function GlobalReset() {
257
+ useRawCSS(`
258
+ body { margin: 0; padding: 0; }
259
+ `);
260
+
261
+ return null;
262
+ }
263
+ ```
264
+
265
+ ### useMergeStyles
266
+
267
+ Merge multiple style objects for compound component prop forwarding:
268
+
269
+ ```tsx
270
+ import { useMergeStyles } from '@tenphi/tasty';
271
+
272
+ function MyTabs({ styles, tabListStyles, prefixStyles }) {
273
+ const mergedStyles = useMergeStyles(styles, {
274
+ TabList: tabListStyles,
275
+ Prefix: prefixStyles,
276
+ });
277
+
278
+ return <TabsElement styles={mergedStyles} />;
279
+ }
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Learn more
285
+
286
+ - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
287
+ - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
288
+ - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
289
+ - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
290
+ - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
291
+ - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
package/docs/ssr.md CHANGED
@@ -2,7 +2,17 @@
2
2
 
3
3
  Tasty supports server-side rendering with zero-cost client hydration. Your existing `tasty()` components work unchanged -- SSR is opt-in and requires no per-component modifications.
4
4
 
5
- **Requires React 19+.**
5
+ ---
6
+
7
+ ## Requirements
8
+
9
+ | Dependency | Version | Required for |
10
+ |---|---|---|
11
+ | `react` | >= 18 | All SSR entry points (peer dependency of `@tenphi/tasty`) |
12
+ | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
13
+ | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
14
+
15
+ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies beyond `react`.
6
16
 
7
17
  ---
8
18
 
package/docs/styles.md CHANGED
@@ -344,7 +344,7 @@ Box shadow. Supports multiple shadows comma-separated.
344
344
 
345
345
  | Value | Effect |
346
346
  |-------|--------|
347
- | `true` | Default shadow (from `$shadow` token) |
347
+ | `true` | Default shadow (uses `var(--shadow)` — define a `$shadow` token in your design system) |
348
348
  | `"1x .5x .5x #dark.50"` | Custom shadow with Tasty units/colors |
349
349
  | `"0 1x 2x #dark.20"` | Standard shadow |
350
350
  | `"inset 0 1x 2x #dark.10"` | Inset shadow |
@@ -511,7 +511,7 @@ Scrollbar styling using CSS standard properties (`scrollbar-width`, `scrollbar-c
511
511
 
512
512
  **Colors:** Up to 2 color values for thumb and track (optional), applied via `scrollbar-color`.
513
513
 
514
- **Defaults:** When no colors are specified, the thumb color comes from the `#scrollbar-thumb` token (`#text.5`) and the track color from the `#scrollbar-track` token (`#dark-bg`). These tokens are provided by the base token set. If the base tokens are not loaded, the track falls back to `transparent` via a CSS fallback, while the thumb has no CSS-level fallback — the browser treats the entire `scrollbar-color` declaration as invalid and reverts to the platform-default scrollbar appearance.
514
+ **Defaults:** When no colors are specified, the thumb color comes from `var(--scrollbar-thumb-color)` and the track color from `var(--scrollbar-track-color, transparent)`. These are not built-in — your design system should define `#scrollbar-thumb` and `#scrollbar-track` tokens (e.g. `'#text.5'` and `'#dark-bg'`). If neither token is defined, the track falls back to `transparent` via a CSS fallback, while the thumb has no CSS-level fallback — the browser treats the entire `scrollbar-color` declaration as invalid and reverts to the platform-default scrollbar appearance.
515
515
 
516
516
  **Note:** `none` takes precedence over all other modifiers and colors. Combining `none` with other tokens (e.g., `"none always #red"`) produces a warning, and only `scrollbar-width: none` is applied.
517
517
 
@@ -11,9 +11,30 @@
11
11
  - **Performance-critical pages** — Zero runtime overhead for styling
12
12
  - **Landing pages** — Minimal bundle size with pre-generated CSS
13
13
 
14
- ## Quick Start
14
+ ## Requirements
15
+
16
+ The zero-runtime mode is part of the main `@tenphi/tasty` package but requires additional peer dependencies depending on your setup:
17
+
18
+ | Dependency | Version | Required for |
19
+ |---|---|---|
20
+ | `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
21
+ | `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
22
+ | `@babel/types` | >= 7.24 | Babel plugin |
23
+ | `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/next`) when using `configFile` option |
24
+
25
+ All of these are declared as optional peer dependencies of `@tenphi/tasty`. Install only what your setup requires:
26
+
27
+ ```bash
28
+ # For any Babel-based setup (Vite, custom Babel config, etc.)
29
+ pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types
30
+
31
+ # For Next.js with TypeScript config file
32
+ pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types jiti
33
+ ```
34
+
35
+ ---
15
36
 
16
- The zero-runtime mode is part of the main `@tenphi/tasty` package. No additional packages required.
37
+ ## Quick Start
17
38
 
18
39
  ### Basic Usage
19
40
 
@@ -134,6 +155,9 @@ module.exports = {
134
155
  | Option | Type | Default | Description |
135
156
  |--------|------|---------|-------------|
136
157
  | `output` | `string` | `'tasty.css'` | Path for generated CSS file |
158
+ | `configFile` | `string` | — | Absolute path to a TS/JS module that default-exports a `TastyZeroConfig` object. JSON-serializable alternative to `config` — required for Turbopack. |
159
+ | `config` | `TastyZeroConfig \| () => TastyZeroConfig` | `{}` | Inline config object or factory function. Takes precedence over `configFile`. |
160
+ | `configDeps` | `string[]` | `[]` | Absolute file paths that affect config (for cache invalidation) |
137
161
  | `config.states` | `Record<string, string>` | `{}` | Predefined state aliases |
138
162
  | `config.devMode` | `boolean` | `false` | Add source comments to CSS |
139
163
  | `config.recipes` | `Record<string, RecipeStyles>` | `{}` | Predefined style recipes |
@@ -176,23 +200,43 @@ const card = tastyStatic({
176
200
 
177
201
  ## Next.js Integration
178
202
 
179
- ```javascript
180
- // next.config.js
181
- const { withTastyZero } = require('@tenphi/tasty/next');
203
+ The `withTastyZero` wrapper configures both **webpack** and **Turbopack** automatically. No `--webpack` flag is needed — it works with whichever bundler Next.js uses.
204
+
205
+ ```typescript
206
+ // next.config.ts
207
+ import { withTastyZero } from '@tenphi/tasty/next';
182
208
 
183
- module.exports = withTastyZero({
209
+ export default withTastyZero({
184
210
  output: 'public/tasty.css',
185
- config: {
186
- states: {
187
- '@mobile': '@media(w < 768px)',
188
- },
189
- devMode: process.env.NODE_ENV === 'development',
190
- },
211
+ configFile: './app/tasty-zero.config.ts',
212
+ configDeps: ['./app/theme.ts'],
191
213
  })({
192
214
  reactStrictMode: true,
193
215
  });
194
216
  ```
195
217
 
218
+ ### `withTastyZero` Options
219
+
220
+ | Option | Type | Default | Description |
221
+ |--------|------|---------|-------------|
222
+ | `output` | `string` | `'public/tasty.css'` | Output path for CSS relative to project root |
223
+ | `enabled` | `boolean` | `true` | Enable/disable the plugin |
224
+ | `configFile` | `string` | — | Path to a TS/JS module that default-exports `TastyZeroConfig`. Recommended for Turbopack compatibility. |
225
+ | `config` | `TastyZeroConfig` | — | Inline config object. For static configs that don't change during dev. |
226
+ | `configDeps` | `string[]` | `[]` | Extra files the config depends on (for cache invalidation) |
227
+
228
+ ### Turbopack Support
229
+
230
+ Starting with Next.js 16, Turbopack is the default bundler. `withTastyZero` supports it out of the box by injecting `turbopack.rules` with `babel-loader` and JSON-serializable options.
231
+
232
+ The `configFile` option is key for Turbopack — it passes a file path (JSON-serializable) instead of a function, and the Babel plugin loads the config internally via jiti.
233
+
234
+ **Requirements**: `babel-loader` must be installed in your project:
235
+
236
+ ```bash
237
+ pnpm add babel-loader
238
+ ```
239
+
196
240
  ### Including the CSS
197
241
 
198
242
  ```tsx
@@ -372,5 +416,6 @@ const card = tastyStatic({
372
416
 
373
417
  ## Related
374
418
 
375
- - [Usage Guide](usage.md) — Runtime styling: component creation, state mappings, sub-elements, variants, and hooks
419
+ - [Style DSL](dsl.md) — State maps, tokens, units, extending semantics (shared by runtime and static)
420
+ - [Runtime API](runtime.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements, hooks
376
421
  - [Configuration](configuration.md) — Global configuration: tokens, recipes, custom units, and style handlers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -92,7 +92,8 @@
92
92
  "@babel/core": "^7.24.0",
93
93
  "@babel/helper-plugin-utils": "^7.24.0",
94
94
  "@babel/types": "^7.24.0",
95
- "react": "^18.0.0 || ^19.0.0"
95
+ "react": "^18.0.0 || ^19.0.0",
96
+ "jiti": "^2.6.1"
96
97
  },
97
98
  "peerDependenciesMeta": {
98
99
  "@babel/core": {
@@ -106,11 +107,13 @@
106
107
  },
107
108
  "react": {
108
109
  "optional": true
110
+ },
111
+ "jiti": {
112
+ "optional": true
109
113
  }
110
114
  },
111
115
  "dependencies": {
112
- "csstype": "^3.1.0",
113
- "jiti": "^2.6.1"
116
+ "csstype": "^3.1.0"
114
117
  },
115
118
  "devDependencies": {
116
119
  "@babel/core": "^7.24.0",
@@ -176,7 +179,7 @@
176
179
  "path",
177
180
  "crypto"
178
181
  ],
179
- "limit": "37 kB"
182
+ "limit": "38 kB"
180
183
  }
181
184
  ],
182
185
  "scripts": {
@@ -1,19 +0,0 @@
1
- //#region src/tokens/typography.d.ts
2
- /**
3
- * Typography preset configuration.
4
- * Each preset defines font properties that get expanded into CSS custom properties.
5
- */
6
- interface TypographyPreset {
7
- fontSize: string;
8
- lineHeight: string;
9
- letterSpacing?: string;
10
- fontWeight: string | number;
11
- boldFontWeight?: string | number;
12
- iconSize?: string;
13
- textTransform?: string;
14
- fontFamily?: string;
15
- fontStyle?: string;
16
- }
17
- //#endregion
18
- export { TypographyPreset };
19
- //# sourceMappingURL=typography.d.ts.map