@tenphi/cookbook 0.21.1 → 0.21.4

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/dist/styling.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { Styles, defineComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles } from "@tenphi/renderer/styling";
3
- export { type Styles, defineComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles };
2
+ import { Button, Styles, defineComponent, extendComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles } from "@tenphi/renderer/styling";
3
+ export { Button, type Styles, defineComponent, extendComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles };
package/dist/styling.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { defineComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles } from "@tenphi/renderer/styling";
3
- export { defineComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles };
2
+ import { Button, defineComponent, extendComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles } from "@tenphi/renderer/styling";
3
+ export { Button, defineComponent, extendComponent, mergeStyles, resolveComponentStyles, tasty, useGlobalStyles };
@@ -47,8 +47,9 @@ Theme resolution has three deliberately separate layers:
47
47
  colors.
48
48
  2. The Glaze values, design tokens, typography presets, states, and units are
49
49
  registered directly with Tasty.
50
- 3. Cookbook components use `tasty()` and the Tasty selector bridge styles DOM
51
- owned by Astro, Pagefind, and rendered Markdown.
50
+ 3. Cookbook interface components use `tasty()` roots. Component-owned Tasty
51
+ bridges style generated Markdown and syntax markup. Search owns a styled
52
+ results root whose named descendants cover Pagefind's dynamic content.
52
53
 
53
54
  The palette owns color relationships. Components never choose raw light/dark
54
55
  colors, and shape tokens never contain palette logic. This keeps a palette
@@ -56,6 +57,50 @@ change, a density change, and a typography change independent. Astro runs
56
57
  Tasty in extract mode, so these runtime style calls become a shared static CSS
57
58
  asset during the build; no Tasty styling runtime ships to the browser.
58
59
 
60
+ ## Component ownership
61
+
62
+ Each built-in component has an owning `components/<Name>.styles.js` module
63
+ with its complete base style tree and named sub-elements. Its Astro markup
64
+ imports that definition directly. Rendered roots use
65
+ `defineComponent(name, options)` to apply partial `theme.styles`
66
+ overrides before extraction. The published component names and sub-element
67
+ lists define the public customization contract.
68
+
69
+ Owned markup uses Tasty’s `data-element` identities; a named part usually needs
70
+ no explicit `$` selector. Keep structural selectors for generated content and
71
+ relationships between parts. Put conditions, including heading levels, in
72
+ property state maps. Each actual part owns its complete styles; avoid broad
73
+ selectors that also style parts with separate definitions.
74
+
75
+ Use paired tokens for block margins and padding. Contextual spacing rules change
76
+ only the relevant token; the owning shorthand consumes both edges. Spacing tokens
77
+ are registered with `inherits: false` and reset in document foundations so nested
78
+ content never inherits another element’s spacing.
79
+
80
+ Use `extendComponent(name, base, options)` for derived roots. `Button`
81
+ owns shared button defaults; `SearchButton` and `MobileMenuToggle` extend it
82
+ with their own layout and named anatomy. Base theme overrides are inherited,
83
+ then the descendant's styles and theme overrides take precedence. Keep shared
84
+ bases configurable and document their names and complete sub-element lists.
85
+
86
+ The page shell owns fonts, document defaults, heading presets, and root layout
87
+ variables. These foundations initialize during every page render, including
88
+ 404 pages, independently of replaceable Header and Head components. Layout
89
+ roots own frame positioning; the components inside them own their contents.
90
+
91
+ `useGlobalStyles()` is reserved for document foundations and generated-content
92
+ bridges, each using `resolveComponentStyles(name, baseStyles)`. MarkdownContent
93
+ collects its prose, code, table, heading, alert, and Mermaid bridges during
94
+ rendering. Generated prose retains its established selector scope and
95
+ specificity so partial theme overrides and styled components inside Markdown
96
+ continue to compose. CodeGroup collects its code and syntax bridges itself,
97
+ including when rendered outside MarkdownContent.
98
+
99
+ Optional browser behavior remains in `src/client/` and imports no styling
100
+ modules. Custom elements attach behavior to the server-rendered markup.
101
+ The ownership checks verify every public style name and sub-element, while
102
+ consumer and browser tests exercise theme overrides and component replacements.
103
+
59
104
  ## Public graph API
60
105
 
61
106
  Renderer and tooling authors can work directly with `@tenphi/docs`:
@@ -6,9 +6,27 @@ For color roles and design tokens, start with the
6
6
  [theme overview](./theme-and-components.md). For your own components, see
7
7
  [Custom components](./custom-components.md).
8
8
 
9
+ Owned markup uses Tasty's built-in `data-element` identities for its named parts.
10
+ For example, Header's `Logo` styles target `data-element="Logo"` directly;
11
+ no anatomy class or separate marker is required. A configured image logo owns
12
+ its dimensions under `SiteLogo`.
13
+
14
+ Heading levels are states of the same element. Customize them through a root
15
+ property map such as `Heading: { textWrap: { "": "balance", ":is(h1)": "pretty" } }`.
16
+ Markdown heading wrappers use `.level-h1` through `.level-h6` as root states.
17
+
18
+ Block spacing uses `$margin-block-start` and `$margin-block-end` tokens. The
19
+ owning `blockMargin` consumes both tokens, and a surrounding prose/layout rule
20
+ changes only the start token. For example, `Callout: { "$margin-block-end": "3x" }`
21
+ changes the end spacing while retaining Markdown's spacing before the callout.
22
+ Document foundations reset these tokens on every element to prevent inheritance
23
+ into nested content. For content-panel padding, use `MainContent.Panel`'s
24
+ `$padding-block-start` and `$padding-block-end` tokens in the same way.
25
+
9
26
  ## Style customization
10
27
 
11
- Cookbook-owned interface elements are direct `tasty()` components. Supported
28
+ Cookbook-owned interface elements use `defineComponent()` to create Tasty components
29
+ and merge their named theme overrides. Shared bases use `extendComponent()`.
12
30
  Cookbook components and generated Markdown surfaces use Tasty style trees.
13
31
  Customize them by name under `theme.styles`; the configuration is resolved
14
32
  before CSS generation, so this is not a selector-based CSS override.
@@ -17,6 +35,12 @@ Cookbook merges your partial style object with the component's base styles
17
35
  before Tasty extracts CSS. The generated stylesheet contains the resolved
18
36
  style, so you can change an element without copying its full defaults.
19
37
 
38
+ Each surface is registered by its owning component or generated-content bridge.
39
+ Fonts and document defaults belong to the page shell, so replacing Header
40
+ preserves the rest of the site's styling. Component-specific CSS is collected
41
+ when its owner renders. See [Component ownership](./architecture.md#component-ownership)
42
+ for the renderer authoring convention.
43
+
20
44
  Start with the area you want to change, then use the complete sub-element
21
45
  inventory below to find its exact anatomy:
22
46
 
@@ -59,17 +83,22 @@ Cookbook registers these Tasty state aliases for `theme.styles`, custom
59
83
  components, and recipes. Widths use CSS `rem` units; their pixel values depend
60
84
  on the browser's font-size settings.
61
85
 
62
- | State | Condition | Typical use |
63
- | ----------------- | -------------------------------- | -------------------------------------------------- |
64
- | `@compact` | width ≤ 23rem | Hide optional header controls in very narrow space |
65
- | `@small` | width ≤ 40rem | Stack compact grids |
66
- | `@shell-mobile` | width ≤ 48rem | Switch the page shell to its narrow arrangement |
67
- | `@shell-desktop` | width > 48rem | Restore the wider shell arrangement |
68
- | `@mobile` | width < 50rem | Mobile navigation and component layouts |
69
- | `@desktop` | width ≥ 50rem | Desktop navigation and component layouts |
70
- | `@medium-layout` | 50rem ≤ width < 72rem | Intermediate two-column layouts |
71
- | `@narrow-layout` | width < 72rem | Hide or reposition the desktop table of contents |
72
- | `@reduced-motion` | `prefers-reduced-motion: reduce` | Remove optional motion |
86
+ | State | Condition | Typical use |
87
+ | ----------------- | ---------------------------------------------------------- | -------------------------------------------------- |
88
+ | `@compact` | width ≤ 23rem | Hide optional header controls in very narrow space |
89
+ | `@small` | width ≤ 40rem | Stack compact grids |
90
+ | `@shell-mobile` | width ≤ 48rem | Switch the page shell to its narrow arrangement |
91
+ | `@shell-desktop` | width > 48rem | Restore the wider shell arrangement |
92
+ | `@mobile` | width < 50rem | Mobile navigation and component layouts |
93
+ | `@desktop` | width ≥ 50rem | Desktop navigation and component layouts |
94
+ | `@medium-layout` | 50rem ≤ width < 72rem | Intermediate two-column layouts |
95
+ | `@narrow-layout` | width < 72rem | Hide or reposition the desktop table of contents |
96
+ | `@popover-open` | selected element is an open native popover | Use inside `@own(...)` for a descendant panel |
97
+ | `@reduced-motion` | `prefers-reduced-motion: reduce` | Remove optional motion |
98
+ | `@light` | explicit light theme, or system light when no theme is set | Match the site's effective light appearance |
99
+ | `@dark` | explicit dark theme, or system dark when no theme is set | Match the site's effective dark appearance |
100
+ | `@system-light` | `prefers-color-scheme: light` | Read the operating system preference directly |
101
+ | `@system-dark` | `prefers-color-scheme: dark` | Read the operating system preference directly |
73
102
 
74
103
  The 48rem shell boundary, 50rem navigation boundary, and 72rem contents
75
104
  boundary serve different parts of the layout; their ranges intentionally
@@ -78,6 +107,12 @@ condition. Add your own aliases with `theme.states`; redefining a built-in
78
107
  alias also changes the renderer's responsive layout, so check the whole site
79
108
  when doing so.
80
109
 
110
+ Use `@light` and `@dark` in component styles. They check the page root's
111
+ `data-theme` attribute and use the system preference only when that attribute
112
+ is absent. Document rules that style the root itself use `@system-light` and
113
+ `@system-dark` within their automatic-theme branch. Keep scheme media queries
114
+ in the shared state definitions instead of repeating them in style objects.
115
+
81
116
  In a generated site, run `npm run validate` (or the matching package manager's
82
117
  command) after editing `docs.config.ts`. It checks TypeScript theme properties,
83
118
  then runs Cookbook's preflight and production output checks. A misspelled
@@ -87,7 +122,34 @@ Provide only the root and named
87
122
  [sub-element](./upstream/tasty/docs/dsl.md#sub-element) properties you want to
88
123
  override. Cookbook deep-merges that partial style object into the complete
89
124
  base style object inside the renderer, following Tasty's
90
- [state-map merge semantics](./upstream/tasty/docs/dsl.md#extending-vs-replacing-state-maps):
125
+ [state-map merge semantics](./upstream/tasty/docs/dsl.md#extending-vs-replacing-state-maps).
126
+
127
+ Keep `$` selectors structural and put attribute or pseudo-class conditions in
128
+ property state maps. Use `@own(...)` for a descendant's state and ordinary
129
+ state keys for the component root. A sub-element cannot target its containing
130
+ element: use root state maps for callout kinds, hover/active buttons, compact
131
+ selectors, and syntax wrapping. Use Tasty element names such as `Primary > Search`
132
+ instead of spelling out `[data-element="Primary"] > [data-element="Search"]`.
133
+ A scalar override replaces the property's
134
+ whole state map; use the matching state key without a default entry to change
135
+ only that branch. `"": null` omits a declaration outside a condition. For
136
+ conditional `display`, `"": null` is also supported, including styles with
137
+ `flow` or `gap`.
138
+
139
+ For a compact language selector, customize the root with
140
+ `LanguageSelect: { border: { "[data-compact]": "4px solid #border" } }`.
141
+ The former `Compact` hook is now a root state. Likewise, customize code wrapping
142
+ with `SyntaxHighlight: { whiteSpace: { ".td-syntax-wrap": "pre-wrap" } }`.
143
+ Syntax category sub-elements such as `Keyword` target descendant spans; use
144
+ `SyntaxHighlight.color[".td-syntax-keyword"]` when customizing the containing
145
+ code element itself.
146
+
147
+ Cookbook temporarily retains selectors for pseudo-element states that Tasty
148
+ cannot yet compile correctly and four empty legacy customization hooks whose
149
+ conditional override scopes must remain compatible. Each has a narrow lint
150
+ exception in its owning style module. Use state maps for new definitions.
151
+
152
+ For example:
91
153
 
92
154
  ```ts
93
155
  theme: {
@@ -103,7 +165,7 @@ theme: {
103
165
  },
104
166
  TopNavigation: {
105
167
  Link: { preset: "body" },
106
- CurrentLink: { color: "#accent-text" }
168
+ CurrentLink: { color: { '@own([aria-current="page"])': "#accent-text" } }
107
169
  },
108
170
  Sidebar: {
109
171
  LinkLabel: { whiteSpace: "normal" }
@@ -132,53 +194,54 @@ it stays complete when a surface changes.
132
194
 
133
195
  #### Page shell
134
196
 
135
- * `Document`: `All`, `Body`, `Control`, `Pointer`, `ResponsiveWidth`, `ResponsiveHeight`, `Hidden`, `PrintHidden`, `DesktopBlock`, `DesktopFlex`, `ScreenReaderOnly`, `Strong`, `Link`, `NarrowBlock`, `MobileBlock`, `Code`, `FocusRing`, `CurrentLink`, `SearchOpen`
136
- * `Layout`: `Islands`, `LockedPage`, `Light`, `Auto`
197
+ * `Document`: `All`, `BlockSpacing`, `Body`, `Control`, `Pointer`, `ResponsiveWidth`, `ResponsiveHeight`, `Hidden`, `PrintHidden`, `DesktopBlock`, `DesktopFlex`, `ScreenReaderOnly`, `Strong`, `Link`, `NarrowBlock`, `MobileBlock`, `Code`, `FocusRing`, `CurrentLink`, `SearchOpen`
198
+ * `Layout`: `Islands`
137
199
  * `PageFrame`: `MainFrame`, `SidebarFrame`, `Columns`
138
- * `MainPane`: `WithSidebars`
139
- * `MainContent`: `ContentSpacing`, `Container`, `Panel`, `FirstPanel`, `BodyPanel`
200
+ * `MainPane`: None
201
+ * `MainContent`: `ContentSpacing`, `Container`, `Panel`
140
202
  * `HeaderFrame`: None
141
- * `Heading`: `Level1`, `Level2`, `Level3`, `Level4`, `Level5`, `Level6`, `PageTitle`
203
+ * `Heading`: None
142
204
  * `Banner`: `Link`
143
- * `SkipLink`: `Focus`
205
+ * `SkipLink`: None
144
206
 
145
207
  #### Navigation and controls
146
208
 
147
- * `Header`: `Primary`, `TitleAndSearch`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `ToolItem`, `Social`, `MobileTheme`, `MobileLanguage`
209
+ * `Header`: `Primary`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `Social`, `MobileTheme`, `MobileLanguage`
148
210
  * `HeaderLinks`: `Desktop`, `DesktopLink`, `Link`, `HoverLink`, `PrimaryLink`, `HoverPrimaryLink`, `Trigger`, `HoverTrigger`, `Panel`, `OpenPanel`, `PanelNavigation`, `PanelLink`, `FirstPanelLink`, `Close`, `HoverClose`
149
- * `SearchButton`: `PendingShortcut`, `Label`, `Shortcut`, `Hover`, `Active`, `NativeIcon`, `Icon`
150
- * `Sidebar`: `Backdrop`, `OpenBackdrop`, `MobileHeading`, `HomeLink`, `HomeLogo`, `HomeLabel`, `Close`, `HoverClose`, `CloseIcon`, `CurrentLink`, `OpenPane`, `EnteredPane`, `Content`, `Tree`, `List`, `Item`, `TopLevelSpacing`, `GroupSpacing`, `NestedItem`, `SectionHeading`, `Control`, `Summary`, `GroupLabel`, `GroupLabelText`, `Link`, `LinkLabel`, `InteractiveControl`, `SummaryMarker`, `Caret`, `ExpandedCaret`, `LinkedSummary`, `GroupLink`, `LinkedSectionHeading`, `SectionLink`, `Badge`, `TopLevelLink`
151
- * `MobileMenuToggle`: `Control`, `Icon`, `Section`, `Page`, `HoverControl`, `ActiveControl`
211
+ * `SearchButton`: `PendingShortcut`, `Label`, `Shortcut`, `NativeIcon`, `Icon`
212
+ * `Button`: None
213
+ * `Sidebar`: `Backdrop`, `OpenBackdrop`, `MobileHeading`, `HomeLink`, `HomeLogo`, `HomeLabel`, `Close`, `HoverClose`, `CloseIcon`, `CurrentLink`, `Content`, `Tree`, `List`, `Item`, `TopLevelSpacing`, `GroupSpacing`, `NestedItem`, `SectionHeading`, `Control`, `Summary`, `GroupLabel`, `GroupLabelText`, `Link`, `LinkLabel`, `InteractiveControl`, `SummaryMarker`, `Caret`, `ExpandedCaret`, `LinkedSummary`, `GroupLink`, `LinkedSectionHeading`, `SectionLink`, `Badge`, `TopLevelLink`
214
+ * `MobileMenuToggle`: `Icon`, `Section`, `Page`
152
215
  * `MobileNavigationTabs`: `Trigger`, `Marker`, `Caret`, `ExpandedCaret`, `Label`, `List`, `Item`, `Link`, `HoverLink`, `CurrentLink`
153
216
  * `MobileMenuFooter`: `Social`
154
217
  * `TopNavigation`: `Scrollbar`, `Link`, `HoverLink`, `CurrentLink`, `ActiveIndicator`
155
- * `TableOfContentsLayout`: `WithMobile`, `Content`
218
+ * `TableOfContentsLayout`: `Content`
156
219
  * `TableOfContents`: `Heading`, `List`, `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink`
157
220
  * `MobileTableOfContents`: `Summary`, `List`, `NestedList`, `Item`, `Link`, `HoverLink`, `Focus`
158
221
  * `Pagination`: `Link`, `PreviousLink`, `NextLink`, `NextIcon`, `NextLabel`, `HoverLink`, `ActiveLink`, `Title`, `LoneNextLink`, `Icon`, `PreviousIconRtl`, `NextIconRtl`
159
222
  * `VersionSwitcher`: `Trigger`, `HoverTrigger`, `TriggerLabel`, `Caret`, `Panel`, `OpenPanel`, `PanelTitle`, `Options`, `Link`, `HoverLink`, `CurrentLink`, `Checkmark`, `SelectedCheckmark`
160
- * `LanguageSelect`: `Compact`, `Trigger`, `HoverTrigger`, `ActiveTrigger`, `LabelIcon`, `TriggerLabel`, `Caret`, `CompactTrigger`, `CompactLabel`, `CompactCaret`, `Label`, `HoverLabel`, `Select`, `CompactSelect`, `CompactLabelIcon`, `SidebarTrigger`, `Panel`, `SidebarPanel`, `OpenPanel`, `PanelTitle`, `Options`, `Option`, `HoverOption`, `CurrentOption`, `Fallback`, `Checkmark`, `SelectedCheckmark`
223
+ * `LanguageSelect`: `Trigger`, `HoverTrigger`, `ActiveTrigger`, `LabelIcon`, `TriggerLabel`, `Caret`, `CompactTrigger`, `CompactLabel`, `CompactCaret`, `Label`, `HoverLabel`, `Select`, `CompactSelect`, `CompactLabelIcon`, `SidebarTrigger`, `Panel`, `SidebarPanel`, `OpenPanel`, `PanelTitle`, `Options`, `Option`, `HoverOption`, `CurrentOption`, `Fallback`, `Checkmark`, `SelectedCheckmark`
161
224
  * `SocialIcons`: `Link`, `HoverLink`, `Icon`
162
225
  * `ThemeSelect`: `Trigger`, `HoverTrigger`, `ActiveTrigger`, `Icon`, `Panel`, `OpenPanel`, `Section`, `SectionSpacing`, `SectionLabel`, `Option`, `HoverOption`, `CheckedOption`, `FocusedOption`, `Input`, `OptionIcon`, `Checkmark`, `SelectedCheckmark`
163
226
 
164
227
  #### Rendered content
165
228
 
166
229
  * `Markdown`: `Block`, `BlockSpacing`, `HeadingSpacing`, `List`, `CompactItem`, `ListItem`, `DefinitionTerm`, `DefinitionDescription`, `Link`, `HoverLink`, `Quote`, `Rule`, `Details`, `HoverDetails`, `Summary`, `OpenSummary`, `SummaryMarker`, `SummaryIcon`, `OpenSummaryIcon`, `Code`
167
- * `MarkdownHeading`: `Heading`, `Heading1`, `Heading2`, `Heading3`, `Heading4`, `Heading5`, `Heading6`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon`
230
+ * `MarkdownHeading`: `Heading`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon`
168
231
  * `MarkdownCodeBlock`: `Pre`, `CopyButton`, `HoverCopyButton`, `CopiedButton`, `CopyIcon`, `CopiedIcon`, `Code`, `Diff`, `DiffCode`, `DiffLine`, `EmptyDiffLine`, `InsertedLine`, `DeletedLine`
169
232
  * `MarkdownInlineCode`: None
170
233
  * `MarkdownTable`: `Table`, `Cell`, `LastBodyRowCell`, `HeaderCell`, `Scroll`
171
- * `MarkdownAlert`: `Note`, `Tip`, `Caution`, `Danger`, `Title`, `FirstContent`
172
- * `SyntaxHighlight`: `Scroll`, `Wrap`, `Marker`, `Comment`, `Punctuation`, `Keyword`, `String`, `Token`, `Property`, `Number`, `Function`, `Value`, `Operator`, `Text`, `Bg`, `Inserted`, `Deleted`, `Italic`, `Strong`, `Underline`
234
+ * `MarkdownAlert`: `Title`, `FirstContent`
235
+ * `SyntaxHighlight`: `Marker`, `Comment`, `Punctuation`, `Keyword`, `String`, `Token`, `Property`, `Number`, `Function`, `Value`, `Operator`, `Text`, `Bg`, `Inserted`, `Deleted`, `Italic`, `Strong`, `Underline`
173
236
  * `Mermaid`: `Diagram`, `Text`, `MonoText`
174
237
  * `MermaidSource`: None
175
238
 
176
239
  #### Authoring components
177
240
 
178
241
  * `Card`: `Heading2`, `Heading3`, `Paragraph`
179
- * `Callout`: `Title`, `Body`, `Tip`, `Caution`, `Danger`
242
+ * `Callout`: `Title`, `Body`
180
243
  * `CodeGroup`: `Caption`, `Pre`, `Code`
181
- * `Tab`: `Heading`, `Hidden`, `HiddenHeading`
244
+ * `Tab`: `Heading`, `HiddenHeading`
182
245
  * `Tabs`: `List`, `Button`, `SelectedButton`, `FocusedButton`
183
246
  * `Steps`: `Item`, `Marker`
184
247
  * `Hero`: `Visual`, `DarkVisual`, `LightVisual`, `Stack`, `Copy`, `Title`, `Tagline`, `Actions`, `Action`, `HoverAction`, `PrimaryAction`, `SecondaryAction`, `MinimalAction`, `ActionIcon`
@@ -186,7 +249,7 @@ it stays complete when a surface changes.
186
249
  * `SiteLogo`: `Image`, `Light`, `Dark`
187
250
  * `Logo`: `Svg`, `Mark`
188
251
  * `PageActions`: `Control`, `Hover`, `Focus`, `Pending`, `Status`
189
- * `Footer`: `Meta`, `LoneMetaItem`, `MetaLink`, `HoverMetaLink`, `Credit`, `CreditLink`, `HoverCreditLink`
252
+ * `Footer`: `Meta`, `MetaLink`, `MetaUpdated`, `Credit`, `CreditLink`
190
253
  * `PackageVersion`: None
191
254
 
192
255
  #### Search
@@ -232,7 +295,7 @@ same.
232
295
 
233
296
  Register custom names and their partial Tasty objects in `theme.customStyles`.
234
297
  Built-in names belong in `theme.styles` and reject misspelled sub-elements.
235
- The name must match the string passed to `defineComponent()` or
298
+ The name must match the string passed to `defineComponent()`, `extendComponent()`, or
236
299
  `resolveComponentStyles()`. A production
237
300
  build warns when a configured custom name has no matching component style
238
301
  resolver.
@@ -441,8 +441,8 @@ theme: {
441
441
  ```
442
442
 
443
443
  The default brand is `okhsl(266 68% 48%)`, a blue with 68% saturation.
444
- Controls use an `8px` radius and cards use `16px`. Onest is the default body
445
- and heading family; JetBrains Mono is used for code. `theme.fonts` loads Google
444
+ Controls use an `8px` radius and cards use `16px`. Onest Variable is the default body
445
+ and heading family; JetBrains Mono Variable is used for code. `theme.fonts` loads Google
446
446
  families by name or local files from `public/`. See
447
447
  [Fonts and typography](./fonts-and-typography.md) for both flows.
448
448
 
@@ -662,10 +662,31 @@ translations: {
662
662
 
663
663
  `navigation.<configured label>` translates named tabs and navigation groups.
664
664
  Automatically generated links use the title from the translated page.
665
- Cookbook message keys are `appearance`, `colorScheme`, `contrast`,
666
- `normalContrast`, `highContrast`, `auto`, `closeNavigation`, `primary`, `sections`,
667
- `chooseSection`, `headerLinks`, `more`, `closeMore`, `moreLinks`, `versions`,
668
- `documentationVersion`, `documentationVersions`, `version`, `home`,
669
- `generatedWith`, `viewSource`, `copyCode`, `codeCopied`, `codeCopyError`,
670
- `copyHeading`, `linkCopied`, `linkCopyError`, `notFound`, `notFoundDescription`,
671
- and `fallback`. Preserve `{heading}` in the `copyHeading` message.
665
+ The supported keys below keep independent overrides, including similar labels:
666
+ `auto` controls contrast, while `themeSelect.auto` controls color scheme;
667
+ `search.label` labels the search control, while `pagefind.search` supplies the
668
+ search input prompt. No migration or key renaming is needed.
669
+
670
+ | Keys | Controls or feedback |
671
+ | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
672
+ | `copyPage`, `viewMarkdown`, `pageCopied`, `pageCopyError` | Page Markdown actions and copy status. |
673
+ | `draftTitle`, `draftNotice` | Draft-page notice. |
674
+ | `searchError` | Search loading failure. |
675
+ | `appearance`, `colorScheme`, `contrast`, `normalContrast`, `highContrast`, `auto` | Appearance and contrast controls. |
676
+ | `closeNavigation`, `primary`, `sections`, `chooseSection` | Primary and mobile navigation. |
677
+ | `headerLinks`, `more`, `closeMore`, `moreLinks` | Header links and overflow menu. |
678
+ | `versions`, `documentationVersion`, `documentationVersions`, `version` | Version selection and package labels. |
679
+ | `home`, `generatedWith`, `viewSource` | Home, footer credit and preview source. |
680
+ | `copyCode`, `codeCopied`, `codeCopyError` | Code copy action and status. |
681
+ | `copyHeading`, `linkCopied`, `linkCopyError` | Heading link action and status. Preserve `{heading}` in `copyHeading`. |
682
+ | `notFound`, `notFoundDescription`, `fallback` | Missing-page and language fallback messages. |
683
+ | `sidebarNav.accessibleLabel`, `menuButton.accessibleLabel` | Sidebar navigation and menu accessible names. |
684
+ | `tableOfContents.onThisPage`, `languageSelect.accessibleLabel` | Contents heading and language selector. |
685
+ | `themeSelect.light`, `themeSelect.dark`, `themeSelect.auto` | Color-scheme options. |
686
+ | `search.label`, `search.ctrlKey`, `search.cancelLabel`, `search.devWarning` | Search control, shortcut, close action and development notice. |
687
+ | `page.nextLink`, `page.previousLink`, `page.editLink`, `page.lastUpdated`, `page.skipToContent` | Pagination, edit/update labels and skip link. |
688
+ | `pagefind.search`, `pagefind.clear_search`, `pagefind.zero_results`, `pagefind.many_results`, `pagefind.one_result` | Search input, clearing and result counts. Preserve `[COUNT]` in count messages. |
689
+
690
+ Custom component message keys are also preserved. Missing custom keys passed to
691
+ `Astro.locals.t(key)` return the key itself. Changing an override does not alter
692
+ other message keys, even when their default text is the same.
@@ -2,7 +2,6 @@
2
2
  title: Custom components
3
3
  description: Style your Astro and MDX components with Tasty and validate their tokens and parts.
4
4
  ---
5
-
6
5
  Use this guide after [theme configuration](./theme-and-components.md) when you
7
6
  need markup beyond Cookbook's built-in surfaces. For ordinary visual changes,
8
7
  consult the [component styles](./component-styles.md) reference first.
@@ -17,6 +16,8 @@ responsive states. The renderer package also exposes them from
17
16
  | Export | Purpose |
18
17
  | ------------------------ | ---------------------------------------------------------------------------------- |
19
18
  | `defineComponent` | Create a named Tasty component with its `theme.customStyles[name]` overrides. |
19
+ | `extendComponent` | Extend an existing Tasty component with a new name and its own theme overrides. |
20
+ | `Button` | Shared button foundation used by Cookbook controls and custom button components. |
20
21
  | `tasty` | Use Tasty directly without binding a component to `theme.customStyles`. |
21
22
  | `useGlobalStyles` | Collect a global Tasty style tree while rendering a page. |
22
23
  | `resolveComponentStyles` | Merge `theme.customStyles[name]` into a complete base style tree for global rules. |
@@ -235,6 +236,49 @@ that does not need a configuration name. To define a named component around
235
236
  an existing React component that forwards `className`, pass it as `as` in the options. Use `className`
236
237
  when passing a class to a Tasty component from Astro.
237
238
 
239
+ ### Extending a component
240
+
241
+ Use `extendComponent(name, base, options)` to inherit an existing Tasty
242
+ component's styles, props, variants, and compound parts:
243
+
244
+ ```ts
245
+ import { Button, extendComponent } from "@tenphi/cookbook/styling";
246
+
247
+ export const ProjectButton = extendComponent("ProjectButton", Button, {
248
+ type: "button",
249
+ styles: {
250
+ padding: "1x 2x",
251
+ radius: "$radius",
252
+ fill: "#surface",
253
+ Hover: { $: "&:hover", fill: "#surface-2-hover" },
254
+ },
255
+ });
256
+
257
+ export const CompactButton = extendComponent("CompactButton", ProjectButton, {
258
+ styles: { padding: "0.5x 1x" },
259
+ });
260
+ ```
261
+
262
+ `theme.styles.Button` customizes the shared foundation, including Cookbook's
263
+ search and mobile-menu buttons. `theme.customStyles.ProjectButton` and
264
+ `CompactButton` customize each derived component independently. `Button` has
265
+ no named sub-elements; this example adds `Hover` to both descendants.
266
+
267
+ The merge order is the base component's configured styles and selected variant,
268
+ the extension's styles, its named theme override, and styles passed while
269
+ rendering. Tasty merges named sub-elements and state maps using its usual
270
+ [extending and replacing rules](./upstream/tasty/docs/dsl.md#extending-vs-replacing-state-maps).
271
+ Changing a descendant never mutates its base or siblings. If a base exposes
272
+ parts such as `Button.Label`, the descendant exposes those same components.
273
+
274
+ Extension options accept styles and default values for existing props. Define
275
+ new `elements`, `variants`, `styleProps`, `modProps`, `tokenProps`, or an `as`
276
+ type on the original factory; Tasty's wrapping API does not install new factory
277
+ settings. Existing required props remain required in the derived component's
278
+ type. To compose reusable style fragments without a component base, use
279
+ `mergeStyles(baseStyles, extensionStyles)`; a shallow spread replaces entire
280
+ nested sub-elements and state maps.
281
+
238
282
  ### Global style trees
239
283
 
240
284
  For markup you do not render through a Tasty component, call
@@ -261,7 +305,7 @@ useGlobalStyles(
261
305
  <aside class="project-note"><strong>Note</strong><slot /></aside>
262
306
  ```
263
307
 
264
- `defineComponent` and `resolveComponentStyles` read the same `theme.customStyles`
308
+ `defineComponent`, `extendComponent`, and `resolveComponentStyles` read the same `theme.customStyles`
265
309
  configuration; user configuration contains only the properties to change. Each custom style name must match a call to one of these helpers.
266
310
 
267
311
  ## Linting custom styles
@@ -283,7 +327,7 @@ export default {
283
327
  Renderer-only consumers can use `extends: "@tenphi/renderer"` instead.
284
328
  The preset registers both Cookbook styling import paths, its built-in tokens,
285
329
  units, responsive states, and typography presets. It also describes
286
- `defineComponent`, `resolveComponentStyles`, and `mergeStyles`, so their inline
330
+ `defineComponent`, `extendComponent`, `resolveComponentStyles`, and `mergeStyles`, so their inline
287
331
  style objects, variants, and named sub-elements receive the same checks as
288
332
  `tasty()` calls. Partial overrides passed to `mergeStyles` may omit default state
289
333
  values and retain the plugin's safeguards against fixes that replace base styles.
@@ -8,10 +8,28 @@ named presets to the rendered site.
8
8
 
9
9
  ## Typography presets
10
10
 
11
- The built-in [typography presets](./upstream/tasty/docs/styles.md#preset) are
11
+ The base [typography presets](./upstream/tasty/docs/styles.md#preset) are
12
12
  `body`, `heading`, `h1` through `h6`, `navigation`, `small`, and `code`. Onest
13
- is self-hosted and used for body and heading text by default; JetBrains Mono is
14
- self-hosted for code.
13
+ Variable is self-hosted and used for body and heading text by default, with a
14
+ continuous weight range from `100` to `900`. This supports intermediate preset
15
+ weights such as `450` for navigation and `610` for headings. JetBrains Mono
16
+ Variable is self-hosted for code with weights from `100` to `800`.
17
+
18
+ Both default fonts bundle their Latin subset and require no Google Fonts
19
+ requests at build time or in the browser. For other character sets, configure
20
+ `theme.fonts` with a Google family or local files as described below.
21
+
22
+ Specialized roles preserve the component sizes while sharing their base typography:
23
+
24
+ | Preset | Used for | Base typography |
25
+ | -------------- | ----------------- | --------------------------------------------------- |
26
+ | `hero-title` | Hero heading | `h1`, with a larger responsive size |
27
+ | `hero-tagline` | Hero description | `body`, with a responsive size and 1.55 line height |
28
+ | `prose` | Markdown content | `body`, at 1.025rem |
29
+ | `inline-code` | Code within prose | `code`, at 0.875em |
30
+
31
+ Customize these through `theme.presets` as well. Changes to the base font family,
32
+ weight, tracking, and other shared fields continue to flow into the derived roles.
15
33
 
16
34
  ### Change font families
17
35
 
package/docs/migration.md CHANGED
@@ -54,7 +54,13 @@ Configure `theme.tokens` with `$name` keys, such as `$radius`. The old `--name`
54
54
  configuration keys now fail validation; the emitted CSS custom properties
55
55
  still use `--name`.
56
56
 
57
- Custom styles no longer target `data-tasty-anatomy` attributes automatically.
57
+ Built-in markup now uses Tasty’s `data-element` identities instead of
58
+ `data-tasty-anatomy`. Custom styles resolve through each owning definition.
59
+ Heading levels move from `Heading.Level1`–`Level6` to root property state maps
60
+ (`:is(h1)`–`:is(h6)`); Markdown wrappers use `.level-h1`–`.level-h6`.
61
+ Panel first/second-child padding moves into `MainContent.Panel` token state maps.
62
+ Header’s shared title/search defaults live on `Title` and `Search`; Footer’s
63
+ only-child and hover states live on `MetaLink`, `MetaUpdated`, and `CreditLink`.
58
64
  For a custom component, use `defineComponent(name, options)`. For custom global
59
65
  rules, pass `resolveComponentStyles(name, baseStyles)` to `useGlobalStyles()`.
60
66
  Both merge `theme.customStyles[name]` into the complete base styles during
@@ -1,10 +1,10 @@
1
1
  [
2
2
  {
3
3
  "name": "@tenphi/tasty",
4
- "version": "3.8.0",
4
+ "version": "3.9.6",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/tenphi/tasty",
7
- "ref": "v3.8.0",
7
+ "ref": "v3.9.6",
8
8
  "directory": "upstream/tasty"
9
9
  },
10
10
  {
@@ -1,18 +1,18 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/tenphi/tasty/v3.8.0/assets/tasty.svg" width="128" height="128" alt="Tasty logo">
2
+ <img src="https://raw.githubusercontent.com/tenphi/tasty/v3.9.6/assets/tasty.svg" width="128" height="128" alt="Tasty logo" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Tasty</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>CSS-in-JS for React design systems.</strong><br>
8
+ <strong>CSS-in-JS for React design systems.</strong><br />
9
9
  Build components whose styles don’t fight.
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <a href="https://www.npmjs.com/package/@tenphi/tasty"><img src="https://img.shields.io/npm/v/@tenphi/tasty.svg" alt="npm version"></a>
14
- <a href="https://github.com/tenphi/tasty/actions/workflows/ci.yml"><img src="https://github.com/tenphi/tasty/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
15
- <a href="https://github.com/tenphi/tasty/blob/main/LICENSE"><img src="https://img.shields.io/github/license/tenphi/tasty" alt="license"></a>
13
+ <a href="https://www.npmjs.com/package/@tenphi/tasty"><img src="https://img.shields.io/npm/v/@tenphi/tasty.svg" alt="npm version" /></a>
14
+ <a href="https://github.com/tenphi/tasty/actions/workflows/ci.yml"><img src="https://github.com/tenphi/tasty/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" /></a>
15
+ <a href="https://github.com/tenphi/tasty/blob/main/LICENSE"><img src="https://img.shields.io/github/license/tenphi/tasty" alt="license" /></a>
16
16
  </p>
17
17
 
18
18
  ***
@@ -615,7 +615,7 @@ const tokens = theme.tasty(); // Ready-to-use Tasty tokens
615
615
  Syntax highlighting for Tasty styles in TypeScript, TSX, JavaScript, and JSX. Highlights color tokens, custom units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, and related APIs.
616
616
 
617
617
  <p align="center">
618
- <img src="https://raw.githubusercontent.com/tenphi/tasty/v3.8.0/assets/tasty-vscode-highlight.png" width="512" alt="Tasty VS Code syntax highlighting example">
618
+ <img src="https://raw.githubusercontent.com/tenphi/tasty/v3.9.6/assets/tasty-vscode-highlight.png" width="512" alt="Tasty VS Code syntax highlighting example" />
619
619
  </p>
620
620
 
621
621
  ## Built with Tasty
@@ -22,6 +22,12 @@ tastyStatic('.card', { … }); // b
22
22
  - Wrap, don't pass styles per instance: ❌ `<Box styles={{ padding: '2x' }} />` → ✅ `const Box = tasty({ styles: { padding: '2x' } })`.
23
23
  - Style values are **static**. Route anything conditional through a state map plus `mods` (or `tokens` / `styleProps`):
24
24
  ❌ `fill: isOpen ? '#primary' : '#surface'` → ✅ `fill: { '': '#surface', open: '#primary' }` with `mods={{ open: isOpen }}`.
25
+ - Keep JavaScript identifiers, calls, computed keys, and interpolated templates
26
+ out of style values. Use token references inside strings for values supplied
27
+ at runtime. Spell out style properties rather than spreading objects; if a
28
+ runtime spread is unavoidable, add a
29
+ `// eslint-disable-next-line tasty/no-style-spread -- reason` comment directly
30
+ above it. `tastyStatic()` cannot use spreads.
25
31
 
26
32
  ## 2. Property names
27
33
 
@@ -128,7 +134,19 @@ Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `row
128
134
 
129
135
  Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset` `flow` `dimension`.
130
136
 
131
- ❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill 0.2s'`
137
+ ❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill'`
138
+
139
+ Omit the duration to use `$fill-transition` with `$transition` as fallback, or
140
+ use a duration token explicitly (`transition: 'fill $transition'`). Avoid raw
141
+ time values in duration slots. An explicit zero can be useful to disable motion.
142
+
143
+ Use `transition` when defining a complete transition, and the native CSS
144
+ `animation` shorthand when defining a complete animation. Tasty accepts
145
+ `animation` directly and tracks local keyframes referenced by it; no special
146
+ animation handler is needed. Use duration tokens in either form. Keep
147
+ `transitionDuration`, `animationDuration`, and other motion longhands when a
148
+ state or extension changes only one part of an existing effect. A shorthand
149
+ resets omitted parts, including an animation's timeline and range.
132
150
 
133
151
  ## 4. State maps
134
152
 
@@ -155,7 +173,8 @@ Rules:
155
173
  3. **`_` is standalone-only** and always first (with `''` right after it, if present). `_` is a never-negated fallback floor for cases where a higher-priority branch may be _unknown_ (`@supports`, container queries). If a map contains only `_` and `''`, drop the `''`.
156
174
  4. **No nested maps:** ❌ `{ hovered: { pressed: 'x' } }` → ✅ `{ 'hovered & pressed': 'x' }`
157
175
  5. **State keys never sit at the top level** of a styles object — `:hover`, `.active`, `[open]` belong inside a property value.
158
- 6. **Prefer automatic native states:** use `disabled` and `checked` in Tasty component styles instead of `[disabled]` and `[checked]`. The concise states track the corresponding native prop or attribute automatically.
176
+ 6. **Write keys Tasty can fully read.** Names start with a letter (a single letter is fine: `x` → `[data-x]`); join states with an operator (`hovered & focused`, not `hovered focused`); no space between `@media` and `(`. Tasty warns (`INVALID_STATE_KEY`) and skips what it cannot read.
177
+ 7. **Prefer automatic native states:** use `disabled` and `checked` in Tasty component styles instead of `[disabled]` and `[checked]`. The concise states track the corresponding native prop or attribute automatically.
159
178
 
160
179
  ```jsx
161
180
  color: { '': '#text', hovered: '#accent', disabled: '#text.40' }
@@ -6,9 +6,7 @@ Runtime CSS inspection and diagnostics for the Tasty styling system. Inspect inj
6
6
 
7
7
  ## Overview
8
8
 
9
- `tastyDebug` is a diagnostic object that exposes Tasty's runtime CSS state. It is designed for development use but can be manually installed in production for debugging.
10
-
11
- In development mode (`isDevEnv()` returns `true`), `tastyDebug` is automatically installed on `window.tastyDebug`. In production, install it manually when needed.
9
+ `tastyDebug` is a diagnostic object that exposes Tasty's runtime CSS state. It is designed for development use and can be installed on `window` explicitly when needed.
12
10
 
13
11
  All methods **log to the console by default**. Pass `{ raw: true }` to suppress logging and only return data.
14
12
 
@@ -19,7 +17,6 @@ All methods **log to the console by default**. Pass `{ raw: true }` to suppress
19
17
  ## Quick Start
20
18
 
21
19
  ```typescript
22
- // Auto-installed in dev mode. Otherwise:
23
20
  import { tastyDebug } from '@tenphi/tasty';
24
21
  tastyDebug.install();
25
22
 
@@ -277,7 +274,7 @@ tastyDebug.help();
277
274
 
278
275
  ### `install(): void`
279
276
 
280
- Attaches `tastyDebug` to `window.tastyDebug`. Called automatically in development mode.
277
+ Attaches `tastyDebug` to `window.tastyDebug`.
281
278
 
282
279
  ```typescript
283
280
  import { tastyDebug } from '@tenphi/tasty';