@juwel-development/design-system 3.9.1 → 3.10.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.
Files changed (79) hide show
  1. package/README.md +9 -298
  2. package/dist/design-system.js +1026 -582
  3. package/dist/index.css +1 -1
  4. package/dist/types/Arrangement/ColumnLayout/ColumnLayout.d.ts +92 -0
  5. package/dist/types/Arrangement/ColumnLayout/ColumnLayoutCompositionError.d.ts +3 -0
  6. package/dist/types/Arrangement/ColumnLayout/ColumnLayoutConfigurationError.d.ts +3 -0
  7. package/dist/types/Arrangement/FieldRow/FieldRow.d.ts +87 -0
  8. package/dist/types/Arrangement/FieldRow/FieldRowCompositionError.d.ts +3 -0
  9. package/dist/types/Arrangement/FieldRow/FieldRowConfigurationError.d.ts +3 -0
  10. package/dist/types/Arrangement/FieldRow/IControlEdge.d.ts +6 -0
  11. package/dist/types/Arrangement/FieldRow/alignControlEdges.d.ts +7 -0
  12. package/dist/types/Display/Box/Box.d.ts +41 -0
  13. package/dist/types/Display/Brandmark/Brandmark.d.ts +1 -1
  14. package/dist/types/Display/DefinitionList/DefinitionList.d.ts +59 -9
  15. package/dist/types/Display/DefinitionList/DefinitionListConfigurationError.d.ts +3 -0
  16. package/dist/types/Display/Figure/Figure.d.ts +2 -2
  17. package/dist/types/Display/Icon/Icon.d.ts +26 -0
  18. package/dist/types/Display/Table/Table.d.ts +118 -8
  19. package/dist/types/Display/Table/TableConfigurationError.d.ts +3 -0
  20. package/dist/types/Display/Typography/Eyebrow/Eyebrow.d.ts +1 -1
  21. package/dist/types/Display/Typography/H1/H1.d.ts +3 -2
  22. package/dist/types/Display/Typography/H2/H2.d.ts +3 -2
  23. package/dist/types/Display/Typography/H3/H3.d.ts +3 -2
  24. package/dist/types/Display/Typography/H4/H4.d.ts +3 -2
  25. package/dist/types/Display/Typography/H5/H5.d.ts +3 -2
  26. package/dist/types/Display/Typography/H6/H6.d.ts +3 -2
  27. package/dist/types/Display/Typography/Note/Note.d.ts +1 -1
  28. package/dist/types/Display/Typography/P/P.d.ts +3 -2
  29. package/dist/types/Display/Typography/Prose/Prose.d.ts +1 -1
  30. package/dist/types/Interaction/Button/Button.d.ts +23 -3
  31. package/dist/types/Interaction/Tabs/Tabs.d.ts +31 -6
  32. package/dist/types/Layout/Header/Header.d.ts +67 -8
  33. package/dist/types/Layout/ScrollContainer/ScrollContainer.d.ts +37 -0
  34. package/dist/types/Layout/Section/Section.d.ts +1 -1
  35. package/dist/types/Layout/Sidebar/Sidebar.d.ts +4 -0
  36. package/dist/types/Theme/Palette.d.ts +31 -7
  37. package/dist/types/index.d.ts +6 -0
  38. package/package.json +1 -1
  39. package/src/Arrangement/ColumnLayout/ColumnLayout.tsx +249 -0
  40. package/src/Arrangement/ColumnLayout/ColumnLayoutCompositionError.ts +8 -0
  41. package/src/Arrangement/ColumnLayout/ColumnLayoutConfigurationError.ts +6 -0
  42. package/src/Arrangement/FieldRow/FieldRow.tsx +313 -0
  43. package/src/Arrangement/FieldRow/FieldRowCompositionError.ts +6 -0
  44. package/src/Arrangement/FieldRow/FieldRowConfigurationError.ts +6 -0
  45. package/src/Arrangement/FieldRow/IControlEdge.ts +6 -0
  46. package/src/Arrangement/FieldRow/alignControlEdges.ts +35 -0
  47. package/src/Display/Box/Box.tsx +77 -0
  48. package/src/Display/Checklist/Checklist.tsx +1 -1
  49. package/src/Display/DefinitionList/DefinitionList.tsx +164 -22
  50. package/src/Display/DefinitionList/DefinitionListConfigurationError.ts +6 -0
  51. package/src/Display/Icon/Icon.tsx +63 -0
  52. package/src/Display/Table/Table.tsx +434 -44
  53. package/src/Display/Table/TableConfigurationError.ts +6 -0
  54. package/src/Display/Typography/H1/H1.tsx +3 -2
  55. package/src/Display/Typography/H2/H2.tsx +3 -2
  56. package/src/Display/Typography/H3/H3.tsx +3 -2
  57. package/src/Display/Typography/H4/H4.tsx +3 -2
  58. package/src/Display/Typography/H5/H5.tsx +3 -2
  59. package/src/Display/Typography/H6/H6.tsx +3 -2
  60. package/src/Display/Typography/P/P.tsx +3 -2
  61. package/src/Display/Typography/Prose/Prose.tsx +3 -3
  62. package/src/Interaction/Button/Button.tsx +45 -17
  63. package/src/Interaction/Input/Input.tsx +1 -1
  64. package/src/Interaction/MultiSelect/MultiSelect.tsx +1 -1
  65. package/src/Interaction/NumberInput/NumberInput.tsx +1 -1
  66. package/src/Interaction/Select/Select.tsx +1 -1
  67. package/src/Interaction/Tabs/Tabs.tsx +38 -11
  68. package/src/Interaction/TextArea/TextArea.tsx +1 -1
  69. package/src/Layout/Dialog/Dialog.tsx +4 -3
  70. package/src/Layout/Header/Header.tsx +139 -39
  71. package/src/Layout/PageHead/PageHead.tsx +6 -5
  72. package/src/Layout/ScrollContainer/ScrollContainer.tsx +149 -0
  73. package/src/Layout/Sidebar/Sidebar.tsx +8 -4
  74. package/src/Theme/Palette.ts +37 -9
  75. package/src/Theme/renderTokens.ts +101 -5
  76. package/src/index.ts +6 -0
  77. package/src/tokens.css +68 -4
  78. package/src/tokens.dark.css +66 -4
  79. package/src/tokens.light.css +64 -2
@@ -1,8 +1,8 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const figure: (props?: ({
4
- ratio?: "portrait" | "square" | "landscape" | "wide" | null | undefined;
5
- focus?: "center" | "top" | "bottom" | "left" | "right" | null | undefined;
4
+ ratio?: "square" | "portrait" | "landscape" | "wide" | null | undefined;
5
+ focus?: "center" | "left" | "right" | "bottom" | "top" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface IFigureProps extends VariantProps<typeof figure> {
8
8
  /** The image source. Produce it through the consuming build's image pipeline - see @UXGuidelines. */
@@ -0,0 +1,26 @@
1
+ import type { FunctionComponent } from 'react';
2
+ declare const drawings: {
3
+ readonly sort: readonly ["M5.5 13V3", "M3 5.5l2.5-2.5L8 5.5", "M10.5 3v10", "M8 10.5l2.5 2.5 2.5-2.5"];
4
+ readonly 'sort-ascending': readonly ["M8 13V3", "M4 7l4-4 4 4"];
5
+ readonly 'sort-descending': readonly ["M8 3v10", "M4 9l4 4 4-4"];
6
+ };
7
+ export interface IIconProps {
8
+ /** Which drawing. A name selects a shape, never a state or a behaviour. */
9
+ name: keyof typeof drawings;
10
+ testId?: string;
11
+ }
12
+ /**
13
+ * A reusable visual glyph: the three sort indicators a consumer composes inside a header cell's
14
+ * plain Button, or beside any text that names what the icon reinforces.
15
+ *
16
+ * @Guarantees — enforced on every render
17
+ * - Hidden from assistive technology and out of the tab order: it adds no stop and no spoken name.
18
+ * - Scales with the surrounding font-size and takes the surrounding text colour.
19
+ * - The three drawings differ in shape, so the ordering is never carried by colour alone.
20
+ *
21
+ * @CallerMustEnsure
22
+ * - The control or text beside it carries the meaning: a `Button` label, a header cell's `ariaSort`.
23
+ * An icon standing alone says nothing to a screen reader.
24
+ */
25
+ export declare const Icon: FunctionComponent<IIconProps>;
26
+ export {};
@@ -1,33 +1,78 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
- import type { FunctionComponent, ReactNode } from 'react';
2
+ import { type FunctionComponent, type ReactNode } from 'react';
3
+ import type { Observable, Subject } from 'rxjs';
4
+ declare const table: (props?: ({
5
+ density?: "compact" | "comfortable" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
3
7
  declare const tableCell: (props?: ({
4
- variant?: "note" | "value" | null | undefined;
8
+ variant?: "value" | "note" | null | undefined;
5
9
  align?: "center" | "left" | "right" | null | undefined;
6
10
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
11
  declare const tableHeaderCell: (props?: ({
8
12
  align?: "center" | "left" | "right" | null | undefined;
9
13
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
- interface ITableRootProps {
14
+ /** The width roles a column may take, as its fixed width or as a floor. Each names a column job the
15
+ * consumer specification attests - the subject's name, a short comparison fact, a tabular figure
16
+ * with its unit, one action control - and is read from the theme as `--table-column-<role>`. */
17
+ type TableColumnWidthRole = 'name' | 'fact' | 'figure' | 'action';
18
+ /**
19
+ * One column's allocation of a table's width, independent of the rows currently displayed: either
20
+ * fixed at a named width role, or a positive share of the width the fixed columns leave, with an
21
+ * optional named minimum as its floor. Declared once on `Table.Root`, in cell order.
22
+ */
23
+ export type TableColumnAllocation = {
24
+ /** The role whose width this column takes exactly, at any available width. */
25
+ readonly width: TableColumnWidthRole;
26
+ readonly weight?: never;
27
+ /** A floor: a `width` smaller than its minimum resolves to the minimum. */
28
+ readonly minWidth?: TableColumnWidthRole;
29
+ } | {
30
+ /** This column's share of the width the fixed columns leave, relative to its siblings' weights:
31
+ * a positive finite number. `3` beside `1` is three quarters and one quarter of it. */
32
+ readonly weight: number;
33
+ readonly width?: never;
34
+ /** The least this column takes. While a minimum holds a column, the other proportional columns
35
+ * share what is left; with none, the share may shrink to nothing. */
36
+ readonly minWidth?: TableColumnWidthRole;
37
+ };
38
+ export interface ITableRootProps extends VariantProps<typeof table> {
11
39
  /** The table's accessible name. Rendered as the first child; always present. */
12
40
  caption: string;
13
41
  /** What the note column is. Governs narrow-viewport behaviour; omit when there is none. */
14
42
  notes?: 'supplementary' | 'content';
43
+ /** The columns' allocations, in the order the cells of every row are written. Omit it and widths
44
+ * follow content as before; declare it and every row shares one allocation the rows' content
45
+ * cannot move, and the narrow-viewport `notes` behaviour is replaced by residual scrolling. */
46
+ columns?: readonly TableColumnAllocation[];
15
47
  children?: ReactNode;
16
48
  testId?: string;
17
49
  }
18
- interface ITableSectionProps {
50
+ export interface ITableSectionProps {
19
51
  children?: ReactNode;
20
52
  }
21
- interface ITableRowProps {
53
+ export interface ITableRowProps {
22
54
  children?: ReactNode;
23
55
  testId?: string;
56
+ /** Emits once per activation of the row body - a click, or Enter or Space while the row has
57
+ * focus. Its presence is what makes the row interactive: a tab stop, a visible focus ring and a
58
+ * pointer cursor. Nested links and controls keep their own operations and never emit here.
59
+ * Activation changes nothing about the row; the consumer decides what the request means. */
60
+ onClick$?: Subject<void>;
61
+ /** The consumer's selection for this row. Rendered as `aria-selected` and the marker bar; omitted
62
+ * or not yet emitted means unselected. Selection is independent of `onClick$`: a selected row
63
+ * may be noninteractive, and an interactive row may be unselected. */
64
+ isSelected$?: Observable<boolean>;
24
65
  }
25
- interface ITableCellProps extends VariantProps<typeof tableCell> {
66
+ export interface ITableCellProps extends VariantProps<typeof tableCell> {
26
67
  children?: ReactNode;
27
68
  }
28
- interface ITableHeaderCellProps extends VariantProps<typeof tableHeaderCell> {
69
+ export interface ITableHeaderCellProps extends VariantProps<typeof tableHeaderCell> {
29
70
  /** Explicit, never inferred from Head/Body position - inference would need render-time context. */
30
71
  scope: 'row' | 'col';
72
+ /** The order the column is *currently* displayed in, as accessibility metadata only. Omit it on a
73
+ * column that is not sortable; set it on the one ordered column. Changing it neither reorders rows
74
+ * nor triggers anything - the consumer owns the sort and composes the action in `children`. */
75
+ ariaSort?: 'none' | 'ascending' | 'descending' | 'other';
31
76
  children?: ReactNode;
32
77
  }
33
78
  /**
@@ -41,9 +86,74 @@ interface ITableHeaderCellProps extends VariantProps<typeof tableHeaderCell> {
41
86
  * - The block reads as a table from two rule weights alone: the heavier `rule` above the first row,
42
87
  * `border` hairlines between rows, and no bottom rule on the last. No cell borders, fill, zebra or hover.
43
88
  * - `Cell variant="value"` sets tabular figures; `variant="note"` does not. Both at the 15px small role.
44
- * - `HeaderCell` emits the `scope` it is given; none is inferred.
89
+ * - `HeaderCell` emits the `scope` it is given; none is inferred. It emits `ariaSort` the same way:
90
+ * the attribute states the displayed order and the component never orders, cycles or requests one.
91
+ * - Residual horizontal overflow scrolls in every `notes` mode, with no opt-in. The wrapper is
92
+ * keyboard-reachable while there is something to scroll - always, as a named region, with no note
93
+ * column; as a named group only once the table overflows, with one. No vertical bound is ever set:
94
+ * a long table sits inside a `ScrollContainer` with `axis="vertical"`, which owns that axis.
95
+ * - `Root columns` declares every column's allocation once, in cell order, and every row shares it:
96
+ * a fixed column takes its named width role at any available width; proportional columns share
97
+ * the width the fixed ones leave by their weights, each floored at its named minimum, and while a
98
+ * minimum holds one column the others share what is left. A fixed width below its minimum is the
99
+ * minimum. Nothing a row holds moves a boundary: filtering, sorting, paging, long or short content,
100
+ * an empty body and its repopulation all leave the allocation as it was. Only the definitions, the
101
+ * theme's `--table-column-*` values and the available width can. All-fixed columns do not stretch.
102
+ * - With `columns` declared, no narrow-viewport rule applies whatever `notes` says: the note column
103
+ * stays, rows stay tabular, and what does not fit scrolls in the wrapper as above. Text wraps inside
104
+ * its allocation, an unbroken run included; the table never truncates, hides or resizes content.
105
+ * Without `columns`, widths follow content and both `notes` behaviours are exactly as before.
106
+ * - `Root density` insets every header and body cell from one theme pair: `comfortable` (the
107
+ * default, the former spacing exactly) or `compact`, per table. It moves no type size and no
108
+ * control's own dimensions. A non-positive or non-finite `weight` throws
109
+ * {@link TableConfigurationError}, loud and early.
110
+ * - A `Row` given `onClick$` is interactive: a tab stop with the shared focus ring, activated by a
111
+ * click on its body or by Enter or Space while focused, emitting exactly once per activation. A
112
+ * nested link, button or other control - and anything inside one - performs its own operation and
113
+ * never activates the row. Without `onClick$` the row body is inert and adds no tab stop; nested
114
+ * controls stay operable. Activation never changes selection.
115
+ * - A `Row` given `isSelected$` carries `aria-selected` and, when true, the marker bar along its
116
+ * leading edge in `foreground` - a shape, not a colour, and not the focus ring. Omitted or not yet
117
+ * emitted reads unselected; a replaced source reads unselected until it emits; unmounting
118
+ * unsubscribes. The selection input is independent of `onClick$`, so a row may be selected and
119
+ * noninteractive, interactive and unselected, or both. Rendering and selection changes emit nothing.
120
+ * - The row stays a `tr` in a `table`: no grid role, no arrow-key navigation. `aria-selected` is a
121
+ * WAI-ARIA 1.2 state of `row` and valid here, but Chromium exposes a row's selected state only
122
+ * inside a `grid`, so Chrome and Edge screen readers do not announce it on these rows (accepted
123
+ * limitation, #113). The marker bar is the one guaranteed selection cue.
124
+ * - A table holding a selection input anywhere insets every row's first cell by the cell padding,
125
+ * head and foot included, so the marker has room and no column shifts as the selection moves. A
126
+ * table holding an interactive row makes ring room around itself, so a focused row's ring is never
127
+ * clipped by the scroll region. A table with neither keeps its static geometry exactly.
128
+ *
129
+ * @CallerMustEnsure — the component cannot see these and does not check them
130
+ * - Every row writes exactly as many cells as there are `columns`, in the same order. A row with
131
+ * fewer leaves tracks empty; one with more breaks onto a second line of its own row.
132
+ * - A cell whose content cannot wrap - a `Button`, an `Input`, an image - sits in a column whose
133
+ * fixed width or minimum holds it: the `action` role holds one standard control at comfortable
134
+ * density. The allocation never widens for content, so an under-allocated control overflows its
135
+ * cell rather than moving its neighbours.
136
+ * - A cell's `align` and `variant` are the consumer's as before; an allocation sets neither.
137
+ * - Each row's `onClick$` and `isSelected$` are tied to that row's stable identity, so a reordered or
138
+ * temporarily removed row keeps its association. Table holds no identity, registry or policy, and
139
+ * removing a row never asks the consumer to clear or replace its selection.
140
+ * - What an activation means - select, open, toggle - is the consumer's answer, given by rerendering
141
+ * from its own state. A request left unanswered leaves the rendered selection unchanged.
142
+ * - An interactive row announces no verb of its own; the caption, a row header or a nested link
143
+ * should make the row's purpose plain.
45
144
  *
46
145
  * @UXGuidelines
146
+ * - Allocate by job, not by measurement: the subject's `name` first, proportional with a minimum so it
147
+ * wraps rather than vanishes; comparison facts proportional at `fact`; figures fixed at `figure`,
148
+ * right-aligned; the action column fixed at `action`, last. A theme that re-points a role moves
149
+ * every table using it, which is the point of naming the role rather than the width.
150
+ * - `compact` is for a dense comparison the viewer scans, not for fitting more in: it changes air,
151
+ * not type, so a table that overflows at `comfortable` mostly still overflows at `compact`.
152
+ * - A sortable column is composed, not configured: a `Button variant="plain"` inside the `HeaderCell`
153
+ * carries the label and an `Icon` (`sort`, `sort-ascending`, `sort-descending`) that matches the
154
+ * order the consumer currently displays, and `ariaSort` on the same cell says so to assistive
155
+ * technology. Only the ordered column carries `ariaSort`; an action-only column carries no sort
156
+ * control. Until the consumer's data arrives, both icon and `ariaSort` keep stating the old order.
47
157
  * - `align` is a cell property but reads as a column one: set the same `align` on a `HeaderCell` and
48
158
  * every `Cell` beneath it, and keep them in sync - the component cannot align a column for you.
49
159
  * - Choose `notes` by what the note column *is*: `"supplementary"` drops it below 48rem for everyone
@@ -0,0 +1,3 @@
1
+ export declare class TableConfigurationError extends Error {
2
+ constructor(reason: string);
3
+ }
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const eyebrow: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IEyebrowProps extends VariantProps<typeof eyebrow> {
7
7
  children: ReactNode;
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h1: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH1Props extends VariantProps<typeof h1> {
7
7
  children: ReactNode;
@@ -12,7 +12,8 @@ interface IH1Props extends VariantProps<typeof h1> {
12
12
  *
13
13
  * @Guarantees — enforced on every render
14
14
  * - Renders an `h1`; its outline level and the display role are one choice, not two (docs/adr/0005).
15
- * - Reads `--font-primary`, sized by `--text-display`, led by `--leading-display` and optically
15
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
16
+ * re-points it (#120), sized by `--text-display`, led by `--leading-display` and optically
16
17
  * corrected by `--tracking-optical`, the large-type correction every role from title up carries.
17
18
  * - Bounded at `--measure-display`, the display role's own measure — narrower than the reading column
18
19
  * because bigger type wants fewer characters per line (docs/adr/0004). The bound is the recipe's,
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h2: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH2Props extends VariantProps<typeof h2> {
7
7
  children: ReactNode;
@@ -12,7 +12,8 @@ interface IH2Props extends VariantProps<typeof h2> {
12
12
  *
13
13
  * @Guarantees — enforced on every render
14
14
  * - Renders an `h2`; its outline level and the title role are one choice, not two (docs/adr/0005).
15
- * - Reads `--font-primary`, sized by `--text-title`, led by `--leading-title` and optically corrected
15
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
16
+ * re-points it (#120), sized by `--text-title`, led by `--leading-title` and optically corrected
16
17
  * by `--tracking-optical` — the title role is the smallest role that carries it, so `H3` and below
17
18
  * take none.
18
19
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h3: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH3Props extends VariantProps<typeof h3> {
7
7
  children: ReactNode;
@@ -13,7 +13,8 @@ interface IH3Props extends VariantProps<typeof h3> {
13
13
  *
14
14
  * @Guarantees — enforced on every render
15
15
  * - Renders an `h3`; its outline level and the subtitle role are one choice, not two (docs/adr/0005).
16
- * - Reads `--font-primary`, sized by `--text-subtitle` and led by `--leading-subtitle`.
16
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
17
+ * re-points it (#120), sized by `--text-subtitle` and led by `--leading-subtitle`.
17
18
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
18
19
  * paints text. A status tone changes colour only and adds no announcement semantics.
19
20
  *
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h4: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH4Props extends VariantProps<typeof h4> {
7
7
  children: ReactNode;
@@ -12,7 +12,8 @@ interface IH4Props extends VariantProps<typeof h4> {
12
12
  *
13
13
  * @Guarantees — enforced on every render
14
14
  * - Renders an `h4`; its outline level and the body role are one choice, not two (docs/adr/0005).
15
- * - Reads `--font-primary`, sized by `--text-body`, and is bold so it stands apart from a paragraph.
15
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
16
+ * re-points it (#120), sized by `--text-body`, and is bold so it stands apart from a paragraph.
16
17
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
17
18
  * paints text. A status tone changes colour only and adds no announcement semantics.
18
19
  *
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h5: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH5Props extends VariantProps<typeof h5> {
7
7
  children: ReactNode;
@@ -13,7 +13,8 @@ interface IH5Props extends VariantProps<typeof h5> {
13
13
  *
14
14
  * @Guarantees — enforced on every render
15
15
  * - Renders an `h5`; its outline level and the body role are one choice, not two (docs/adr/0005).
16
- * - Reads `--font-primary`, sized by `--text-body`, semibold so it stands apart from a paragraph.
16
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
17
+ * re-points it (#120), sized by `--text-body`, semibold so it stands apart from a paragraph.
17
18
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
18
19
  * paints text. A status tone changes colour only and adds no announcement semantics.
19
20
  *
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const h6: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IH6Props extends VariantProps<typeof h6> {
7
7
  children: ReactNode;
@@ -13,7 +13,8 @@ interface IH6Props extends VariantProps<typeof h6> {
13
13
  *
14
14
  * @Guarantees — enforced on every render
15
15
  * - Renders an `h6`; its outline level and the body role are one choice, not two (docs/adr/0005).
16
- * - Reads `--font-primary`, sized by `--text-body`, medium so it stands apart from a paragraph.
16
+ * - Reads `--font-heading`, the heading family that follows `--font-primary` until a theme
17
+ * re-points it (#120), sized by `--text-body`, medium so it stands apart from a paragraph.
17
18
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
18
19
  * paints text. A status tone changes colour only and adds no announcement semantics.
19
20
  *
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const note: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  export interface INoteProps extends VariantProps<typeof note> {
7
7
  /** The annotation. A `ReactNode` rather than a string, because these lines carry inline links. */
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const p: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IPProps extends VariantProps<typeof p> {
7
7
  children: ReactNode;
@@ -12,7 +12,8 @@ interface IPProps extends VariantProps<typeof p> {
12
12
  * table cell — because the reading measure belongs to whatever owns the reading column (Prose #21).
13
13
  *
14
14
  * @Guarantees — enforced on every render
15
- * - Renders a `p`, reading `--font-primary`, sized by `--text-body` and led by `--leading-body`.
15
+ * - Renders a `p`, reading `--font-body` (the body family, which follows `--font-primary` until a
16
+ * theme re-points it, #120), sized by `--text-body` and led by `--leading-body`.
16
17
  * - `color` selects `foreground`, `muted`, `success`, `warning`, `error` or `info`; nothing else
17
18
  * paints text. A status tone changes colour only and adds no announcement semantics.
18
19
  *
@@ -1,7 +1,7 @@
1
1
  import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const proseBody: (props?: ({
4
- color?: "foreground" | "muted" | "success" | "warning" | "error" | "info" | null | undefined;
4
+ color?: "error" | "foreground" | "muted" | "success" | "warning" | "info" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  interface IProseRootProps {
7
7
  children?: ReactNode;
@@ -2,7 +2,7 @@ import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  import type { Subject } from 'rxjs';
4
4
  declare const button: (props?: ({
5
- variant?: "primary" | "secondary" | "ghost" | null | undefined;
5
+ variant?: "primary" | "secondary" | "ghost" | "outlined" | "destructive" | "plain" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  interface IButtonProps extends VariantProps<typeof button> {
8
8
  /** Optional: an icon-only button renders none, and names itself with `ariaLabel` instead. */
@@ -22,10 +22,27 @@ interface IButtonProps extends VariantProps<typeof button> {
22
22
  *
23
23
  * @component
24
24
  *
25
+ * @Variants
26
+ * - `primary` and `secondary`: the filled actions
27
+ * - `outlined`: the quiet secondary - `secondary` text and edge on an unfilled surface, for an
28
+ * action beside a primary one that must not compete with it
29
+ * - `destructive`: an action that removes or ends something, filled with the `error` status tone
30
+ * and inked with `errorForeground`. The tone reinforces words it never replaces: the label, or
31
+ * the `ariaLabel` of a symbol-only button, must say what the action does
32
+ * - `ghost`: the padded text action with a hover underline and no floor
33
+ *
34
+ * The four faced variants share the control minimum width and inset, so a row of them aligns, and
35
+ * every one of them shrinks and wraps its label where its holder is narrower than that.
36
+ *
25
37
  * @UXGuidelines
26
38
  * - Use clear, action-oriented text (e.g., "Save" instead of "OK")
27
- * - Keep button text concise (1-3 words)
39
+ * - Keep button text short; a translated label that runs longer wraps where the layout constrains
40
+ * it, so the words stay readable rather than overflowing
28
41
  * - Use primary buttons for main actions, secondary buttons for alternative actions
42
+ * - `plain` is a plain action (CONTEXT.md): an operable button in the typography and colour of the
43
+ * text around it, with no face, padding, corner or hover underline of its own. Reach for it where
44
+ * the action belongs to a line of content - a sortable column header composed with `Icon` - and
45
+ * for `ghost` where a quiet but still button-shaped action is wanted
29
46
  * - Maintain consistent button styling throughout the application
30
47
  * - Provide visual feedback on hover/active states
31
48
  * - Ensure sufficient touch target size (minimum 44x44px) for mobile users
@@ -35,8 +52,11 @@ interface IButtonProps extends VariantProps<typeof button> {
35
52
  *
36
53
  * @Accessibility
37
54
  * - Ensure adequate color contrast (4.5:1 minimum ratio)
38
- * - Provide focus styles for keyboard navigation
55
+ * - Provide focus styles for keyboard navigation: every variant, `plain` included, draws the shared
56
+ * focus ring and nothing else changes on focus
57
+ * - A disabled `plain` button is told apart by the `disabled` text tone and keeps native disabled semantics
39
58
  * - Use appropriate ARIA attributes when needed
59
+ * - Status colour never stands alone: a destructive action is named by its words or symbol
40
60
  */
41
61
  export declare const Button: FunctionComponent<IButtonProps>;
42
62
  export {};
@@ -18,8 +18,10 @@ export interface ITabsTabProps {
18
18
  /** The stable identity connecting this tab to its panel and emitted by selection requests.
19
19
  * Not React's `key`, and never inferred from the label or the position. */
20
20
  value: string;
21
- /** The visible text label. Text only - no icons, no per-tab markup. */
22
- children: string;
21
+ /** The label: visible text naming the view - a plain string still works - optionally with an
22
+ * icon, a count or inline emphasis beside it. Decorative parts carry the consumer's own
23
+ * `aria-hidden`; nothing in it is interactive, as the tab is the control. Rendered whole. */
24
+ children: ReactNode;
23
25
  testId?: string;
24
26
  }
25
27
  export interface ITabsPanelProps {
@@ -47,15 +49,31 @@ export interface ITabsPanelProps {
47
49
  * immediately; focus stays on the operated tab. Up/Down are left to the browser.
48
50
  * - Roving tabindex: Tab enters the list at the active tab, then the active panel - a consistent
49
51
  * Tab stop whether or not its content is focusable. Inactive panels add no stop.
50
- * - The row scrolls horizontally on overflow - one line, no wrap, no shrink - and holds its own
51
- * ring room, so the focused tab's ring survives the scroll clip.
52
+ * - A label is rendered whole, text and markup alike, however long its translation runs: never
53
+ * clipped, shortened, elided or replaced by a hint. The tab's accessible name is computed from
54
+ * its content, so what the device reads is what the viewer sees, less what the consumer marked
55
+ * `aria-hidden`. A rich label and a plain string are one and the same control.
56
+ * - The row is one line of controls. As it narrows, a label's text wraps inside its tab, down to
57
+ * the tab's longest word; only when the controls still cannot fit does the row scroll
58
+ * horizontally - the row, never the page. The row holds its own ring room so the focused tab's
59
+ * ring survives the scroll clip, and focus reveals a scrolled-off tab, so every tab is reachable
60
+ * by keyboard without scrolling first.
61
+ * - The tabs on the row share one height, so every marker sits on one line whether or not a
62
+ * neighbour's label wrapped.
52
63
  * - Selection is marked by a persistent line under the active tab: `--tab-marker-thickness` in
53
64
  * `foreground`, constant thickness in both states, so switching shifts no widths and no weights.
54
65
  * Keyboard focus is the separate shared focus ring. Colour moves on the one motion token.
55
66
  *
56
67
  * @CallerMustEnsure — the component cannot see these and does not check them
57
68
  * - One List with Tab elements as direct DOM children (arrays and fragments are supported),
58
- * and sibling Panels under Root. Do not wrap tabs in host elements.
69
+ * and the Panels under the same Root - as siblings of the List, or with it inside one
70
+ * arrangement such as a `Stack` (see the separation guidance below). Do not wrap tabs in host
71
+ * elements.
72
+ * - A label is a meaningful visible text name, with non-interactive decoration at most: no link,
73
+ * button, input or other focusable descendant, since the tab is the one control and a nested
74
+ * control inside a `button` is invalid. Decorative parts - an icon that repeats the text - carry
75
+ * `aria-hidden` so they stay out of the name; the library does not guess which parts those are.
76
+ * A count or an emphasis is meaning and stays in the name.
59
77
  * - At least two tabs, each `value` unique and stable, each with exactly one matching `Panel`
60
78
  * under the same `Root`, and `active` naming a declared pair. Invalid input is a contract
61
79
  * violation, not a request for a fallback.
@@ -66,10 +84,17 @@ export interface ITabsPanelProps {
66
84
  *
67
85
  * @UXGuidelines
68
86
  * - Labels are short names for views, not actions; the consuming app words and translates them.
87
+ * A long translation wraps inside its tab and the row scrolls only when even the longest words
88
+ * do not fit, so a label is never the reason to shorten a name.
69
89
  * - This is not a router: no location, no history, no deep links. Wire `onSelect$` to whatever
70
90
  * owns the active key and pass that key back in.
91
+ * - Tabs sets no spacing between the row and the panel; the separation is a `Stack` between Root
92
+ * and its members - `<Tabs.Root><Stack gap="region"><Tabs.List/>…<Tabs.Panel/>…</Stack>
93
+ * </Tabs.Root>`. `gap="stack"` holds the row and its view together as one block; `gap="region"`
94
+ * sets the view apart as a region of its own. Inactive panels are hidden, so the gap is exactly
95
+ * one whichever panel is active, from the token the consumer's other groups already use.
71
96
  * - The panel is an opaque slot: compose the view's own rhythm inside it - a `Stack`, a `Prose` -
72
- * as the view owns it; Tabs sets no spacing between the row and the panel.
97
+ * as the view owns it.
73
98
  */
74
99
  export declare const Tabs: {
75
100
  readonly Root: FunctionComponent<ITabsRootProps>;
@@ -2,8 +2,14 @@ import type { VariantProps } from 'class-variance-authority';
2
2
  import type { FunctionComponent, ReactNode } from 'react';
3
3
  declare const header: (props?: ({
4
4
  edge?: "none" | "rule" | null | undefined;
5
+ mode?: "navigation" | "statusAction" | null | undefined;
5
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
- export interface IHeaderProps extends VariantProps<typeof header> {
7
+ interface IHeaderShellProps extends Omit<VariantProps<typeof header>, 'mode'> {
8
+ testId?: string;
9
+ }
10
+ /** The navigation bar: the mode every existing caller is in, and the default. A standing link and a
11
+ * `<nav>`, and never a status or an action slot - the other shape is `IHeaderStatusActionProps`. */
12
+ export interface IHeaderNavigationProps extends IHeaderShellProps {
7
13
  /** The standing link: a place name, a mark, or a home link. The consumer supplies the whole anchor -
8
14
  * Header renders no link of its own. A place name uses `<Link treatment="quiet" href="/">…</Link>`; a
9
15
  * mark uses `<Link treatment="graphic" href="/"><Brandmark …/></Link>`, whose `graphic` treatment
@@ -13,15 +19,45 @@ export interface IHeaderProps extends VariantProps<typeof header> {
13
19
  navName?: string;
14
20
  /** The nav links. */
15
21
  children?: ReactNode;
16
- testId?: string;
22
+ status?: never;
23
+ action?: never;
17
24
  }
25
+ /** The status/action bar: a bar that reports and acts rather than navigates, for a product whose
26
+ * shell carries a readout and a control and no links. It has no standing link and no `<nav>` - a
27
+ * button is not navigation - and the two shapes cannot be mixed: the compiler rejects a call that
28
+ * hands this bar a standing link, a nav name or nav children. */
29
+ export interface IHeaderStatusActionProps extends IHeaderShellProps {
30
+ /** The readout - a date, a balance, a short note, or a `Cluster` of them - at the start edge, first
31
+ * in reading order. A plain `div` with no role, no name and no live region: what it holds carries its
32
+ * own semantics, and whether a change is announced is the consumer's decision, made by wrapping its
33
+ * own live region. Long and unbroken wording wraps inside the slot. Omitted, nothing renders. */
34
+ status?: ReactNode;
35
+ /** The control - a `Button`, or a `Cluster` of them - at the end edge, last in reading and keyboard
36
+ * order, on whichever line it lands when the bar breaks. A plain `div` like `status`. Header never
37
+ * decides whether the control is available: pass it disabled, or withhold it (`{ready && <Button/>}`
38
+ * renders no box). Omitted, nothing renders. */
39
+ action?: ReactNode;
40
+ standing?: never;
41
+ navName?: never;
42
+ children?: never;
43
+ }
44
+ export type IHeaderProps = IHeaderNavigationProps | IHeaderStatusActionProps;
18
45
  /**
19
- * The shell's top edge: a standing link and a nav, at the label type role. It renders the banner
20
- * landmark and a single `<nav>`, arranging nothing beyond the two slots, so it works with no hydration.
46
+ * The shell's top edge, at the label type role, in one of two mutually exclusive modes. The
47
+ * **navigation bar** - the default, and what every existing caller renders - is a standing link and a
48
+ * single `<nav>`. The **status/action bar** is a readout at the start edge and a control at the end
49
+ * edge, both plain boxes with no landmark of their own and no `<nav>` anywhere. The props type is a
50
+ * union of the two shapes, so a call that mixes them does not compile. Either bar arranges nothing
51
+ * beyond its slots and works with no hydration.
21
52
  *
22
53
  * @Guarantees — enforced on every render
23
54
  * - The header is set at the label role and carries no size of its own: `font-secondary text-label
24
55
  * leading-label tracking-label text-muted`, so it never grows past that role whatever the page font size.
56
+ * - The shell's air is one value above, below and between: `--space-region` vertically, between the
57
+ * nav's items and between the status/action bar's slots along a line, `--gutter` across, so the bar
58
+ * aligns with every inset `Section`. It is never sticky and needs no JavaScript.
59
+ *
60
+ * The navigation bar, unchanged by #125:
25
61
  * - The nav's line box is the library's own rather than the consuming document's: the header leads
26
62
  * itself at the label role, so the height it declares for its slot is the height it renders.
27
63
  * - The standing slot is floored, never fixed: its minimum height is that same line box - the label
@@ -31,10 +67,24 @@ export interface IHeaderProps extends VariantProps<typeof header> {
31
67
  * grows the slot, on one line, and is never clamped or wrapped.
32
68
  * - A nav item marked `aria-current="page"` renders at `foreground` whatever supplied it - the treatment
33
69
  * keys on the attribute, not on `Link`, so it holds against a bare anchor or any component.
34
- * - The shell's air is one value above, below and between: `--space-region` vertically and between nav
35
- * items, `--gutter` across, so the bar aligns with every inset `Section`.
36
- * - It is never sticky and needs no JavaScript: there is no `sticky` variant and nothing to hydrate.
37
- * - Omitting `navName` emits no `aria-label` at all, not an empty one.
70
+ * - The standing slot and the `<nav>` are the bar's two children whatever the caller passes, the nav
71
+ * flush with the end content edge and absorbing narrowing by wrapping its links. Omitting `navName`
72
+ * emits no `aria-label` at all, not an empty one.
73
+ *
74
+ * The status/action bar:
75
+ * - It renders no `<nav>`, no standing slot, no role, no name and no live region of its own. Each
76
+ * filled slot is one `div` directly inside the banner; an omitted or withheld slot (`false`, `null`)
77
+ * renders no box and reserves no space, so a status-only bar starts with its readout and an action-only
78
+ * bar is the control alone at the end edge.
79
+ * - Reading and keyboard order is status, then action, and the DOM order is the same at every width.
80
+ * - With both fitting, the readout sits at the start content edge and the control at the end content
81
+ * edge on one line. When they cannot share a line, the control drops below the readout and stays
82
+ * flush with the end edge; a readout then takes the whole line and wraps inside it, unbroken wording
83
+ * included, so the bar never widens the page and never clips.
84
+ * - Breaking is CSS alone: a width change re-flows the same elements, so descendant state and focus
85
+ * survive it.
86
+ * - What the consumer passes keeps its semantics: a heading stays a heading, a live region stays one, a
87
+ * disabled control stays disabled. Header adds, removes and announces nothing.
38
88
  *
39
89
  * @CallerMustEnsure — the component cannot see these and does not check them
40
90
  * - A mark that should *fill* the standing slot is given a **definite width** by whoever placed it -
@@ -47,6 +97,10 @@ export interface IHeaderProps extends VariantProps<typeof header> {
47
97
  * width; `flex: 1 1 0; min-width: 0` only collapses the mark when the bar is already out of room, so
48
98
  * it is not the rule to reach for. The library cannot apply either rule for you: the same width on a
49
99
  * place name would clamp the name and wrap it.
100
+ * - Set the type of what fills a status/action slot: the bar sets the label role, so a bare string in
101
+ * `status` reads as a label, while a `P` or a `Note` wears its own role and a `Button` its own.
102
+ * - Whether a changing readout interrupts is yours to decide: wrap your own live region inside `status`
103
+ * where a change must be announced, and leave it out where it must not. Header takes neither side.
50
104
  *
51
105
  * @UXGuidelines
52
106
  * - Name the nav with `navName` once the page has more than one navigation landmark - a footer nav will
@@ -55,6 +109,11 @@ export interface IHeaderProps extends VariantProps<typeof header> {
55
109
  * `aria-current`, so set `current` on the `Link` and style nothing yourself.
56
110
  * - The nav does not collapse into a menu: a disclosure needs JavaScript, so on a narrow viewport the
57
111
  * items wrap. A mobile menu is out of scope, not a follow-up.
112
+ * - A bar that reports and acts is a status/action bar, never a navigation bar with a button among its
113
+ * links: a Balance line or a Continue button inside a `<nav>` is announced as navigation. A product
114
+ * whose shell needs links as well as a control has two bars, not one.
115
+ * - Several readouts or several actions go in a `Cluster` inside the slot: `gap="stack"` keeps them one
116
+ * group, and the Cluster wraps inside the slot before the bar runs out of room.
58
117
  */
59
118
  export declare const Header: FunctionComponent<IHeaderProps>;
60
119
  export {};