@handamade/psi-react 0.11.0 → 0.13.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/Dialog/Dialog.d.ts +18 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Tabs/Tab.d.ts +19 -0
- package/dist/Tabs/Tab.d.ts.map +1 -0
- package/dist/Tabs/TabList.d.ts +19 -0
- package/dist/Tabs/TabList.d.ts.map +1 -0
- package/dist/Tabs/TabPanel.d.ts +23 -0
- package/dist/Tabs/TabPanel.d.ts.map +1 -0
- package/dist/Tabs/Tabs.d.ts +45 -0
- package/dist/Tabs/Tabs.d.ts.map +1 -0
- package/dist/Tabs/useTabsKeyboard.d.ts +21 -0
- package/dist/Tabs/useTabsKeyboard.d.ts.map +1 -0
- package/dist/a11y-meta.d.ts.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +722 -571
- package/dist/manifest.json +182 -2
- package/dist/patterns.json +55 -23
- package/dist/styles.css +1 -1
- package/docs/Dialog.md +4 -3
- package/docs/Tab.md +39 -0
- package/docs/TabList.md +40 -0
- package/docs/TabPanel.md +38 -0
- package/docs/Tabs.md +40 -0
- package/llms.txt +13 -1
- package/package.json +2 -2
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @handamade/psi-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
32 React 19 components consuming @handamade/psi-tokens. Zero runtime deps, CSS Modules, ref-as-prop.
|
|
4
4
|
|
|
5
5
|
## Machine-readable artifacts
|
|
6
6
|
- dist/manifest.json: full component/prop inventory with types and defaults
|
|
@@ -48,12 +48,24 @@
|
|
|
48
48
|
- `<Pagination page={n} pageCount={total} onPageChange={...}>`: a numbered pager with ellipsis truncation (`‹ 1 … 4 5 6 … 13 ›`), built from Button/IconButton (D63). `siblingCount` (default 1) sets how many pages flank the current one before truncating; `aria-current="page"` marks the active page and the ellipsis is inert.
|
|
49
49
|
- It is **standalone, not a Table family member** and has no connection to `Table` — `Table` has no `page` prop. The `table-pagination` pattern composes it as a `Toolbar` sibling of a page-size `Select`; the consumer owns page state and slices the rows.
|
|
50
50
|
|
|
51
|
+
## Dialog, and drawers (there is no Drawer component)
|
|
52
|
+
- `<Dialog open onClose={(reason) => …} title footer width={400|560|720} dismissible>`: modal on the native `<dialog>` top layer. **Controlled-only** (D50) — every dismissal path (`"esc"`, `"backdrop"`, `"close-button"`) only *reports* via `onClose(reason)`; the consumer flips `open`. `dismissible={false}` swallows Esc and backdrop, leaving the footer as the only exit.
|
|
53
|
+
- **A drawer / side sheet is `<Dialog placement="inline-end">`, not a separate component (D66).** `placement` is `center | inline-start | inline-end`; the `inline-*` values pin the panel full-height to that edge. Do not import `Drawer` — it does not exist and never will. `width` is then the drawer's width; the height is always the viewport.
|
|
54
|
+
- Placement changes position and nothing else: modality, the focus trap, `aria-modal`, focus restore and the dismissal reasons are identical. Logical (`inline-*`, not left/right), so RTL flips for free. Consult dist/patterns.json for the `detail-drawer` pattern.
|
|
55
|
+
|
|
51
56
|
## Toast family (3) + useToast
|
|
52
57
|
- `<Toast variant="neutral|success|warning|danger" action={...} onDismiss={...}>message</Toast>` is presentational and **controlled** (D64): it holds no state, runs no timer and never removes itself — `onDismiss` reports and the owner disposes. Variants are Tag's **status** axis, not the flat action variants; there is deliberately no `accent`. The variant's meaning is announced by a visually hidden status word, so never rely on the icon colour alone.
|
|
53
58
|
- `<ToastRegion placement="bottom-end">` is the positioned container and must wrap every toast. It renders **two always-present live wrappers** (`role="status"` polite, `role="alert"` assertive) and routes each toast by variant — neutral/success polite, warning/danger assertive. It sits on the native top layer via `popover="manual"`, so a toast raised from inside a modal `Dialog` is still painted above the backdrop and still announced — though not clickable until the dialog closes, since `showModal()` makes everything outside the dialog inert. Raise toasts *after* the dialog closes if they carry an `action`.
|
|
54
59
|
- `<ToastProvider limit={3} duration={5000} actionDuration={10000}>` owns the queue, the timers and the single region — the library's **one stateful container** (D65), and opt-in. Inside it, `const toast = useToast()` gives `show({variant, message, action}) => id`, `dismiss(id)` and `clear()`. Timers pause while the pointer or focus is in the region and resume with the time remaining (WCAG 2.2.1); toasts carrying an action get the longer lifetime.
|
|
55
60
|
- **Do not hand-roll a queue or a `setTimeout`** — that is what `ToastProvider` is for. Do not render a bare `<Toast>` outside a `ToastRegion`: it would announce nothing. `useToast()` throws outside a provider rather than silently doing nothing.
|
|
56
61
|
|
|
62
|
+
## Tabs family (4)
|
|
63
|
+
- `<Tabs value={v} onValueChange={setV} orientation="horizontal|vertical">` is **controlled-only** (D67): `value` and `onValueChange` are required, there is no `defaultValue`, and Tabs never selects itself. Compose `<TabList aria-label="…">` with one `<Tab value>` each, then one `<TabPanel value>` per tab.
|
|
64
|
+
- **Values are strings, not indices**, and `Tab`/`TabPanel` pair by value — their source order need not match, though keeping it matching reads better.
|
|
65
|
+
- **Activation is automatic**: arrow keys move focus and selection together. There is no manual-activation mode. Arrows follow the orientation's axis (Left/Right horizontal, Up/Down vertical) with wrap; Home/End jump to the first/last enabled tab; disabled tabs are skipped and use `aria-disabled`, not the `disabled` attribute.
|
|
66
|
+
- **Every panel renders; unselected ones get `hidden`.** So `aria-controls` always resolves and panel DOM state survives a switch. If a panel is expensive, render less *inside* it (`{selected ? <Heavy/> : null}`) rather than omitting the `TabPanel` — omitting it leaves the tab's `aria-controls` pointing at nothing.
|
|
67
|
+
- `TabList` requires `aria-label`. The active panel is itself a tab stop, so a panel with no focusable content is still reachable.
|
|
68
|
+
|
|
57
69
|
## Compositions (recipes for what has no component yet)
|
|
58
70
|
|
|
59
71
|
- Form field: wrap the label, control, and message with `<Field label description error required>{control}</Field>` — label association (htmlFor/id), the description/error message line (swaps to error, aria-live="polite"), and aria-describedby/aria-invalid wiring onto a nested Input/Select are automatic (D49). `group` mode renders fieldset/legend for self-labeled controls (Checkbox/Switch). Standalone Input/Select used outside Field still need `aria-invalid` and `aria-describedby` paired manually. Spacing: `psi-gap-24` between fields, `psi-gap-12` for toggle groups, `psi-gap-8` for button rows and label+Tag rows.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handamade/psi-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"tsx": "^4.22.4",
|
|
41
41
|
"vite": "^6.0.0",
|
|
42
42
|
"vitest": "^3.2.6",
|
|
43
|
-
"@handamade/psi-tokens": "0.
|
|
43
|
+
"@handamade/psi-tokens": "0.13.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "vite build && tsc -p tsconfig.build.json && tsx scripts/emit-manifest.ts && tsx scripts/emit-patterns.ts && tsx scripts/emit-docs.ts",
|