@handamade/psi-mcp 0.25.0 → 0.27.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 +1 -1
- package/dist/psi-index.json +550 -16
- package/llms.txt +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @handamade/psi-mcp
|
|
2
2
|
|
|
3
|
-
MCP server and agent-context generator for the Psi (Ψ) design system. Documents the system — the
|
|
3
|
+
MCP server and agent-context generator for the Psi (Ψ) design system. Documents the system — the 52 React 19 components, 24 composition patterns (D47) that recipe them together with gap-annotated compose trees, every semantic token with its OKLCH derivation formula resolved across all four themes, and the usage guidance — through two read-only tools. It does not write code.
|
|
4
4
|
|
|
5
5
|
## Connect
|
|
6
6
|
|
package/dist/psi-index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.27.0",
|
|
3
3
|
"themes": [
|
|
4
4
|
"light",
|
|
5
5
|
"dark",
|
|
@@ -242,6 +242,120 @@
|
|
|
242
242
|
],
|
|
243
243
|
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Panel\n\nElevated surface panel (D51): secondary background, faint hairline, radius-12 — the shared --psi-surface-* recipe Dialog's panel also binds. Not a Card: no media slot, no hover lift, opaque by design.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Panel contents. |\n| `padding` | `16 \\| 24` | 24 | no | Inner padding in px. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Skipped — Panel itself is not focusable; focus moves through its children. |\n\nPlain <div> container with no implicit role. Pass aria-* host props if the panel should announce as a region.\n\n## Theming\n\nOverride `--psi-panel-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
244
244
|
},
|
|
245
|
+
{
|
|
246
|
+
"name": "Stack",
|
|
247
|
+
"description": "A column with a gap from the space scale (D109): Toolbar's vertical counterpart, minus the wrap. A plain <div> with no role. It gives a pattern tree a layout context, which Panel and the psi-gap-* utilities do not.",
|
|
248
|
+
"props": [
|
|
249
|
+
{
|
|
250
|
+
"name": "children",
|
|
251
|
+
"type": "ReactNode",
|
|
252
|
+
"required": false,
|
|
253
|
+
"default": null,
|
|
254
|
+
"description": "Stack contents, laid out top to bottom."
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"name": "gap",
|
|
258
|
+
"type": "4 | 8 | 12 | 16 | 24 | 32",
|
|
259
|
+
"required": false,
|
|
260
|
+
"default": 16,
|
|
261
|
+
"description": "Space between children in px, from the space scale."
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "align",
|
|
265
|
+
"type": "\"stretch\" | \"start\" | \"center\" | \"end\"",
|
|
266
|
+
"required": false,
|
|
267
|
+
"default": "stretch",
|
|
268
|
+
"description": "Cross-axis alignment. `stretch` (the default) makes each child full\nwidth; `start` keeps a Button or a short line at its own width."
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"name": "ref",
|
|
272
|
+
"type": "Ref<HTMLDivElement>",
|
|
273
|
+
"required": false,
|
|
274
|
+
"default": null,
|
|
275
|
+
"description": "Forwarded ref to the root element."
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"name": "className",
|
|
279
|
+
"type": "string",
|
|
280
|
+
"required": false,
|
|
281
|
+
"default": null,
|
|
282
|
+
"description": "Additional CSS class name(s) merged onto the component's root element."
|
|
283
|
+
}
|
|
284
|
+
],
|
|
285
|
+
"slots": [
|
|
286
|
+
{
|
|
287
|
+
"name": "body",
|
|
288
|
+
"accepts": {},
|
|
289
|
+
"cardinality": "0..*",
|
|
290
|
+
"order": 1
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Stack\n\nA column with a gap from the space scale (D109): Toolbar's vertical counterpart, minus the wrap. A plain <div> with no role. It gives a pattern tree a layout context, which Panel and the psi-gap-* utilities do not.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Stack contents, laid out top to bottom. |\n| `gap` | `4 \\| 8 \\| 12 \\| 16 \\| 24 \\| 32` | 16 | no | Space between children in px, from the space scale. |\n| `align` | `\"stretch\" \\| \"start\" \\| \"center\" \\| \"end\"` | stretch | no | Cross-axis alignment. `stretch` (the default) makes each child full width; `start` keeps a Button or a short line at its own width. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Skipped — Stack itself is not focusable; focus moves through its children in source order. |\n\nPlain <div> with no role (D109): a column with a gap. Visual order is source order, so reading and focus order match.\n\n## Theming\n\nThis component has no `--psi-stack-*` tokens — its styling binds scale tokens only, so there is nothing component-scoped to override. Theme changes reach it through the semantic tokens of its children and surroundings.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"name": "Text",
|
|
297
|
+
"description": "A line or paragraph of text at one of Psi's text styles (D109). It gives a pattern tree an element per line, so a headline and its explanation do not run together. Margin 0: a Stack's gap does the spacing.",
|
|
298
|
+
"props": [
|
|
299
|
+
{
|
|
300
|
+
"name": "children",
|
|
301
|
+
"type": "ReactNode",
|
|
302
|
+
"required": false,
|
|
303
|
+
"default": null,
|
|
304
|
+
"description": "The text."
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "as",
|
|
308
|
+
"type": "\"p\" | \"span\" | \"div\" | \"h2\" | \"h3\" | \"h4\"",
|
|
309
|
+
"required": false,
|
|
310
|
+
"default": "p",
|
|
311
|
+
"description": "The element. A heading's level follows the document outline, its size\nthe design, so `as` and `size` are separate."
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"name": "size",
|
|
315
|
+
"type": "12 | 14 | 16 | 18",
|
|
316
|
+
"required": false,
|
|
317
|
+
"default": 14,
|
|
318
|
+
"description": "Font size in px; each size carries its own line height."
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"name": "weight",
|
|
322
|
+
"type": "\"regular\" | \"medium\"",
|
|
323
|
+
"required": false,
|
|
324
|
+
"default": "regular",
|
|
325
|
+
"description": ""
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"name": "tone",
|
|
329
|
+
"type": "\"primary\" | \"secondary\"",
|
|
330
|
+
"required": false,
|
|
331
|
+
"default": "primary",
|
|
332
|
+
"description": "`secondary` for supporting text."
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"name": "ref",
|
|
336
|
+
"type": "Ref<HTMLElement>",
|
|
337
|
+
"required": false,
|
|
338
|
+
"default": null,
|
|
339
|
+
"description": "Forwarded ref to the element."
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"name": "className",
|
|
343
|
+
"type": "string",
|
|
344
|
+
"required": false,
|
|
345
|
+
"default": null,
|
|
346
|
+
"description": "Additional CSS class name(s) merged onto the component's root element."
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
"slots": [
|
|
350
|
+
{
|
|
351
|
+
"name": "body",
|
|
352
|
+
"accepts": {},
|
|
353
|
+
"cardinality": "0..*",
|
|
354
|
+
"order": 1
|
|
355
|
+
}
|
|
356
|
+
],
|
|
357
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Text\n\nA line or paragraph of text at one of Psi's text styles (D109). It gives a pattern tree an element per line, so a headline and its explanation do not run together. Margin 0: a Stack's gap does the spacing.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | The text. |\n| `as` | `\"p\" \\| \"span\" \\| \"div\" \\| \"h2\" \\| \"h3\" \\| \"h4\"` | p | no | The element. A heading's level follows the document outline, its size the design, so `as` and `size` are separate. |\n| `size` | `12 \\| 14 \\| 16 \\| 18` | 14 | no | Font size in px; each size carries its own line height. |\n| `weight` | `\"regular\" \\| \"medium\"` | regular | no | |\n| `tone` | `\"primary\" \\| \"secondary\"` | primary | no | `secondary` for supporting text. |\n| `ref` | `Ref<HTMLElement>` | — | no | Forwarded ref to the element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Not focusable. |\n\nRenders the element `as` names (p by default). Use h2–h4 for a heading at its outline level, whatever its `size`.\n\n## Theming\n\nOverride `--psi-text-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
358
|
+
},
|
|
245
359
|
{
|
|
246
360
|
"name": "DescriptionList",
|
|
247
361
|
"description": "Key/value display (D70): a `<dl>` of term/value pairs, the body `detail-drawer` has always described and never had a component for. No `size` prop — this is type, and the type scale is already expressed by --psi-text-*. Consumers restyle via the token family, not a prop.",
|
|
@@ -587,7 +701,28 @@
|
|
|
587
701
|
"type": "boolean",
|
|
588
702
|
"required": false,
|
|
589
703
|
"default": true,
|
|
590
|
-
"description": "false = no close button, Esc and backdrop swallowed — footer is the only exit."
|
|
704
|
+
"description": "false = no close button, Esc and backdrop swallowed — footer is the only exit.\n`closeOnEscape` can give Escape back on its own (D101)."
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
"name": "closeOnEscape",
|
|
708
|
+
"type": "boolean",
|
|
709
|
+
"required": false,
|
|
710
|
+
"default": "dismissible",
|
|
711
|
+
"description": "Whether Escape reports onClose(\"esc\"), separately from the close button\nand the backdrop (D101). A consequential confirmation is\n`dismissible={false} closeOnEscape`: no close button, no backdrop\ndismissal, and Escape cancels."
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
"name": "initialFocus",
|
|
715
|
+
"type": "RefObject<HTMLElement | null>",
|
|
716
|
+
"required": false,
|
|
717
|
+
"default": null,
|
|
718
|
+
"description": "The element to focus each time the dialog opens, after the platform's own\nfocusing (D101), e.g. a ref to the footer's Cancel button. Without it, the\nplatform focuses the first focusable control, as before."
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
"name": "closeLabel",
|
|
722
|
+
"type": "string",
|
|
723
|
+
"required": false,
|
|
724
|
+
"default": null,
|
|
725
|
+
"description": "Visible text for the close button (D101). When set, the close button is a\nghost Button with this text instead of the icon-only \"Close dialog\"\nbutton."
|
|
591
726
|
},
|
|
592
727
|
{
|
|
593
728
|
"name": "ref",
|
|
@@ -635,11 +770,11 @@
|
|
|
635
770
|
"order": 3
|
|
636
771
|
}
|
|
637
772
|
],
|
|
638
|
-
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Dialog\n\nModal dialog on the native <dialog> top layer: focus trap, aria-modal and focus restore come from the platform; title/footer slots, Esc/backdrop/ close-button dismissal via onClose(reason) (D50). `placement` moves the panel and nothing else (D66): an `inline-start` / `inline-end` Dialog is a drawer, pinned full-height to that edge, with the same modality, focus trap, focus restore and dismissal reasons as a centered one. There is deliberately no separate Drawer component — a sibling would have to duplicate or wrap all of that.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Body content, between the title and footer slots. |\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showModal()/close(). |\n| `onClose` | `(reason: \"esc\" \\| \"backdrop\" \\| \"close-button\") => void` | — | yes | Called on every dismissal attempt with its source; the consumer flips `open`. |\n| `title` | `ReactNode` | — | no | Heading; renders an <h2> wired to aria-labelledby. Without it, pass aria-label. |\n| `footer` | `ReactNode` | — | no | Action row (Buttons — one accent per group, danger for destructive). |\n| `width` | `400 \\| 560 \\| 720` | 560 | no | Panel width in px (400 \\| 560 \\| 720). For an `inline-*` placement this is the drawer's width; the height is always the full viewport. |\n| `placement` | `\"center\" \\| \"inline-start\" \\| \"inline-end\"` | center | no | Where the panel sits. `inline-start` / `inline-end` pin it full-height to that edge, making it a drawer (D66). |\n| `dismissible` | `boolean` | true | no | false = no close button, Esc and backdrop swallowed — footer is the only exit. |\n| `ref` | `Ref<HTMLDialogElement>` | — | no | Forwarded ref to the underlying <dialog> element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Esc | Dismisses via onClose('esc') when dismissible; swallowed otherwise. |\n| Tab | Focus is trapped inside by the native <dialog> top layer; restored on close. |\n\nRendered with showModal(): aria-modal, inert background and focus restore come from the platform. title wires aria-labelledby; without title, pass aria-label. Backdrop click dismisses only when dismissible. placement=\"inline-start\"/\"inline-end\" pins the panel full-height to that edge — that is Psi's drawer, and it changes nothing about modality, the focus trap, focus restore or the dismissal reasons (D66). A drawer's panel scrolls internally so a dismissible={false} footer stays reachable.\n\n## Theming\n\nOverride `--psi-dialog-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
773
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Dialog\n\nModal dialog on the native <dialog> top layer: focus trap, aria-modal and focus restore come from the platform; title/footer slots, Esc/backdrop/ close-button dismissal via onClose(reason) (D50). `placement` moves the panel and nothing else (D66): an `inline-start` / `inline-end` Dialog is a drawer, pinned full-height to that edge, with the same modality, focus trap, focus restore and dismissal reasons as a centered one. There is deliberately no separate Drawer component — a sibling would have to duplicate or wrap all of that.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Body content, between the title and footer slots. |\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showModal()/close(). |\n| `onClose` | `(reason: \"esc\" \\| \"backdrop\" \\| \"close-button\") => void` | — | yes | Called on every dismissal attempt with its source; the consumer flips `open`. |\n| `title` | `ReactNode` | — | no | Heading; renders an <h2> wired to aria-labelledby. Without it, pass aria-label. |\n| `footer` | `ReactNode` | — | no | Action row (Buttons — one accent per group, danger for destructive). |\n| `width` | `400 \\| 560 \\| 720` | 560 | no | Panel width in px (400 \\| 560 \\| 720). For an `inline-*` placement this is the drawer's width; the height is always the full viewport. |\n| `placement` | `\"center\" \\| \"inline-start\" \\| \"inline-end\"` | center | no | Where the panel sits. `inline-start` / `inline-end` pin it full-height to that edge, making it a drawer (D66). |\n| `dismissible` | `boolean` | true | no | false = no close button, Esc and backdrop swallowed — footer is the only exit. `closeOnEscape` can give Escape back on its own (D101). |\n| `closeOnEscape` | `boolean` | dismissible | no | Whether Escape reports onClose(\"esc\"), separately from the close button and the backdrop (D101). A consequential confirmation is `dismissible={false} closeOnEscape`: no close button, no backdrop dismissal, and Escape cancels. |\n| `initialFocus` | `RefObject<HTMLElement \\| null>` | — | no | The element to focus each time the dialog opens, after the platform's own focusing (D101), e.g. a ref to the footer's Cancel button. Without it, the platform focuses the first focusable control, as before. |\n| `closeLabel` | `string` | — | no | Visible text for the close button (D101). When set, the close button is a ghost Button with this text instead of the icon-only \"Close dialog\" button. |\n| `ref` | `Ref<HTMLDialogElement>` | — | no | Forwarded ref to the underlying <dialog> element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Esc | Dismisses via onClose('esc') when closeOnEscape (default: dismissible); swallowed otherwise (D101). |\n| Tab | Focus is trapped inside by the native <dialog> top layer; restored on close. |\n\nRendered with showModal(): aria-modal, inert background and focus restore come from the platform. title wires aria-labelledby; without title, pass aria-label. Backdrop click dismisses only when dismissible. placement=\"inline-start\"/\"inline-end\" pins the panel full-height to that edge — that is Psi's drawer, and it changes nothing about modality, the focus trap, focus restore or the dismissal reasons (D66). A drawer's panel scrolls internally so a dismissible={false} footer stays reachable.\n\n## Theming\n\nOverride `--psi-dialog-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
639
774
|
},
|
|
640
775
|
{
|
|
641
776
|
"name": "Checkbox",
|
|
642
|
-
"description": "Native checkbox with a built-in label; for independent binary choices.",
|
|
777
|
+
"description": "Native checkbox with a built-in label; for independent binary choices. In an invalid group Field it is aria-invalid, and an unchecked box marked aria-invalid outlines in the error colour (D108).",
|
|
643
778
|
"props": [
|
|
644
779
|
{
|
|
645
780
|
"name": "children",
|
|
@@ -655,6 +790,13 @@
|
|
|
655
790
|
"default": null,
|
|
656
791
|
"description": "Accessible name when there is no visible label — a table's row-selection\ncheckbox, say. Optional, unlike IconButton's required form (D60): a\nlabelled checkbox is still the common case."
|
|
657
792
|
},
|
|
793
|
+
{
|
|
794
|
+
"name": "indeterminate",
|
|
795
|
+
"type": "boolean",
|
|
796
|
+
"required": false,
|
|
797
|
+
"default": null,
|
|
798
|
+
"description": "The mixed state, e.g. a \"select all\" over a partial selection (D103).\nSets the input's DOM property, which the platform exposes as\naria-checked=\"mixed\", and draws a dash. Re-applied on every render,\nbecause a click clears the property before the consumer re-renders.\nWhen absent, the property is left alone, so a state set through the ref\nsurvives."
|
|
799
|
+
},
|
|
658
800
|
{
|
|
659
801
|
"name": "ref",
|
|
660
802
|
"type": "Ref<HTMLInputElement>",
|
|
@@ -671,7 +813,7 @@
|
|
|
671
813
|
}
|
|
672
814
|
],
|
|
673
815
|
"slots": [],
|
|
674
|
-
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Checkbox\n\nNative checkbox with a built-in label; for independent binary choices.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `aria-label` | `string` | — | no | Accessible name when there is no visible label — a table's row-selection checkbox, say. Optional, unlike IconButton's required form (D60): a labelled checkbox is still the common case. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native <input type=checkbox> underneath (visually hidden). |\n\n\n## Theming\n\nOverride `--psi-checkbox-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
816
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Checkbox\n\nNative checkbox with a built-in label; for independent binary choices. In an invalid group Field it is aria-invalid, and an unchecked box marked aria-invalid outlines in the error colour (D108).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `aria-label` | `string` | — | no | Accessible name when there is no visible label — a table's row-selection checkbox, say. Optional, unlike IconButton's required form (D60): a labelled checkbox is still the common case. |\n| `indeterminate` | `boolean` | — | no | The mixed state, e.g. a \"select all\" over a partial selection (D103). Sets the input's DOM property, which the platform exposes as aria-checked=\"mixed\", and draws a dash. Re-applied on every render, because a click clears the property before the consumer re-renders. When absent, the property is left alone, so a state set through the ref survives. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native <input type=checkbox> underneath (visually hidden). |\n\n\n## Theming\n\nOverride `--psi-checkbox-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
675
817
|
},
|
|
676
818
|
{
|
|
677
819
|
"name": "Switch",
|
|
@@ -702,6 +844,202 @@
|
|
|
702
844
|
"slots": [],
|
|
703
845
|
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Switch\n\nOn/off toggle (`role=\"switch\"`) with a built-in label; for settings that take effect immediately.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\" role=\"switch\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native checkbox input with role=\"switch\"; announced as a switch, not a checkbox. |\n\n\n## Theming\n\nOverride `--psi-switch-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
704
846
|
},
|
|
847
|
+
{
|
|
848
|
+
"name": "Textarea",
|
|
849
|
+
"description": "Multi-line text input (D104): the Input recipe on a native `<textarea>`. Inside a Field, id/aria-describedby/aria-invalid/required are wired as for Input (D49). `minLength` and `maxLength` are the native attributes. With `maxLength`, a counter under the control shows the length; it is not a live region (a count spoken on every keystroke is noise) and joins aria-describedby, so it is read with the field on focus.",
|
|
850
|
+
"props": [
|
|
851
|
+
{
|
|
852
|
+
"name": "rows",
|
|
853
|
+
"type": "number",
|
|
854
|
+
"required": false,
|
|
855
|
+
"default": 3,
|
|
856
|
+
"description": "Visible rows. A textarea's height is its rows; there is no `size`."
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
"name": "minLength",
|
|
860
|
+
"type": "number",
|
|
861
|
+
"required": false,
|
|
862
|
+
"default": null,
|
|
863
|
+
"description": "Native minimum length, reported by constraint validation."
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
"name": "maxLength",
|
|
867
|
+
"type": "number",
|
|
868
|
+
"required": false,
|
|
869
|
+
"default": null,
|
|
870
|
+
"description": "Native maximum length, enforced while typing. With it, the counter shows\n`{length} / {maxLength}` unless `showCounter={false}`."
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
"name": "error",
|
|
874
|
+
"type": "boolean",
|
|
875
|
+
"required": false,
|
|
876
|
+
"default": false,
|
|
877
|
+
"description": "Show error styling. Inside a Field, the Field's error also lights this."
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"name": "showCounter",
|
|
881
|
+
"type": "boolean",
|
|
882
|
+
"required": false,
|
|
883
|
+
"default": true,
|
|
884
|
+
"description": "With `maxLength`, show the `{length} / {maxLength}` counter under the\ncontrol. It is not a live region; it describes the textarea."
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
"name": "ref",
|
|
888
|
+
"type": "Ref<HTMLTextAreaElement>",
|
|
889
|
+
"required": false,
|
|
890
|
+
"default": null,
|
|
891
|
+
"description": "Forwarded ref to the underlying `<textarea>` element."
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
"name": "placeholder",
|
|
895
|
+
"type": "string",
|
|
896
|
+
"required": false,
|
|
897
|
+
"default": null,
|
|
898
|
+
"description": "Native placeholder text shown while the field is empty (only on components whose host element supports it)."
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
"name": "className",
|
|
902
|
+
"type": "string",
|
|
903
|
+
"required": false,
|
|
904
|
+
"default": null,
|
|
905
|
+
"description": "Additional CSS class name(s) merged onto the component's root element."
|
|
906
|
+
}
|
|
907
|
+
],
|
|
908
|
+
"slots": [],
|
|
909
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Textarea\n\nMulti-line text input (D104): the Input recipe on a native `<textarea>`. Inside a Field, id/aria-describedby/aria-invalid/required are wired as for Input (D49). `minLength` and `maxLength` are the native attributes. With `maxLength`, a counter under the control shows the length; it is not a live region (a count spoken on every keystroke is noise) and joins aria-describedby, so it is read with the field on focus.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `rows` | `number` | 3 | no | Visible rows. A textarea's height is its rows; there is no `size`. |\n| `minLength` | `number` | — | no | Native minimum length, reported by constraint validation. |\n| `maxLength` | `number` | — | no | Native maximum length, enforced while typing. With it, the counter shows `{length} / {maxLength}` unless `showCounter={false}`. |\n| `error` | `boolean` | false | no | Show error styling. Inside a Field, the Field's error also lights this. |\n| `showCounter` | `boolean` | true | no | With `maxLength`, show the `{length} / {maxLength}` counter under the control. It is not a live region; it describes the textarea. |\n| `ref` | `Ref<HTMLTextAreaElement>` | — | no | Forwarded ref to the underlying `<textarea>` element. |\n| `placeholder` | `string` | — | no | Native placeholder text shown while the field is empty (only on components whose host element supports it). |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | A native <textarea>; Enter adds a line, it does not submit the form. |\n\nInside a Field, id/aria-describedby/aria-invalid/required are wired as for Input (D104). minLength and maxLength are native. With maxLength, a counter under the control shows {length} / {maxLength}; it is not a live region, and it joins aria-describedby so it is read with the field on focus, with a visually hidden \"characters\".\n\n## Theming\n\nOverride `--psi-textarea-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"name": "RadioGroup",
|
|
913
|
+
"description": "A set of native radios sharing one name (D104). The browser supplies the keyboard: Tab enters on the checked radio, and the arrows move and select. Inside `Field group` the fieldset and legend form and name the group, so this adds no role; standalone it is `role=\"radiogroup\"` and needs `aria-label` or `aria-labelledby`. A required or invalid Field makes every radio `required` and `aria-invalid`.",
|
|
914
|
+
"props": [
|
|
915
|
+
{
|
|
916
|
+
"name": "children",
|
|
917
|
+
"type": "ReactNode",
|
|
918
|
+
"required": false,
|
|
919
|
+
"default": null,
|
|
920
|
+
"description": "`Radio` children."
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"name": "name",
|
|
924
|
+
"type": "string",
|
|
925
|
+
"required": false,
|
|
926
|
+
"default": null,
|
|
927
|
+
"description": "The radios' shared `name`. Generated when absent."
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
"name": "value",
|
|
931
|
+
"type": "string",
|
|
932
|
+
"required": false,
|
|
933
|
+
"default": null,
|
|
934
|
+
"description": "The selected value (controlled)."
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
"name": "defaultValue",
|
|
938
|
+
"type": "string",
|
|
939
|
+
"required": false,
|
|
940
|
+
"default": null,
|
|
941
|
+
"description": "The initially selected value (uncontrolled)."
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"name": "onValueChange",
|
|
945
|
+
"type": "((value: string) => void)",
|
|
946
|
+
"required": false,
|
|
947
|
+
"default": null,
|
|
948
|
+
"description": "Called with the value of the radio the user selects."
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
"name": "disabled",
|
|
952
|
+
"type": "boolean",
|
|
953
|
+
"required": false,
|
|
954
|
+
"default": false,
|
|
955
|
+
"description": "Disables every radio in the group."
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
"name": "orientation",
|
|
959
|
+
"type": "\"vertical\" | \"horizontal\"",
|
|
960
|
+
"required": false,
|
|
961
|
+
"default": "vertical",
|
|
962
|
+
"description": "Stack the radios, or lay them in a row."
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
"name": "aria-label",
|
|
966
|
+
"type": "string",
|
|
967
|
+
"required": false,
|
|
968
|
+
"default": null,
|
|
969
|
+
"description": "Accessible name when the group stands alone. Inside `Field group` the\nlegend names the group instead."
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"name": "ref",
|
|
973
|
+
"type": "Ref<HTMLDivElement>",
|
|
974
|
+
"required": false,
|
|
975
|
+
"default": null,
|
|
976
|
+
"description": "Forwarded ref to the root element."
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
"name": "className",
|
|
980
|
+
"type": "string",
|
|
981
|
+
"required": false,
|
|
982
|
+
"default": null,
|
|
983
|
+
"description": "Additional CSS class name(s) merged onto the component's root element."
|
|
984
|
+
}
|
|
985
|
+
],
|
|
986
|
+
"slots": [
|
|
987
|
+
{
|
|
988
|
+
"name": "body",
|
|
989
|
+
"accepts": {
|
|
990
|
+
"components": [
|
|
991
|
+
"Radio"
|
|
992
|
+
]
|
|
993
|
+
},
|
|
994
|
+
"cardinality": "1..*",
|
|
995
|
+
"order": 1
|
|
996
|
+
}
|
|
997
|
+
],
|
|
998
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# RadioGroup\n\nA set of native radios sharing one name (D104). The browser supplies the keyboard: Tab enters on the checked radio, and the arrows move and select. Inside `Field group` the fieldset and legend form and name the group, so this adds no role; standalone it is `role=\"radiogroup\"` and needs `aria-label` or `aria-labelledby`. A required or invalid Field makes every radio `required` and `aria-invalid`.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | `Radio` children. |\n| `name` | `string` | — | no | The radios' shared `name`. Generated when absent. |\n| `value` | `string` | — | no | The selected value (controlled). |\n| `defaultValue` | `string` | — | no | The initially selected value (uncontrolled). |\n| `onValueChange` | `((value: string) => void)` | — | no | Called with the value of the radio the user selects. |\n| `disabled` | `boolean` | false | no | Disables every radio in the group. |\n| `orientation` | `\"vertical\" \\| \"horizontal\"` | vertical | no | Stack the radios, or lay them in a row. |\n| `aria-label` | `string` | — | no | Accessible name when the group stands alone. Inside `Field group` the legend names the group instead. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Enters the group on the checked radio, or the first one; one tab stop for the group. |\n| Arrow keys | Move to the next or previous radio and select it (native radios sharing one name). |\n\nNative radios sharing one name (D104). Inside Field group the fieldset and legend form and name the group, so RadioGroup adds no role; standalone it is role=\"radiogroup\" and needs aria-label or aria-labelledby. A required or invalid Field makes every radio required and aria-invalid.\n\n## Theming\n\nOverride `--psi-radio-group-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
"name": "Radio",
|
|
1002
|
+
"description": "One choice in a RadioGroup (D104): a native radio with a built-in label, drawn like Checkbox on a circle. Takes its name, checked state, required and aria-invalid from the group.",
|
|
1003
|
+
"props": [
|
|
1004
|
+
{
|
|
1005
|
+
"name": "value",
|
|
1006
|
+
"type": "string",
|
|
1007
|
+
"required": true,
|
|
1008
|
+
"default": null,
|
|
1009
|
+
"description": "The value this radio selects."
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
"name": "children",
|
|
1013
|
+
"type": "ReactNode",
|
|
1014
|
+
"required": false,
|
|
1015
|
+
"default": null,
|
|
1016
|
+
"description": "Label text."
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
"name": "disabled",
|
|
1020
|
+
"type": "boolean",
|
|
1021
|
+
"required": false,
|
|
1022
|
+
"default": null,
|
|
1023
|
+
"description": "Disables this radio; a disabled RadioGroup disables all of them."
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
"name": "className",
|
|
1027
|
+
"type": "string",
|
|
1028
|
+
"required": false,
|
|
1029
|
+
"default": null,
|
|
1030
|
+
"description": "Additional CSS class name(s) merged onto the component's root element."
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
"name": "ref",
|
|
1034
|
+
"type": "Ref<HTMLInputElement>",
|
|
1035
|
+
"required": false,
|
|
1036
|
+
"default": null,
|
|
1037
|
+
"description": "Forwarded ref to the underlying `<input type=\"radio\">` element."
|
|
1038
|
+
}
|
|
1039
|
+
],
|
|
1040
|
+
"slots": [],
|
|
1041
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Radio\n\nOne choice in a RadioGroup (D104): a native radio with a built-in label, drawn like Checkbox on a circle. Takes its name, checked state, required and aria-invalid from the group.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `value` | `string` | — | yes | The value this radio selects. |\n| `children` | `ReactNode` | — | no | Label text. |\n| `disabled` | `boolean` | — | no | Disables this radio; a disabled RadioGroup disables all of them. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"radio\">` element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Selects the focused radio when none is selected. |\n\nNative <input type=radio> underneath (visually hidden), drawn like Checkbox on a circle. Takes its name, checked state, required and aria-invalid from its RadioGroup.\n\n## Theming\n\nThis component has no `--psi-radio-*` tokens — its styling binds scale tokens only, so there is nothing component-scoped to override. Theme changes reach it through the semantic tokens of its children and surroundings.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
1042
|
+
},
|
|
705
1043
|
{
|
|
706
1044
|
"name": "Tag",
|
|
707
1045
|
"description": "Compact label for status or category; semantic variants, optional subtle tint and dismiss button.",
|
|
@@ -825,7 +1163,7 @@
|
|
|
825
1163
|
"type": "ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>>",
|
|
826
1164
|
"required": true,
|
|
827
1165
|
"default": null,
|
|
828
|
-
"description": "The trigger element (must accept
|
|
1166
|
+
"description": "The trigger element (must accept onFocus/Blur)."
|
|
829
1167
|
},
|
|
830
1168
|
{
|
|
831
1169
|
"name": "placement",
|
|
@@ -836,7 +1174,7 @@
|
|
|
836
1174
|
}
|
|
837
1175
|
],
|
|
838
1176
|
"slots": [],
|
|
839
|
-
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tooltip\n\nShort hover/focus hint attached to a single trigger element.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `content` | `ReactNode` | — | yes | Tooltip content. |\n| `children` | `ReactElement<Record<string, unknown>, string \\| JSXElementConstructor<any>>` | — | yes | The trigger element (must accept
|
|
1177
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tooltip\n\nShort hover/focus hint attached to a single trigger element.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `content` | `ReactNode` | — | yes | Tooltip content. |\n| `children` | `ReactElement<Record<string, unknown>, string \\| JSXElementConstructor<any>>` | — | yes | The trigger element (must accept onFocus/Blur). |\n| `placement` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | top | no | Preferred placement. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab (focus trigger) | Shows immediately on focus (no delay). |\n| Escape | Dismisses while visible (WCAG 1.4.13). |\n\nHover opens after a short delay; content is linked via aria-describedby while visible. Trigger must accept onMouseEnter/Leave and onFocus/Blur props (cloned in automatically) — no ref forwarding required.\n\n## Theming\n\nOverride `--psi-tooltip-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
840
1178
|
},
|
|
841
1179
|
{
|
|
842
1180
|
"name": "NavBar",
|
|
@@ -1248,10 +1586,10 @@
|
|
|
1248
1586
|
},
|
|
1249
1587
|
{
|
|
1250
1588
|
"name": "onClose",
|
|
1251
|
-
"type": "(reason:
|
|
1589
|
+
"type": "(reason: MenuCloseReason) => void",
|
|
1252
1590
|
"required": true,
|
|
1253
1591
|
"default": null,
|
|
1254
|
-
"description": "Called when a dismissal is *requested*; the consumer flips `open`.\n\nMenu never closes itself on `\"esc\"` or `\"item-select\"` — the popover stays\nopen until `open` becomes false (D50). `\"outside\"` is the one exception:\nlight dismiss is performed by the browser before it tells us, so by the\ntime that reason is reported the popover is already closed. Flipping\n`open` to false is still required, to keep React's state in step.\n\nInvariant (D58): a dismissal is only ever reported for a menu that is\ncurrently open according to its own `open` prop. The platform can close\nan auto popover before the consumer's state catches up — clicking another\nmenu's trigger light-dismisses this one before the consumer's click\nhandler runs — and a report for an already-closed menu would clear a\nselection that has since moved on."
|
|
1592
|
+
"description": "Called when a dismissal is *requested*; the consumer flips `open`.\n\nMenu never closes itself on `\"esc\"` or `\"item-select\"` — the popover stays\nopen until `open` becomes false (D50). `\"outside\"` is the one exception:\nlight dismiss is performed by the browser before it tells us, so by the\ntime that reason is reported the popover is already closed. Flipping\n`open` to false is still required, to keep React's state in step.\n\nInvariant (D58): a dismissal is only ever reported for a menu that is\ncurrently open according to its own `open` prop. The platform can close\nan auto popover before the consumer's state catches up — clicking another\nmenu's trigger light-dismisses this one before the consumer's click\nhandler runs — and a report for an already-closed menu would clear a\nselection that has since moved on.\n\n`\"tab\"` (D102): Tab or Shift Tab inside the open menu. Focus moves on to\nthe next control as the browser moves it; the consumer flips `open`."
|
|
1255
1593
|
},
|
|
1256
1594
|
{
|
|
1257
1595
|
"name": "trigger",
|
|
@@ -1267,6 +1605,13 @@
|
|
|
1267
1605
|
"default": "bottom-start",
|
|
1268
1606
|
"description": "Placement relative to the trigger."
|
|
1269
1607
|
},
|
|
1608
|
+
{
|
|
1609
|
+
"name": "id",
|
|
1610
|
+
"type": "string",
|
|
1611
|
+
"required": false,
|
|
1612
|
+
"default": null,
|
|
1613
|
+
"description": "The menu element's id, which the trigger's `aria-controls` names (D102).\nGenerated when absent."
|
|
1614
|
+
},
|
|
1270
1615
|
{
|
|
1271
1616
|
"name": "aria-label",
|
|
1272
1617
|
"type": "string",
|
|
@@ -1327,7 +1672,7 @@
|
|
|
1327
1672
|
"order": 2
|
|
1328
1673
|
}
|
|
1329
1674
|
],
|
|
1330
|
-
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Menu\n\nAction menu on the native Popover API top layer: the top layer and light dismiss come from the platform; Esc, roving keyboard, placement and dismissal reasons are Psi's (D53). Controlled-only, like Dialog (D50). Controlled-only means Menu does not change its own visibility. Esc and item-select *report* a dismissal via onClose(reason) and leave the popover open; only the consumer flipping `open` to false actually closes it. Esc calls preventDefault() to suppress the platform's own popover dismissal, which is what makes that possible. Light dismiss (outside click) is the one asymmetry, and it is forced by the platform: the browser hides the popover itself and the hide-side `beforetoggle` is not cancelable, so the popover is already closed by the time the resulting `toggle` lets us report onClose(\"outside\"). The consumer must still flip `open` to false so React's state matches the DOM. `toggle` therefore reports only that one reason. A close driven by the consumer (`open` true -> false) runs through the sync effect's own hidePopover(), which raises a `toggle` too — that one is suppressed, because a programmatic close is not a dismissal and must not call onClose.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showPopover()/hidePopover(). |\n| `onClose` | `(reason:
|
|
1675
|
+
"doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Menu\n\nAction menu on the native Popover API top layer: the top layer and light dismiss come from the platform; Esc, roving keyboard, placement and dismissal reasons are Psi's (D53). Controlled-only, like Dialog (D50). Controlled-only means Menu does not change its own visibility. Esc and item-select *report* a dismissal via onClose(reason) and leave the popover open; only the consumer flipping `open` to false actually closes it. Esc calls preventDefault() to suppress the platform's own popover dismissal, which is what makes that possible. Light dismiss (outside click) is the one asymmetry, and it is forced by the platform: the browser hides the popover itself and the hide-side `beforetoggle` is not cancelable, so the popover is already closed by the time the resulting `toggle` lets us report onClose(\"outside\"). The consumer must still flip `open` to false so React's state matches the DOM. `toggle` therefore reports only that one reason. A close driven by the consumer (`open` true -> false) runs through the sync effect's own hidePopover(), which raises a `toggle` too — that one is suppressed, because a programmatic close is not a dismissal and must not call onClose.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showPopover()/hidePopover(). |\n| `onClose` | `(reason: MenuCloseReason) => void` | — | yes | Called when a dismissal is *requested*; the consumer flips `open`. Menu never closes itself on `\"esc\"` or `\"item-select\"` — the popover stays open until `open` becomes false (D50). `\"outside\"` is the one exception: light dismiss is performed by the browser before it tells us, so by the time that reason is reported the popover is already closed. Flipping `open` to false is still required, to keep React's state in step. Invariant (D58): a dismissal is only ever reported for a menu that is currently open according to its own `open` prop. The platform can close an auto popover before the consumer's state catches up — clicking another menu's trigger light-dismisses this one before the consumer's click handler runs — and a report for an already-closed menu would clear a selection that has since moved on. `\"tab\"` (D102): Tab or Shift Tab inside the open menu. Focus moves on to the next control as the browser moves it; the consumer flips `open`. |\n| `trigger` | `ReactElement<Record<string, unknown>, string \\| JSXElementConstructor<any>>` | — | yes | The trigger element. Must be a single React element that spreads unknown props onto a focusable node (a Psi `Button`, say): Menu clones it to add `aria-haspopup=\"menu\"` and `aria-expanded`, so assistive tech associates the menu with the control users actually focus. |\n| `placement` | `\"bottom-start\" \\| \"bottom-end\" \\| \"top-start\" \\| \"top-end\"` | bottom-start | no | Placement relative to the trigger. |\n| `id` | `string` | — | no | The menu element's id, which the trigger's `aria-controls` names (D102). Generated when absent. |\n| `aria-label` | `string` | — | no | Accessible name for the menu when there is no visible label. |\n| `aria-describedby` | `string` | — | no | The id of a `MenuText` (or any element) that describes the menu (D99). Unset by default: a description may be spoken every time the menu opens. |\n| `children` | `ReactNode` | — | yes | MenuItem, MenuSeparator and MenuText children. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the popover element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Arrow Down / Arrow Up | Moves between enabled items, wrapping at both ends. Disabled items are skipped. |\n| Home / End | Jumps to the first or last enabled item. |\n| Any single printable key | Typeahead — focuses the first enabled item whose label starts with the typed prefix; the prefix resets after 500ms. Keystrokes with Meta/Ctrl/Alt held are ignored. |\n| Esc | Suppresses the platform's own dismissal and reports onClose(\"esc\"); the menu stays open until the consumer flips `open` (D50). |\n| Enter / Space | Activates the focused item (native button behavior). |\n| Arrow Down / Arrow Up on the closed trigger | Opens the menu on its first / last enabled item by clicking the trigger, since Menu cannot open itself; the consumer's onClick flips `open` (D102). |\n| Tab / Shift Tab | Reports onClose(\"tab\") and lets focus move on to the next control; the consumer flips `open`, and focus is not pulled back to the trigger (D102). |\n\nOpens on the native top layer via popover=\"auto\", which supplies light dismiss. Controlled-only: every dismissal path (esc, item-select, outside) only reports onClose(reason) — the consumer must flip `open` to actually close it; outside is the one path the platform has already acted on by the time it is reported. Opening moves focus to the first enabled item — Menu takes focus off the trigger as soon as `open` becomes true. Focus returns to the trigger when the menu actually closes, and only if focus is still inside the menu, so a light dismiss onto another control does not steal focus back. Requires an accessible name — pass aria-label. Placement uses CSS anchor positioning where supported and a JS fallback below that floor; below it there is no collision flip.\n\n## Theming\n\nOverride `--psi-menu-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"start\" of Fields with one-line labels, where a field's error line is the Field's own and hangs below its control (D87, D92).\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
|
|
1331
1676
|
},
|
|
1332
1677
|
{
|
|
1333
1678
|
"name": "MenuItem",
|
|
@@ -3757,6 +4102,48 @@
|
|
|
3757
4102
|
}
|
|
3758
4103
|
}
|
|
3759
4104
|
},
|
|
4105
|
+
{
|
|
4106
|
+
"name": "borderControl",
|
|
4107
|
+
"formula": "ref(fgPrimary) → alpha(0.55)",
|
|
4108
|
+
"values": {
|
|
4109
|
+
"light": {
|
|
4110
|
+
"oklch": {
|
|
4111
|
+
"l": 0.3,
|
|
4112
|
+
"c": 0.03,
|
|
4113
|
+
"h": 250,
|
|
4114
|
+
"alpha": 0.55
|
|
4115
|
+
},
|
|
4116
|
+
"hex": "#222f3c"
|
|
4117
|
+
},
|
|
4118
|
+
"dark": {
|
|
4119
|
+
"oklch": {
|
|
4120
|
+
"l": 0.93,
|
|
4121
|
+
"c": 0.01,
|
|
4122
|
+
"h": 250,
|
|
4123
|
+
"alpha": 0.4
|
|
4124
|
+
},
|
|
4125
|
+
"hex": "#e3e8ee"
|
|
4126
|
+
},
|
|
4127
|
+
"acme": {
|
|
4128
|
+
"oklch": {
|
|
4129
|
+
"l": 0.3,
|
|
4130
|
+
"c": 0.03,
|
|
4131
|
+
"h": 30,
|
|
4132
|
+
"alpha": 0.55
|
|
4133
|
+
},
|
|
4134
|
+
"hex": "#3c2825"
|
|
4135
|
+
},
|
|
4136
|
+
"ember": {
|
|
4137
|
+
"oklch": {
|
|
4138
|
+
"l": 0.949,
|
|
4139
|
+
"c": 0.011,
|
|
4140
|
+
"h": 72,
|
|
4141
|
+
"alpha": 0.4
|
|
4142
|
+
},
|
|
4143
|
+
"hex": "#f3ede6"
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
},
|
|
3760
4147
|
{
|
|
3761
4148
|
"name": "borderFocus",
|
|
3762
4149
|
"formula": "ref(fgAccent)",
|
|
@@ -4845,14 +5232,39 @@
|
|
|
4845
5232
|
},
|
|
4846
5233
|
"slots": {
|
|
4847
5234
|
"body": [
|
|
4848
|
-
"{content:headline}",
|
|
4849
|
-
"{content:explanation}",
|
|
4850
5235
|
{
|
|
4851
|
-
"component": "
|
|
5236
|
+
"component": "Stack",
|
|
4852
5237
|
"props": {
|
|
4853
|
-
"
|
|
5238
|
+
"gap": 8,
|
|
5239
|
+
"align": "start"
|
|
4854
5240
|
},
|
|
4855
|
-
"
|
|
5241
|
+
"slots": {
|
|
5242
|
+
"body": [
|
|
5243
|
+
{
|
|
5244
|
+
"component": "Text",
|
|
5245
|
+
"props": {
|
|
5246
|
+
"as": "h3",
|
|
5247
|
+
"size": 16,
|
|
5248
|
+
"weight": "medium"
|
|
5249
|
+
},
|
|
5250
|
+
"content": "headline"
|
|
5251
|
+
},
|
|
5252
|
+
{
|
|
5253
|
+
"component": "Text",
|
|
5254
|
+
"props": {
|
|
5255
|
+
"tone": "secondary"
|
|
5256
|
+
},
|
|
5257
|
+
"content": "explanation"
|
|
5258
|
+
},
|
|
5259
|
+
{
|
|
5260
|
+
"component": "Button",
|
|
5261
|
+
"props": {
|
|
5262
|
+
"variant": "neutral"
|
|
5263
|
+
},
|
|
5264
|
+
"content": "reset-label"
|
|
5265
|
+
}
|
|
5266
|
+
]
|
|
5267
|
+
}
|
|
4856
5268
|
}
|
|
4857
5269
|
]
|
|
4858
5270
|
}
|
|
@@ -4865,7 +5277,129 @@
|
|
|
4865
5277
|
},
|
|
4866
5278
|
"gaps": [],
|
|
4867
5279
|
"blocked": false,
|
|
4868
|
-
"preset": "<Panel\n padding={24}\n>\n [nothing matches these filters]\
|
|
5280
|
+
"preset": "<Panel\n padding={24}\n>\n <Stack\n align=\"start\"\n gap={8}\n >\n <Text as=\"h3\" size={16} weight=\"medium\">[nothing matches these filters]</Text>\n <Text tone=\"secondary\">[say whether the dataset is empty or the filters are too narrow]</Text>\n <Button variant=\"neutral\">Clear filters</Button>\n </Stack>\n</Panel>\n"
|
|
5281
|
+
},
|
|
5282
|
+
{
|
|
5283
|
+
"id": "feedback-form",
|
|
5284
|
+
"intent": "A short report form: a required choice of category as radios, a bounded description with a length counter, and a submit button",
|
|
5285
|
+
"match": [
|
|
5286
|
+
"feedback form",
|
|
5287
|
+
"report a problem",
|
|
5288
|
+
"radio buttons",
|
|
5289
|
+
"text area",
|
|
5290
|
+
"character counter"
|
|
5291
|
+
],
|
|
5292
|
+
"compose": {
|
|
5293
|
+
"component": "Panel",
|
|
5294
|
+
"props": {
|
|
5295
|
+
"padding": 24
|
|
5296
|
+
},
|
|
5297
|
+
"slots": {
|
|
5298
|
+
"body": [
|
|
5299
|
+
{
|
|
5300
|
+
"component": "Stack",
|
|
5301
|
+
"props": {
|
|
5302
|
+
"gap": 24
|
|
5303
|
+
},
|
|
5304
|
+
"slots": {
|
|
5305
|
+
"body": [
|
|
5306
|
+
{
|
|
5307
|
+
"component": "Field",
|
|
5308
|
+
"props": {
|
|
5309
|
+
"group": true,
|
|
5310
|
+
"required": true,
|
|
5311
|
+
"requiredText": "(Required)"
|
|
5312
|
+
},
|
|
5313
|
+
"slots": {
|
|
5314
|
+
"label": [
|
|
5315
|
+
"{content:category-label}"
|
|
5316
|
+
],
|
|
5317
|
+
"body": [
|
|
5318
|
+
{
|
|
5319
|
+
"component": "RadioGroup",
|
|
5320
|
+
"slots": {
|
|
5321
|
+
"body": [
|
|
5322
|
+
{
|
|
5323
|
+
"component": "Radio",
|
|
5324
|
+
"props": {
|
|
5325
|
+
"value": "problem"
|
|
5326
|
+
},
|
|
5327
|
+
"content": "first-option"
|
|
5328
|
+
},
|
|
5329
|
+
{
|
|
5330
|
+
"component": "Radio",
|
|
5331
|
+
"props": {
|
|
5332
|
+
"value": "idea"
|
|
5333
|
+
},
|
|
5334
|
+
"content": "second-option"
|
|
5335
|
+
},
|
|
5336
|
+
{
|
|
5337
|
+
"component": "Radio",
|
|
5338
|
+
"props": {
|
|
5339
|
+
"value": "other"
|
|
5340
|
+
},
|
|
5341
|
+
"content": "third-option"
|
|
5342
|
+
}
|
|
5343
|
+
]
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
]
|
|
5347
|
+
}
|
|
5348
|
+
},
|
|
5349
|
+
{
|
|
5350
|
+
"component": "Field",
|
|
5351
|
+
"slots": {
|
|
5352
|
+
"label": [
|
|
5353
|
+
"{content:description-label}"
|
|
5354
|
+
],
|
|
5355
|
+
"body": [
|
|
5356
|
+
{
|
|
5357
|
+
"component": "Textarea",
|
|
5358
|
+
"props": {
|
|
5359
|
+
"maxLength": 500,
|
|
5360
|
+
"rows": 4
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
],
|
|
5364
|
+
"description": [
|
|
5365
|
+
"{content:description-help}"
|
|
5366
|
+
]
|
|
5367
|
+
}
|
|
5368
|
+
},
|
|
5369
|
+
{
|
|
5370
|
+
"component": "Toolbar",
|
|
5371
|
+
"slots": {
|
|
5372
|
+
"body": [
|
|
5373
|
+
{
|
|
5374
|
+
"component": "Button",
|
|
5375
|
+
"props": {
|
|
5376
|
+
"type": "submit",
|
|
5377
|
+
"variant": "accent"
|
|
5378
|
+
},
|
|
5379
|
+
"content": "submit-label"
|
|
5380
|
+
}
|
|
5381
|
+
]
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
]
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
]
|
|
5388
|
+
}
|
|
5389
|
+
},
|
|
5390
|
+
"parameters": [],
|
|
5391
|
+
"content": {
|
|
5392
|
+
"category-label": "Category",
|
|
5393
|
+
"first-option": "Something is broken",
|
|
5394
|
+
"second-option": "An idea",
|
|
5395
|
+
"third-option": "Something else",
|
|
5396
|
+
"description-label": "Description",
|
|
5397
|
+
"description-help": "[what happened, and what you expected]",
|
|
5398
|
+
"submit-label": "Send"
|
|
5399
|
+
},
|
|
5400
|
+
"gaps": [],
|
|
5401
|
+
"blocked": false,
|
|
5402
|
+
"preset": "<Panel\n padding={24}\n>\n <Stack\n gap={24}\n >\n <Field\n group={true}\n label=\"Category\"\n required={true}\n requiredText=\"(Required)\"\n >\n <RadioGroup>\n <Radio value=\"problem\">Something is broken</Radio>\n <Radio value=\"idea\">An idea</Radio>\n <Radio value=\"other\">Something else</Radio>\n </RadioGroup>\n </Field>\n <Field\n description=\"[what happened, and what you expected]\"\n label=\"Description\"\n >\n <Textarea maxLength={500} rows={4} />\n </Field>\n <Toolbar>\n <Button type=\"submit\" variant=\"accent\">Send</Button>\n </Toolbar>\n </Stack>\n</Panel>\n"
|
|
4869
5403
|
},
|
|
4870
5404
|
{
|
|
4871
5405
|
"id": "filter-form",
|
package/llms.txt
CHANGED
|
@@ -7,4 +7,4 @@ Ids: component:<Name>, token:<name>, topic:<key>, pattern:<id>; bare names resol
|
|
|
7
7
|
|
|
8
8
|
Connect: https://psi.kurkin.de/mcp (Streamable HTTP) or `npx @handamade/psi-mcp` (stdio).
|
|
9
9
|
Agent context: `npx @handamade/psi-mcp init [--claude]` writes a marker-delimited block into AGENTS.md/CLAUDE.md (D44).
|
|
10
|
-
The index bakes packages/react/dist/{manifest.json,patterns.json} (patterns.json:
|
|
10
|
+
The index bakes packages/react/dist/{manifest.json,patterns.json} (patterns.json: 24 composition patterns with clarifying parameters, blocked/gaps flags, preset JSX) + packages/tokens/dist/{guidance.json,resolved/*.json} at publish; lockstep versioning keeps it truthful (D43).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handamade/psi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server and agent-context generator for the Psi (Ψ) design system",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"tsx": "^4.22.4",
|
|
34
34
|
"typescript": "^5.7.0",
|
|
35
35
|
"vitest": "^3.2.6",
|
|
36
|
-
"@handamade/psi-
|
|
37
|
-
"@handamade/psi-
|
|
36
|
+
"@handamade/psi-react": "0.27.0",
|
|
37
|
+
"@handamade/psi-tokens": "0.27.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsx scripts/build-index.ts && tsc -p tsconfig.build.json"
|