ionbase-ui 0.18.0 → 0.19.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/dist/meta/Alert.json +0 -21
- package/dist/meta/Avatar.json +66 -0
- package/dist/meta/AvatarGroup.json +57 -0
- package/dist/meta/Badge.json +65 -0
- package/dist/meta/Button.json +0 -38
- package/dist/meta/Checkbox.json +82 -0
- package/dist/meta/Divider.json +39 -0
- package/dist/meta/FullCard.json +77 -0
- package/dist/meta/Header.json +59 -0
- package/dist/meta/Icon.json +54 -0
- package/dist/meta/Link.json +67 -0
- package/dist/meta/Logo.json +49 -0
- package/dist/meta/LogoMark.json +38 -0
- package/dist/meta/Menu.json +54 -0
- package/dist/meta/MenuItem.json +51 -0
- package/dist/meta/NavItem.json +53 -0
- package/dist/meta/PhoneInput.json +60 -0
- package/dist/meta/Popover.json +92 -0
- package/dist/meta/Radio.json +77 -0
- package/dist/meta/RadioGroup.json +84 -0
- package/dist/meta/ScrollProgress.json +41 -0
- package/dist/meta/TabItem.json +42 -0
- package/dist/meta/TableBody.json +36 -0
- package/dist/meta/TableCell.json +83 -0
- package/dist/meta/TableHead.json +39 -0
- package/dist/meta/TableRow.json +45 -0
- package/dist/meta/Tabs.json +85 -0
- package/dist/meta/Toast.json +81 -0
- package/dist/meta/ToastProvider.json +57 -0
- package/dist/meta/Toggle.json +76 -0
- package/dist/meta/Tooltip.json +74 -0
- package/dist/meta/components.json +1856 -132
- package/dist/meta/contrast.json +6 -1
- package/dist/meta/index.json +88 -59
- package/package.json +1 -1
|
@@ -4,6 +4,90 @@
|
|
|
4
4
|
"propsType": "RadioGroupProps",
|
|
5
5
|
"description": "Renders a `<fieldset>` with a `<legend>` rather than a div with\n`role=\"radiogroup\"`. Both are announced correctly, but a fieldset also groups\nthe inputs for form submission and native validation, which the ARIA version\ndoes not.",
|
|
6
6
|
"import": "import { RadioGroup } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A <fieldset>/<legend> group of mutually exclusive options. Owns the shared name and the selected value.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"exactly one choice from a small set, all worth showing at once",
|
|
11
|
+
"the options need explaining — each one can carry its own label text"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the list is long, or space is tight",
|
|
16
|
+
"use": "Select"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"when": "more than one may be picked",
|
|
20
|
+
"use": "Checkbox"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"when": "there are exactly two states and the change is immediate",
|
|
24
|
+
"use": "Toggle"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"variants": {
|
|
28
|
+
"size": {
|
|
29
|
+
"sm": {
|
|
30
|
+
"use": "dense forms"
|
|
31
|
+
},
|
|
32
|
+
"md": {
|
|
33
|
+
"use": "the default"
|
|
34
|
+
},
|
|
35
|
+
"lg": {
|
|
36
|
+
"use": "touch targets and prominent choices"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"intent": {
|
|
40
|
+
"brand": {
|
|
41
|
+
"use": "the default"
|
|
42
|
+
},
|
|
43
|
+
"neutral": {
|
|
44
|
+
"use": "a choice with no positive reading — sort order, display mode"
|
|
45
|
+
},
|
|
46
|
+
"danger": {
|
|
47
|
+
"use": "a set where the options are destructive"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"slots": {
|
|
52
|
+
"label": {
|
|
53
|
+
"accepts": "text",
|
|
54
|
+
"note": "renders as the <legend> — the question the options answer"
|
|
55
|
+
},
|
|
56
|
+
"children": {
|
|
57
|
+
"accepts": "Radio"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"a11y": {
|
|
61
|
+
"role": "group, from <fieldset> and <legend>",
|
|
62
|
+
"guarantees": [
|
|
63
|
+
"a real fieldset rather than `role=\"radiogroup\"`: both announce correctly, but the fieldset also groups the inputs for form submission and native validation",
|
|
64
|
+
"`name` is generated when omitted, so two groups on one page never collide",
|
|
65
|
+
"`isDisabled` cascades to every Radio that has not set its own"
|
|
66
|
+
],
|
|
67
|
+
"requires": [
|
|
68
|
+
"`label`, or `aria-label` when the question is already visible elsewhere"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"antiPatterns": [
|
|
72
|
+
{
|
|
73
|
+
"dont": "an unlabelled group",
|
|
74
|
+
"why": "the options are announced with no question attached"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"dont": "passing both `value` and `defaultValue`",
|
|
78
|
+
"why": "controlled and uncontrolled at once; the controlled value wins and `defaultValue` silently does nothing"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"dont": "no option selected by default with no \"none\" option",
|
|
82
|
+
"why": "a radio group cannot be returned to empty once touched"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"deprecated": [
|
|
86
|
+
{
|
|
87
|
+
"prop": "disabled",
|
|
88
|
+
"replacement": "isDisabled"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
7
91
|
"stylesheet": "src/styles/radio.css",
|
|
8
92
|
"tokens": [
|
|
9
93
|
"--border-disabled",
|
|
@@ -4,6 +4,47 @@
|
|
|
4
4
|
"propsType": "ScrollProgressProps",
|
|
5
5
|
"description": "The compact rail is the disclosure trigger. It is a real `<button>`, so a\nkeyboard user reaches it by Tab and opens it the same way as any button —\nEnter or Space — no `:focus-within` trick required; a mouse user can also\njust hover it. Built as a WAI-ARIA Disclosure (`aria-expanded` +\n`aria-controls`) rather than a menu: see the CSS header for why\n`role=\"menu\"` is deliberately not used here.\n\nCloses on outside pointerdown and on Escape — the minimum a disclosure\nneeds to not trap the page once opened, not a full popover/focus-trap\nimplementation.",
|
|
6
6
|
"import": "import { ScrollProgress } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A compact progress rail that expands into a section list. A disclosure, not a menu — and it computes nothing itself.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a long document or page needs an in-page section index with reading progress",
|
|
11
|
+
"the caller already knows the scroll fraction and the active section"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the sections are peer views rather than positions in one document",
|
|
16
|
+
"use": "Tabs"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"when": "it is site navigation rather than in-page position",
|
|
20
|
+
"use": "NavItem"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"a11y": {
|
|
24
|
+
"role": "disclosure — a real <button> with aria-expanded and aria-controls",
|
|
25
|
+
"guarantees": [
|
|
26
|
+
"the rail is reachable by Tab and opens with Enter or Space, not just hover",
|
|
27
|
+
"closes on outside pointerdown and on Escape"
|
|
28
|
+
],
|
|
29
|
+
"requires": [
|
|
30
|
+
"`progress` as 0–100 and `activeId` — the component has no opinion on where they come from",
|
|
31
|
+
"handling `onSelect` yourself: it reports the choice and does not scroll anywhere"
|
|
32
|
+
],
|
|
33
|
+
"notes": [
|
|
34
|
+
"Deliberately not `role=\"menu\"` — see the stylesheet header. It is a disclosure with a list inside it, not a menu with roving focus.",
|
|
35
|
+
"Not a focus trap: it is the minimum a disclosure needs to not trap the page once opened."
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"antiPatterns": [
|
|
39
|
+
{
|
|
40
|
+
"dont": "expecting it to scroll the page when a section is chosen",
|
|
41
|
+
"why": "it reports the choice, matching Menu — what \"select this section\" means is the page's decision"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"dont": "deriving `progress` from a scroll listener without throttling",
|
|
45
|
+
"why": "this re-renders the rail on every scroll frame"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
7
48
|
"stylesheet": "src/styles/scroll-progress.css",
|
|
8
49
|
"tokens": [
|
|
9
50
|
"--border-default",
|
package/dist/meta/TabItem.json
CHANGED
|
@@ -3,6 +3,48 @@
|
|
|
3
3
|
"source": "../../node_modules/.pnpm/react-stately@3.48.0_react@19.2.8/node_modules/react-stately/dist/types/src/collections/Item.d.ts",
|
|
4
4
|
"propsType": null,
|
|
5
5
|
"import": "import { TabItem } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "One tab and its panel. Re-exported from react-stately's collection Item — `title` is the tab label, children are the panel.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"a child of Tabs — it is not usable anywhere else"
|
|
10
|
+
],
|
|
11
|
+
"useInstead": [
|
|
12
|
+
{
|
|
13
|
+
"when": "it is a row in a dropdown list",
|
|
14
|
+
"use": "MenuItem"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"when": "it is a navigation destination",
|
|
18
|
+
"use": "NavItem"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"composition": {
|
|
22
|
+
"order": [
|
|
23
|
+
"Tabs",
|
|
24
|
+
"TabItem"
|
|
25
|
+
],
|
|
26
|
+
"note": "A collection item, not a rendered component: Tabs reads it to build the collection, so it never appears in the DOM itself. That is why it exposes no props of its own here — `key` and `title` are the collection's API.",
|
|
27
|
+
"example": "<TabItem key=\"billing\" title=\"Billing\">Billing panel content</TabItem>"
|
|
28
|
+
},
|
|
29
|
+
"a11y": {
|
|
30
|
+
"guarantees": [
|
|
31
|
+
"the tab/panel roles, ids and aria-controls pairing are supplied by Tabs, not by this element"
|
|
32
|
+
],
|
|
33
|
+
"requires": [
|
|
34
|
+
"a stable `key` — it identifies the tab across renders and is what selection is expressed in",
|
|
35
|
+
"a `title` that reads as a label on its own"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"antiPatterns": [
|
|
39
|
+
{
|
|
40
|
+
"dont": "wrapping TabItem in a <div> or a fragment inside Tabs",
|
|
41
|
+
"why": "Tabs reads the collection from its direct children; a wrapper makes the item invisible to it"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"dont": "conditionally rendering TabItems with array indexes as keys",
|
|
45
|
+
"why": "selection is tracked by key, so the selected tab silently becomes a different tab"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
6
48
|
"stylesheet": null,
|
|
7
49
|
"tokens": [],
|
|
8
50
|
"props": {},
|
package/dist/meta/TableBody.json
CHANGED
|
@@ -3,6 +3,42 @@
|
|
|
3
3
|
"source": "src/components/Table.tsx",
|
|
4
4
|
"propsType": null,
|
|
5
5
|
"import": "import { TableBody } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "The <tbody> section. Takes no props — it places the data rows and gives their cells body context.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"every Table — the data rows go here"
|
|
10
|
+
],
|
|
11
|
+
"useInstead": [
|
|
12
|
+
{
|
|
13
|
+
"when": "the row names the columns",
|
|
14
|
+
"use": "TableHead"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"composition": {
|
|
18
|
+
"order": [
|
|
19
|
+
"Table",
|
|
20
|
+
"TableBody",
|
|
21
|
+
"TableRow",
|
|
22
|
+
"TableCell"
|
|
23
|
+
],
|
|
24
|
+
"note": "Zebra striping is read from each row's position inside this section rather than a prop repeated on every TableRow.",
|
|
25
|
+
"example": "<TableBody><TableRow><TableCell>Ada</TableCell></TableRow></TableBody>"
|
|
26
|
+
},
|
|
27
|
+
"a11y": {
|
|
28
|
+
"guarantees": [
|
|
29
|
+
"cells inside it default to <td>, and `scope` is inferred as `row` when a cell is marked `header`"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"antiPatterns": [
|
|
33
|
+
{
|
|
34
|
+
"dont": "rendering rows directly under Table with no TableBody",
|
|
35
|
+
"why": "the browser inserts a tbody anyway, but the head-or-body context does not exist, so cells cannot infer their scope"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"dont": "an empty TableBody with no empty state",
|
|
39
|
+
"why": "an empty table looks like a broken one — Table has no empty state of its own, so it is the caller's"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
6
42
|
"stylesheet": "src/styles/table.css",
|
|
7
43
|
"tokens": [
|
|
8
44
|
"--border-default",
|
package/dist/meta/TableCell.json
CHANGED
|
@@ -4,6 +4,89 @@
|
|
|
4
4
|
"propsType": "TableCellProps",
|
|
5
5
|
"description": "One component covers Figma's `Table Cell` + `Cell Text`: the two are never\nused apart in the design (`Table Cell` always wraps exactly one `Cell\nText`), so splitting them into two exported components would only add API\nsurface for a composition nothing ever varies independently.\n\n`header` decides `<th>` vs `<td>` directly rather than a `type` prop that\ncould disagree with where the cell actually sits — a `<th>` rendered inside\n`<tbody>` is still a header cell to the browser and to CSS either way.",
|
|
6
6
|
"import": "import { TableCell } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "One cell, covering Figma's Table Cell and Cell Text together. `header` decides <th> versus <td>.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"every cell in a Table, header or data"
|
|
11
|
+
],
|
|
12
|
+
"useInstead": [
|
|
13
|
+
{
|
|
14
|
+
"when": "the content is a status label",
|
|
15
|
+
"use": "Badge",
|
|
16
|
+
"why": "put it inside the cell — the cell is the container, not the label"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"variants": {
|
|
20
|
+
"scope": {
|
|
21
|
+
"col": {
|
|
22
|
+
"use": "a column header — inferred inside TableHead"
|
|
23
|
+
},
|
|
24
|
+
"row": {
|
|
25
|
+
"use": "a row header, e.g. the name column — inferred for a `header` cell in TableBody"
|
|
26
|
+
},
|
|
27
|
+
"colgroup": {
|
|
28
|
+
"use": "a header spanning several columns, with colspan"
|
|
29
|
+
},
|
|
30
|
+
"rowgroup": {
|
|
31
|
+
"use": "a header spanning several rows, with rowspan"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"align": {
|
|
35
|
+
"leading": {
|
|
36
|
+
"use": "the default — text, names, identifiers"
|
|
37
|
+
},
|
|
38
|
+
"trailing": {
|
|
39
|
+
"use": "numbers, currency and dates, so digits line up column-wise"
|
|
40
|
+
},
|
|
41
|
+
"center": {
|
|
42
|
+
"use": "icons, short status marks, checkboxes"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"variant": {
|
|
46
|
+
"default": {
|
|
47
|
+
"use": "the default — body text and icon colours"
|
|
48
|
+
},
|
|
49
|
+
"link": {
|
|
50
|
+
"use": "recolours the content to `text/link` / `icon/primary` for a cell whose content navigates"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"slots": {
|
|
55
|
+
"icon": {
|
|
56
|
+
"accepts": "Icon",
|
|
57
|
+
"note": "leading icon; omit its `label`"
|
|
58
|
+
},
|
|
59
|
+
"trailingIcon": {
|
|
60
|
+
"accepts": "Icon",
|
|
61
|
+
"note": "trailing icon; same rule"
|
|
62
|
+
},
|
|
63
|
+
"children": {
|
|
64
|
+
"accepts": "any"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"a11y": {
|
|
68
|
+
"guarantees": [
|
|
69
|
+
"`scope` is inferred when omitted — `col` in TableHead, `row` in TableBody — and can be set explicitly when the inference is wrong",
|
|
70
|
+
"`header` decides the element directly rather than a `type` prop that could disagree with where the cell sits"
|
|
71
|
+
],
|
|
72
|
+
"requires": [
|
|
73
|
+
"`scope=\"col\"` on header cells — inferred inside TableHead, but explicit for a header rendered outside it"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"antiPatterns": [
|
|
77
|
+
{
|
|
78
|
+
"dont": "`variant=\"link\"` on a cell with no link in it",
|
|
79
|
+
"why": "link-coloured text that does nothing is the clearest possible false affordance"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"dont": "leading-aligning a numeric column",
|
|
83
|
+
"why": "digits stop lining up, so the column can no longer be scanned for magnitude"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"dont": "`showDivider` on every cell",
|
|
87
|
+
"why": "it is a column rule, not a row rule — on every cell it becomes a grid"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
7
90
|
"stylesheet": "src/styles/table.css",
|
|
8
91
|
"tokens": [
|
|
9
92
|
"--border-default",
|
package/dist/meta/TableHead.json
CHANGED
|
@@ -3,6 +3,45 @@
|
|
|
3
3
|
"source": "src/components/Table.tsx",
|
|
4
4
|
"propsType": null,
|
|
5
5
|
"import": "import { TableHead } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "The <thead> section. Takes no props — it places the header row and gives its cells their head-or-body context.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"every Table with column headers, which is every data table"
|
|
10
|
+
],
|
|
11
|
+
"useInstead": [
|
|
12
|
+
{
|
|
13
|
+
"when": "the rows are data",
|
|
14
|
+
"use": "TableBody"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"composition": {
|
|
18
|
+
"order": [
|
|
19
|
+
"Table",
|
|
20
|
+
"TableHead",
|
|
21
|
+
"TableRow",
|
|
22
|
+
"TableCell"
|
|
23
|
+
],
|
|
24
|
+
"note": "Exists so TableRow and TableCell can tell head from body through context, rather than the caller repeating a prop that has to stay in sync with where the row actually sits.",
|
|
25
|
+
"example": "<TableHead><TableRow><TableCell scope=\"col\">Name</TableCell></TableRow></TableHead>"
|
|
26
|
+
},
|
|
27
|
+
"a11y": {
|
|
28
|
+
"guarantees": [
|
|
29
|
+
"cells inside it default to <th scope=\"col\">, and a row-selection cell becomes the select-all header"
|
|
30
|
+
],
|
|
31
|
+
"requires": [
|
|
32
|
+
"a header row for every data table — without one, no data cell has a column to be associated with"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"antiPatterns": [
|
|
36
|
+
{
|
|
37
|
+
"dont": "a data table with no TableHead",
|
|
38
|
+
"why": "a screen-reader user hears values with no field names"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"dont": "styling a first TableBody row to look like a header",
|
|
42
|
+
"why": "it looks like a header and announces as data"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
6
45
|
"stylesheet": "src/styles/table.css",
|
|
7
46
|
"tokens": [
|
|
8
47
|
"--border-default",
|
package/dist/meta/TableRow.json
CHANGED
|
@@ -4,6 +4,51 @@
|
|
|
4
4
|
"propsType": "TableRowProps",
|
|
5
5
|
"description": "A plain `<tr>`. Hover is CSS-only (`:hover` plus a `data-hovered` escape\nhatch, matching the rest of the system) rather than React Aria's\n`useHover`: a row is not itself an interactive element — nothing about it\ntakes focus or fires a click — so there is no keyboard-vs-pointer\ndistinction to track. A clickable row is a link or button inside a cell,\nthe same accessible pattern Menu and Table Cell's own link variant use;\nnesting an interactive role on `<tr>` itself is not valid HTML.",
|
|
6
6
|
"import": "import { TableRow } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A plain <tr>. Hover is CSS-only; a row is not itself interactive.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"every row, in either TableHead or TableBody"
|
|
11
|
+
],
|
|
12
|
+
"useInstead": [
|
|
13
|
+
{
|
|
14
|
+
"when": "the whole row should be clickable",
|
|
15
|
+
"use": "Link",
|
|
16
|
+
"why": "put the link or Button in a cell — nesting an interactive role on <tr> is not valid HTML"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"slots": {
|
|
20
|
+
"selection": {
|
|
21
|
+
"accepts": "Checkbox props",
|
|
22
|
+
"note": "renders a leading Checkbox cell — a <th scope=\"col\"> select-all inside TableHead, a <td> inside TableBody. Takes the checkbox's own props rather than a boolean, since a selectable row needs `checked`/`onChange` wiring"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"a11y": {
|
|
26
|
+
"guarantees": [
|
|
27
|
+
"hover comes from CSS `:hover` plus a `data-hovered` escape hatch, not React Aria — a row takes no focus and fires no click, so there is no keyboard-versus-pointer distinction to track",
|
|
28
|
+
"the selection cell's element follows the section it sits in"
|
|
29
|
+
],
|
|
30
|
+
"requires": [
|
|
31
|
+
"every row-selection Checkbox to be labelled — those boxes have no visible label by design, so see Checkbox's own contract",
|
|
32
|
+
"`isSelected` alongside a checked selection checkbox, so the row's state is not carried by colour alone"
|
|
33
|
+
],
|
|
34
|
+
"notes": [
|
|
35
|
+
"TableRow itself is never named — a <tr> is not an interactive element and takes no accessible name. The naming requirement above belongs to the Checkbox inside it."
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"antiPatterns": [
|
|
39
|
+
{
|
|
40
|
+
"dont": "`onClick` on TableRow",
|
|
41
|
+
"why": "it takes no focus, so a keyboard user can never fire it"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"dont": "`selection={{ checked }}` with no `onChange`",
|
|
45
|
+
"why": "a checkbox that cannot be changed reads as broken rather than as disabled"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"dont": "`isSelected` without a selection checkbox",
|
|
49
|
+
"why": "the row is tinted with nothing announcing why"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
7
52
|
"stylesheet": "src/styles/table.css",
|
|
8
53
|
"tokens": [
|
|
9
54
|
"--border-default",
|
package/dist/meta/Tabs.json
CHANGED
|
@@ -3,6 +3,91 @@
|
|
|
3
3
|
"source": "src/components/Tabs.tsx",
|
|
4
4
|
"propsType": "TabsProps",
|
|
5
5
|
"import": "import { Tabs } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "A tab list and its panels, built on react-aria's tab collection. Children are TabItem.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"one region of the page shows one of several peer views, and only one at a time",
|
|
10
|
+
"the views are peers — no ordering, no progression between them"
|
|
11
|
+
],
|
|
12
|
+
"useInstead": [
|
|
13
|
+
{
|
|
14
|
+
"when": "the steps must be completed in order",
|
|
15
|
+
"use": "Button",
|
|
16
|
+
"why": "tabs imply peers you may visit in any order; a wizard is a sequence"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"when": "the sections should all be readable at once, or printable",
|
|
20
|
+
"use": "Divider",
|
|
21
|
+
"why": "tabs hide everything but the active panel, including from Ctrl-F"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"when": "it is navigation between pages rather than panels within one",
|
|
25
|
+
"use": "NavItem"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"composition": {
|
|
29
|
+
"order": [
|
|
30
|
+
"Tabs",
|
|
31
|
+
"TabItem"
|
|
32
|
+
],
|
|
33
|
+
"note": "TabItem comes from react-stately's collection Item — it takes no props of its own beyond `title` (the tab label) and `key`; its children are the panel.",
|
|
34
|
+
"example": "<Tabs aria-label=\"Account\"><TabItem key=\"profile\" title=\"Profile\">…</TabItem><TabItem key=\"billing\" title=\"Billing\">…</TabItem></Tabs>"
|
|
35
|
+
},
|
|
36
|
+
"variants": {
|
|
37
|
+
"type": {
|
|
38
|
+
"pill": {
|
|
39
|
+
"use": "the default — a filled track, for switching views inside a panel or card"
|
|
40
|
+
},
|
|
41
|
+
"underline": {
|
|
42
|
+
"use": "a rule under the active tab, for page-level sections"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"size": {
|
|
46
|
+
"sm": {
|
|
47
|
+
"use": "dense panels and toolbars"
|
|
48
|
+
},
|
|
49
|
+
"md": {
|
|
50
|
+
"use": "the default"
|
|
51
|
+
},
|
|
52
|
+
"lg": {
|
|
53
|
+
"use": "page-level section switching"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"orientation": {
|
|
57
|
+
"horizontal": {
|
|
58
|
+
"use": "the default"
|
|
59
|
+
},
|
|
60
|
+
"vertical": {
|
|
61
|
+
"use": "a stacked track with up/down arrow keys",
|
|
62
|
+
"caveat": "keyboard and ARIA are complete, but the decoration is not — the underline rule and the pill track's padding are still written for the horizontal axis. Figma has not specified the vertical drawing"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"a11y": {
|
|
67
|
+
"role": "tablist / tab / tabpanel",
|
|
68
|
+
"guarantees": [
|
|
69
|
+
"roving tabindex, arrow-key navigation and the aria-controls/aria-labelledby pairing between each tab and its panel come from react-aria",
|
|
70
|
+
"the focus ring shows for keyboard focus only, and hover does not latch on touch"
|
|
71
|
+
],
|
|
72
|
+
"requires": [
|
|
73
|
+
"`aria-label` or `aria-labelledby` on Tabs — the tab list is a named region",
|
|
74
|
+
"a stable `key` on every TabItem"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"antiPatterns": [
|
|
78
|
+
{
|
|
79
|
+
"dont": "tabs whose panels the user must read in order",
|
|
80
|
+
"why": "the role announces peers, and nothing stops a user landing on the last one first"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"dont": "hiding required form fields behind a tab",
|
|
84
|
+
"why": "a validation error in an inactive panel is invisible; the user is told the form is invalid with nothing on screen to fix"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"dont": "one tab",
|
|
88
|
+
"why": "a tablist with a single tab is chrome with no choice in it"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
6
91
|
"stylesheet": "src/styles/tabs.css",
|
|
7
92
|
"tokens": [
|
|
8
93
|
"--border-default",
|
package/dist/meta/Toast.json
CHANGED
|
@@ -4,6 +4,87 @@
|
|
|
4
4
|
"propsType": "ToastProps",
|
|
5
5
|
"description": "Toast — Figma `Toast` (820:1655).\n\nNeutral chrome on `surface/raised`; the intent is carried by the icon alone.\nA tinted panel floating over unknown content competes with whatever is\nbehind it, and makes Toast and Alert indistinguishable at a glance.\n\nAUTO-DISMISS PAUSES ON HOVER AND FOCUS. A toast that keeps counting down\nwhile being read or while its action has keyboard focus takes the action\naway mid-reach — WCAG 2.2.1 asks for exactly this. The timer restarts rather\nthan resumes, which is the forgiving direction.",
|
|
6
6
|
"import": "import { Toast } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A transient notification on neutral chrome. Intent is carried by the icon, not by a tinted panel.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"confirming that something the user just did succeeded or failed",
|
|
11
|
+
"the message is short, transient, and not required to complete a task"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the message must stay until the condition is resolved",
|
|
16
|
+
"use": "Alert",
|
|
17
|
+
"why": "a toast disappears, and anything the user must act on should not"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"when": "the message belongs to one field",
|
|
21
|
+
"use": "Input",
|
|
22
|
+
"why": "Input's error text sits with the field it is about"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"when": "you need a decision from the user",
|
|
26
|
+
"use": "Modal",
|
|
27
|
+
"why": "a toast offers one action and takes it away on a timer"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"variants": {
|
|
31
|
+
"intent": {
|
|
32
|
+
"neutral": {
|
|
33
|
+
"use": "a plain fact with no status attached"
|
|
34
|
+
},
|
|
35
|
+
"primary": {
|
|
36
|
+
"use": "brand-flavoured confirmations"
|
|
37
|
+
},
|
|
38
|
+
"success": {
|
|
39
|
+
"use": "the action completed"
|
|
40
|
+
},
|
|
41
|
+
"warning": {
|
|
42
|
+
"use": "it completed with a caveat worth knowing"
|
|
43
|
+
},
|
|
44
|
+
"error": {
|
|
45
|
+
"use": "it failed — pair with a way to retry"
|
|
46
|
+
},
|
|
47
|
+
"information": {
|
|
48
|
+
"use": "the default — a neutral update"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"slots": {
|
|
53
|
+
"title": {
|
|
54
|
+
"accepts": "text"
|
|
55
|
+
},
|
|
56
|
+
"message": {
|
|
57
|
+
"accepts": "text"
|
|
58
|
+
},
|
|
59
|
+
"action": {
|
|
60
|
+
"accepts": "{ label, onPress }",
|
|
61
|
+
"note": "exactly one action. A second choice belongs in a Modal, which will still be there"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"a11y": {
|
|
65
|
+
"guarantees": [
|
|
66
|
+
"auto-dismiss pauses on hover and on focus, and restarts rather than resumes — WCAG 2.2.1, and the forgiving direction",
|
|
67
|
+
"announcement comes from ToastProvider's live region, which is always present rather than appearing with its content"
|
|
68
|
+
],
|
|
69
|
+
"requires": [
|
|
70
|
+
"rendering inside a ToastProvider — a Toast on its own is not in a live region",
|
|
71
|
+
"`duration={null}` when the message carries an action the user genuinely needs"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"antiPatterns": [
|
|
75
|
+
{
|
|
76
|
+
"dont": "a toast for an error the user must fix",
|
|
77
|
+
"why": "it vanishes; the fix does not"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"dont": "two actions in one toast",
|
|
81
|
+
"why": "a timed choice is not a choice"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"dont": "stacking several toasts for one operation",
|
|
85
|
+
"why": "the stack covers the page and the last one is the only one read"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
7
88
|
"stylesheet": "src/styles/toast.css",
|
|
8
89
|
"tokens": [
|
|
9
90
|
"--border-subtle",
|
|
@@ -4,6 +4,63 @@
|
|
|
4
4
|
"propsType": "ToastProviderProps",
|
|
5
5
|
"description": "Renders the queue and supplies `useToast`.\n\nThe live region is the CONTAINER, declared once and always present. A region\nthat appears at the same moment as its content is not reliably announced —\nassistive tech has to be watching the node before the text lands in it.",
|
|
6
6
|
"import": "import { ToastProvider } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Renders the toast queue and supplies the `useToast` hook. Mount once, near the app root.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"the app shows toasts anywhere — this is the only way to get `useToast`"
|
|
11
|
+
],
|
|
12
|
+
"useInstead": [
|
|
13
|
+
{
|
|
14
|
+
"when": "you only need one persistent in-page message",
|
|
15
|
+
"use": "Alert",
|
|
16
|
+
"why": "no provider, no queue, and it stays put"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"variants": {
|
|
20
|
+
"placement": {
|
|
21
|
+
"top-left": {
|
|
22
|
+
"use": "rare — for RTL layouts or where the top-right is occupied"
|
|
23
|
+
},
|
|
24
|
+
"top-right": {
|
|
25
|
+
"use": "the common desktop choice, out of the way of primary content"
|
|
26
|
+
},
|
|
27
|
+
"bottom-left": {
|
|
28
|
+
"use": "keeps toasts clear of a right-hand panel"
|
|
29
|
+
},
|
|
30
|
+
"bottom-right": {
|
|
31
|
+
"use": "close to where a save or submit action usually sits"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"slots": {
|
|
36
|
+
"children": {
|
|
37
|
+
"accepts": "any",
|
|
38
|
+
"note": "your app — the provider wraps it"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"a11y": {
|
|
42
|
+
"guarantees": [
|
|
43
|
+
"the live region is the container, declared once and always present — a region that appears at the same moment as its content is not reliably announced",
|
|
44
|
+
"`limit` drops the oldest toasts, so the stack can never cover the page"
|
|
45
|
+
],
|
|
46
|
+
"requires": [
|
|
47
|
+
"mounting exactly one ToastProvider — a second live region competes with the first"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"antiPatterns": [
|
|
51
|
+
{
|
|
52
|
+
"dont": "mounting a ToastProvider per route or per page",
|
|
53
|
+
"why": "toasts queued before the switch are unmounted mid-announcement, and two live regions announce over each other"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"dont": "placing it inside a Modal",
|
|
57
|
+
"why": "the toasts are removed from the tree when the modal closes, and hidden from assistive tech while it is open"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"dont": "raising `limit` to keep everything",
|
|
61
|
+
"why": "an unbounded stack covers the content the toasts are about"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
7
64
|
"stylesheet": "src/styles/toast.css",
|
|
8
65
|
"tokens": [
|
|
9
66
|
"--border-subtle",
|