ionbase-ui 0.18.1 → 0.20.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/Avatar.json +66 -0
- package/dist/meta/AvatarGroup.json +57 -0
- package/dist/meta/Badge.json +65 -0
- 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 +1859 -76
- package/dist/meta/index.json +88 -59
- package/llms.txt +27 -0
- package/package.json +7 -4
package/dist/meta/Avatar.json
CHANGED
|
@@ -4,6 +4,72 @@
|
|
|
4
4
|
"propsType": "AvatarProps",
|
|
5
5
|
"description": "Avatar picks its content the way Figma's `Type` variant does, but by\nprecedence rather than by a prop: image, then initials, then icon. A caller\npassing `src` and `initials` gets the image with the initials as its alt\nfallback, which is what you want when the image 404s.\n\nA failed `src` falls through to initials (then icon) rather than leaving the\nbrowser's broken-image glyph — that is why initials exist alongside `src`.\n\nIt is a `<span>`, not a `<div>`, so it can sit inline beside text without the\ncaller fighting a block element.",
|
|
6
6
|
"import": "import { Avatar } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A person or entity marker. Content is chosen by precedence — image, then initials, then icon — not by a type prop.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"identifying who owns, authored or is assigned to something",
|
|
11
|
+
"a table cell, menu row or header needs a compact identity marker beside a name"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the thing being marked is a status, count or category rather than a person",
|
|
16
|
+
"use": "Badge"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"when": "you only need a decorative graphic with no identity behind it",
|
|
20
|
+
"use": "Icon"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"variants": {
|
|
24
|
+
"size": {
|
|
25
|
+
"mini": {
|
|
26
|
+
"use": "24px — inline beside body text, dense table rows"
|
|
27
|
+
},
|
|
28
|
+
"sm": {
|
|
29
|
+
"use": "32px — menu rows, compact lists"
|
|
30
|
+
},
|
|
31
|
+
"md": {
|
|
32
|
+
"use": "the default, 40px"
|
|
33
|
+
},
|
|
34
|
+
"lg": {
|
|
35
|
+
"use": "48px — profile headers and detail pages"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"shape": {
|
|
39
|
+
"circle": {
|
|
40
|
+
"use": "the default — people"
|
|
41
|
+
},
|
|
42
|
+
"square": {
|
|
43
|
+
"use": "organisations, projects, workspaces and other non-human entities"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"slots": {
|
|
48
|
+
"icon": {
|
|
49
|
+
"accepts": "Icon",
|
|
50
|
+
"note": "the last fallback — rendered only when there is neither `src` nor `initials`"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"a11y": {
|
|
54
|
+
"guarantees": [
|
|
55
|
+
"`alt` falls back to `initials`, so the avatar is never announced as an unlabelled image",
|
|
56
|
+
"a `src` that fails to load falls through to initials, then icon — never the browser's broken-image glyph"
|
|
57
|
+
],
|
|
58
|
+
"requires": [
|
|
59
|
+
"`alt` describing the person, not the picture — \"Ada Lovelace\", not \"profile photo\""
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"antiPatterns": [
|
|
63
|
+
{
|
|
64
|
+
"dont": "passing `src` without `initials`",
|
|
65
|
+
"why": "a 404 leaves the fallback empty; initials are what make the failure invisible"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"dont": "using Avatar as a button",
|
|
69
|
+
"do": "put the Avatar inside a Button or Link",
|
|
70
|
+
"why": "Avatar renders a <span> with no role and takes no focus"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
7
73
|
"stylesheet": "src/styles/avatar.css",
|
|
8
74
|
"tokens": [
|
|
9
75
|
"--border-strong",
|
|
@@ -4,6 +4,63 @@
|
|
|
4
4
|
"propsType": "AvatarGroupProps",
|
|
5
5
|
"description": "AvatarGroup overlaps its children by a quarter of the avatar size, matching\nFigma's -6 / -8 / -10 / -12 gaps at Mini / Small / Medium / Large.\n\nGroup defaults fill in `size` / `shape` only when a child Avatar has not set\nthem itself — an explicit child prop wins. Non-Avatar children are left\nalone so `size`/`shape` are never pushed onto arbitrary DOM nodes.\n\nChildren render in source order because that is Figma's stacking: each avatar\npaints over the one before it, so the `+N` overflow ends up on top. Later\nsiblings paint later, so this needs no z-index.",
|
|
6
6
|
"import": "import { AvatarGroup } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Overlapping Avatars with a `+N` overflow, for showing several people in one row's width.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"several people share one thing — assignees, participants, collaborators",
|
|
11
|
+
"the exact list matters less than the fact that there are several"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "each person needs their own name, role or action visible",
|
|
16
|
+
"use": "Table",
|
|
17
|
+
"why": "an overlapping stack hides everything but the picture"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"variants": {
|
|
21
|
+
"size": {
|
|
22
|
+
"mini": {
|
|
23
|
+
"use": "24px — inline beside body text, dense table rows"
|
|
24
|
+
},
|
|
25
|
+
"sm": {
|
|
26
|
+
"use": "32px — menu rows, compact lists"
|
|
27
|
+
},
|
|
28
|
+
"md": {
|
|
29
|
+
"use": "the default, 40px"
|
|
30
|
+
},
|
|
31
|
+
"lg": {
|
|
32
|
+
"use": "48px — profile headers and detail pages"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"shape": {
|
|
36
|
+
"circle": {
|
|
37
|
+
"use": "the default — people"
|
|
38
|
+
},
|
|
39
|
+
"square": {
|
|
40
|
+
"use": "organisations, projects, workspaces"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"slots": {
|
|
45
|
+
"children": {
|
|
46
|
+
"accepts": "Avatar",
|
|
47
|
+
"note": "group `size`/`shape` fill in only where a child has not set its own; a child's explicit prop wins. Non-Avatar children are left untouched."
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"a11y": {
|
|
51
|
+
"requires": [
|
|
52
|
+
"`alt` on every child Avatar — the overflow avatar is the only one the group names for you"
|
|
53
|
+
],
|
|
54
|
+
"notes": [
|
|
55
|
+
"Children paint in source order, so the `+N` overflow lands on top without any z-index."
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"antiPatterns": [
|
|
59
|
+
{
|
|
60
|
+
"dont": "omitting `max` on an unbounded list",
|
|
61
|
+
"why": "twenty overlapping avatars is not a summary; it is a smear"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
7
64
|
"stylesheet": "src/styles/avatar.css",
|
|
8
65
|
"tokens": [
|
|
9
66
|
"--border-strong",
|
package/dist/meta/Badge.json
CHANGED
|
@@ -4,6 +4,71 @@
|
|
|
4
4
|
"propsType": "BadgeProps",
|
|
5
5
|
"description": "Badge is presentational — no role, no interaction. React Aria has nothing to\noffer here, so this is a plain span rather than a hook wrapper for its own\nsake.\n\nIt carries no `status` or `alert` role on purpose: a badge is a label on\nsomething else, and announcing it as a live region would interrupt screen\nreader users on every render. Wrap it yourself if the value genuinely changes\nand matters.",
|
|
6
6
|
"import": "import { Badge } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A small, non-interactive label that classifies the thing next to it. Presentational — no role, no live region.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"labelling a record's status, type, count or category",
|
|
11
|
+
"the label belongs to something else on the page and is read as part of it"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the message is a standalone statement the user must read",
|
|
16
|
+
"use": "Alert",
|
|
17
|
+
"why": "Badge carries no role and is not announced on its own"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"when": "the label should be clickable — a filter chip, a removable tag",
|
|
21
|
+
"use": "Button",
|
|
22
|
+
"why": "Badge takes no focus and fires nothing"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"variants": {
|
|
26
|
+
"intent": {
|
|
27
|
+
"neutral": {
|
|
28
|
+
"use": "the default — a category with no judgement attached"
|
|
29
|
+
},
|
|
30
|
+
"primary": {
|
|
31
|
+
"use": "brand emphasis: Beta, New, Pro"
|
|
32
|
+
},
|
|
33
|
+
"success": {
|
|
34
|
+
"use": "a terminal good state: Active, Paid, Passed"
|
|
35
|
+
},
|
|
36
|
+
"warning": {
|
|
37
|
+
"use": "needs attention but is not broken: Pending, Expiring"
|
|
38
|
+
},
|
|
39
|
+
"error": {
|
|
40
|
+
"use": "a failed or blocked state: Failed, Overdue, Rejected"
|
|
41
|
+
},
|
|
42
|
+
"information": {
|
|
43
|
+
"use": "a neutral fact worth marking: Draft, Scheduled"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"slots": {
|
|
48
|
+
"icon": {
|
|
49
|
+
"accepts": "Icon",
|
|
50
|
+
"note": "mutually exclusive with `dot` — `dot` wins and `icon` is dropped when both are set"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"a11y": {
|
|
54
|
+
"guarantees": [
|
|
55
|
+
"the dot marker is `aria-hidden`, so the badge announces its text only",
|
|
56
|
+
"no `status` or `alert` role, so a re-render never interrupts a screen reader"
|
|
57
|
+
],
|
|
58
|
+
"notes": [
|
|
59
|
+
"If the value genuinely changes and the change matters, wrap the Badge in your own live region — the component will not do it for you."
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"antiPatterns": [
|
|
63
|
+
{
|
|
64
|
+
"dont": "relying on intent colour alone to carry the meaning",
|
|
65
|
+
"why": "colour is not a label (WCAG 1.4.1); the text has to say Failed, not just be red"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"dont": "putting a Button or Link inside a Badge",
|
|
69
|
+
"why": "it renders a plain <span>; interactive content there has no affordance and no focus style"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
7
72
|
"stylesheet": "src/styles/badge.css",
|
|
8
73
|
"tokens": [
|
|
9
74
|
"--border-default",
|
package/dist/meta/Checkbox.json
CHANGED
|
@@ -4,6 +4,88 @@
|
|
|
4
4
|
"propsType": "CheckboxProps",
|
|
5
5
|
"description": "Checkbox wraps a native `<input type=\"checkbox\">` and hides it.\n\nThe native input is kept rather than replaced by a div with `role=checkbox`\nbecause it brings form association, the indeterminate property, label\nclicking, and correct announcement — none of which are free to reimplement,\nand all of which are easy to get subtly wrong.\n\n`indeterminate` is the reason for the effect below: HTML has no\n`indeterminate` attribute, only a DOM property, so React cannot set it\ndeclaratively and it must be written after every render.",
|
|
6
6
|
"import": "import { Checkbox } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A native checkbox with the system's styling, supporting an indeterminate state.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a single on/off choice inside a form that is submitted",
|
|
11
|
+
"selecting rows in a Table",
|
|
12
|
+
"several independent options where more than one may be picked"
|
|
13
|
+
],
|
|
14
|
+
"useInstead": [
|
|
15
|
+
{
|
|
16
|
+
"when": "the change takes effect immediately rather than on submit",
|
|
17
|
+
"use": "Toggle",
|
|
18
|
+
"why": "a switch reads as a setting taking effect now; a checkbox reads as a value being collected"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"when": "the options are mutually exclusive",
|
|
22
|
+
"use": "RadioGroup"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"when": "there are many options and space is tight",
|
|
26
|
+
"use": "Select"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"variants": {
|
|
30
|
+
"size": {
|
|
31
|
+
"sm": {
|
|
32
|
+
"use": "dense forms and table rows"
|
|
33
|
+
},
|
|
34
|
+
"md": {
|
|
35
|
+
"use": "the default"
|
|
36
|
+
},
|
|
37
|
+
"lg": {
|
|
38
|
+
"use": "touch targets and prominent single choices"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"intent": {
|
|
42
|
+
"brand": {
|
|
43
|
+
"use": "the default"
|
|
44
|
+
},
|
|
45
|
+
"neutral": {
|
|
46
|
+
"use": "where the checked state is a fact rather than an approval — bulk-selection columns"
|
|
47
|
+
},
|
|
48
|
+
"danger": {
|
|
49
|
+
"use": "consenting to something destructive or irreversible"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"slots": {
|
|
54
|
+
"children": {
|
|
55
|
+
"accepts": "text",
|
|
56
|
+
"note": "the visible label; omit for a bare box and supply `aria-label` instead"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"a11y": {
|
|
60
|
+
"role": "checkbox",
|
|
61
|
+
"guarantees": [
|
|
62
|
+
"a real <input type=\"checkbox\">, so form association, label clicking, and correct announcement come from the platform",
|
|
63
|
+
"`isIndeterminate` is written to the DOM property after every render — HTML has no such attribute, so React cannot set it declaratively"
|
|
64
|
+
],
|
|
65
|
+
"requires": [
|
|
66
|
+
"`children`, or `aria-label` when the box has no visible label — a row-selection checkbox always needs one"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"antiPatterns": [
|
|
70
|
+
{
|
|
71
|
+
"dont": "a bare checkbox with no `children` and no `aria-label`",
|
|
72
|
+
"why": "every row announces an unnamed checkbox"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"dont": "`isIndeterminate` on a leaf checkbox",
|
|
76
|
+
"why": "indeterminate means \"some of my children are checked\"; on a leaf it is a state the user can never reach"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"dont": "a div with `role=\"checkbox\"`",
|
|
80
|
+
"why": "form association, the indeterminate property and label clicking are not free to reimplement"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"deprecated": [
|
|
84
|
+
{
|
|
85
|
+
"prop": "disabled",
|
|
86
|
+
"replacement": "isDisabled"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
7
89
|
"stylesheet": "src/styles/checkbox.css",
|
|
8
90
|
"tokens": [
|
|
9
91
|
"--border-disabled",
|
package/dist/meta/Divider.json
CHANGED
|
@@ -4,6 +4,45 @@
|
|
|
4
4
|
"propsType": "DividerProps",
|
|
5
5
|
"description": "A single `<hr>`, matching Figma's `Border` (70:22153).\n\n`<hr>` rather than a styled `<div>`: it is a semantic thematic break, so a\nscreen reader announces it as one, and it needs no `role` to get there.\n`aria-orientation` is set for the vertical case, since a vertical rule\ninside a horizontal toolbar is the one shape a screen reader cannot infer\nfrom the element alone.",
|
|
6
6
|
"import": "import { Divider } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A semantic thematic break, rendered as a real <hr>.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"separating groups of content or controls that are related but distinct",
|
|
11
|
+
"a menu, toolbar or form needs a visible section break"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "you only want space between two things",
|
|
16
|
+
"use": "CSS gap or margin",
|
|
17
|
+
"why": "a rule announces a break to a screen reader; whitespace does not, and usually shouldn't"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"variants": {
|
|
21
|
+
"orientation": {
|
|
22
|
+
"horizontal": {
|
|
23
|
+
"use": "the default — between stacked sections"
|
|
24
|
+
},
|
|
25
|
+
"vertical": {
|
|
26
|
+
"use": "inside a horizontal row: toolbars, button groups, header bars"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"a11y": {
|
|
31
|
+
"guarantees": [
|
|
32
|
+
"`<hr>` is a thematic break to assistive tech without needing a role",
|
|
33
|
+
"`aria-orientation=\"vertical\"` is set for the vertical case — the one thing a screen reader cannot infer from the element"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"antiPatterns": [
|
|
37
|
+
{
|
|
38
|
+
"dont": "a styled <div> for a rule",
|
|
39
|
+
"why": "that is what this component exists to replace"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"dont": "a Divider between every row of a list",
|
|
43
|
+
"why": "a break announced on every row is noise; use the list's own row borders"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
7
46
|
"stylesheet": "src/styles/divider.css",
|
|
8
47
|
"tokens": [
|
|
9
48
|
"--border-strong",
|
package/dist/meta/FullCard.json
CHANGED
|
@@ -4,6 +4,83 @@
|
|
|
4
4
|
"propsType": "FullCardProps",
|
|
5
5
|
"description": "Full Card — the full-bleed case study row from Figma `Full Card` (592:857).\n\nA text column beside a framed media panel, split down the middle, mirrored\nby the `Alignment` variant.\n\nThe split holds from 1080 and stacks below it, media above content in BOTH\nalignments — `Alignment` names a horizontal side and\nstops meaning anything once there is one column, so it does not get to\ndecide the vertical order too. Size is a media query, not a prop, the same\ncall Header makes about Device.\n\nWHY THE `show*` BOOLEANS ARE GONE\n\nFigma carries `Show Eyebrow`, `Show Description` and `Show Actions` beside\nthe slots they gate, because a Figma component instance always holds every\nlayer and needs a switch to hide one. React has no such constraint — an\nabsent prop is the switch. Badge made the same call with `Show Dot`, and\nkeeping both would have let `showActions` and `actions` disagree.\n\n`headline` is required, and it is the only required prop. The heading is\nwhat makes this a section rather than a decorated div, and every other part\nof the card is optional in Figma too.\n\nNo `'use client'`: nothing here is stateful or interactive. The interactive\nparts arrive through `actions`, and they carry their own boundary.",
|
|
6
6
|
"import": "import { FullCard } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A full-bleed row: a text column beside a framed media panel, mirrored by `alignment`.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a marketing or overview section presents one thing with a screenshot beside it",
|
|
11
|
+
"a case-study or feature row on a landing or product page"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "you are listing records with comparable fields",
|
|
16
|
+
"use": "Table"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"when": "it is a short status message rather than a content section",
|
|
20
|
+
"use": "Alert"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"slots": {
|
|
24
|
+
"eyebrow": {
|
|
25
|
+
"accepts": "Badge",
|
|
26
|
+
"note": "any node works"
|
|
27
|
+
},
|
|
28
|
+
"headline": {
|
|
29
|
+
"accepts": "text",
|
|
30
|
+
"note": "required — the heading is what makes this a section rather than a decorated div"
|
|
31
|
+
},
|
|
32
|
+
"description": {
|
|
33
|
+
"accepts": "text"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"accepts": "Button",
|
|
37
|
+
"note": "a secondary Button in the design"
|
|
38
|
+
},
|
|
39
|
+
"media": {
|
|
40
|
+
"accepts": "img or video",
|
|
41
|
+
"note": "rendered inside the framed screen holder — pass the screenshot or embed itself, not the frame"
|
|
42
|
+
},
|
|
43
|
+
"children": {
|
|
44
|
+
"accepts": "any",
|
|
45
|
+
"note": "extra content below the description"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"variants": {
|
|
49
|
+
"alignment": {
|
|
50
|
+
"left": {
|
|
51
|
+
"use": "media on the left, text on the right"
|
|
52
|
+
},
|
|
53
|
+
"right": {
|
|
54
|
+
"use": "media on the right — the variant names the media, not the text"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"a11y": {
|
|
59
|
+
"guarantees": [
|
|
60
|
+
"the headline renders as a real heading, so the card appears in the document outline"
|
|
61
|
+
],
|
|
62
|
+
"requires": [
|
|
63
|
+
"`headingLevel` matching the surrounding outline — `h3` is the default because Figma applies Type/H3, but only the page knows the real level",
|
|
64
|
+
"`alt` on whatever you pass as `media`"
|
|
65
|
+
],
|
|
66
|
+
"notes": [
|
|
67
|
+
"`h1` is deliberately not available: a full card is a section within a page, never the page's own title."
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"antiPatterns": [
|
|
71
|
+
{
|
|
72
|
+
"dont": "looking for `showEyebrow` / `showDescription` / `showActions`",
|
|
73
|
+
"why": "an absent prop is the switch — Figma needs the boolean because an instance always holds every layer; React does not"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"dont": "leaving `headingLevel` at the default inside a section that is already at h3",
|
|
77
|
+
"why": "it produces a skipped or duplicated outline level"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"dont": "passing your own frame or border around `media`",
|
|
81
|
+
"why": "the component already draws the screen holder"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
7
84
|
"stylesheet": "src/styles/full-card.css",
|
|
8
85
|
"tokens": [
|
|
9
86
|
"--border-default",
|
package/dist/meta/Header.json
CHANGED
|
@@ -4,6 +4,65 @@
|
|
|
4
4
|
"propsType": "HeaderProps",
|
|
5
5
|
"description": "Header renders Figma's four Device variants — Desktop, Tablet,\nMobile-Closed, Mobile-Open — from one DOM tree.\n\nTWO AXES, AND THEY ARE NOT THE SAME KIND OF THING\n\nFigma spells Device as a single four-way variant, but it is really two: a\n*breakpoint* (Desktop / Tablet / Mobile) and a *state* (Closed / Open). They\nare modelled differently here because they are known by different people.\n\n Breakpoint is a media query. It is the one variant axis in the system the\n browser already knows the answer to — a header is Mobile because the\n viewport is narrow — and having React duplicate that judgement is how the\n two drift apart. Breakpoints match the Breakpoint collection's container\n widths: Tablet below 1216, Mobile below 896.\n\n Open/Closed is a prop, because only the caller knows. It follows the\n controlled/uncontrolled pair the rest of the system uses.\n\nONE TREE, TWO LAYOUTS\n\n`center` and `end` sit inline in the bar on Desktop and Tablet, and inside\nthe dropped Menu-Container on Mobile-Open. They are rendered once, in one\nwrapper, which is `display: contents` above the mobile breakpoint and an\nabsolutely positioned panel below it. Rendering them twice — or moving them\nwith JavaScript — would mean the same nav link exists twice in the\naccessibility tree, and would reset any state a caller put in a slot every\ntime the viewport crossed 896px.\n\nThe centre slot is no longer hidden on Tablet. Figma's Tablet variant ships\nit populated; the previous release collapsed it, which was correct for the\nprevious design and is not for this one.\n\nA `<header>` element with no explicit role: it is a landmark already when it\nis a direct child of body, which is where a page header sits.",
|
|
6
6
|
"import": "import { Header } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "The page header bar. Renders Figma's Desktop, Tablet, Mobile-Closed and Mobile-Open from one DOM tree.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"the app needs a top bar with a brand, navigation and actions",
|
|
11
|
+
"that bar must collapse to a mobile menu below 896px"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "it is a sidebar rather than a top bar",
|
|
16
|
+
"use": "NavItem",
|
|
17
|
+
"why": "Header owns a horizontal bar and its own breakpoint behaviour"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"slots": {
|
|
21
|
+
"brand": {
|
|
22
|
+
"accepts": "LogoMark or Logo",
|
|
23
|
+
"note": "rendered at the start; never shrinks"
|
|
24
|
+
},
|
|
25
|
+
"center": {
|
|
26
|
+
"accepts": "NavItem",
|
|
27
|
+
"note": "inline on Desktop and Tablet; moves into the mobile menu below 896px. Rendered once, so slot state survives crossing the breakpoint"
|
|
28
|
+
},
|
|
29
|
+
"end": {
|
|
30
|
+
"accepts": "Button, Avatar",
|
|
31
|
+
"note": "actions; same placement rules as `center`"
|
|
32
|
+
},
|
|
33
|
+
"children": {
|
|
34
|
+
"accepts": "any",
|
|
35
|
+
"note": "escape hatch — rendered in the bar after the menu wrapper and before the mobile toggle. Prefer the named slots"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"a11y": {
|
|
39
|
+
"role": "banner, from the <header> element when it is a direct child of body",
|
|
40
|
+
"guarantees": [
|
|
41
|
+
"the mobile toggle carries `aria-expanded` and `aria-controls` pointing at the menu container",
|
|
42
|
+
"Escape closes the open mobile menu — and the listener sits on the header, so a header that never opens never listens",
|
|
43
|
+
"the slots are rendered once, so no nav link exists twice in the accessibility tree"
|
|
44
|
+
],
|
|
45
|
+
"requires": [
|
|
46
|
+
"`menuLabel` — the mobile toggle is icon-only and has no other name"
|
|
47
|
+
],
|
|
48
|
+
"notes": [
|
|
49
|
+
"This is a disclosure, not a modal: no focus trap and no scroll lock, so Escape is the whole dismissal contract."
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"antiPatterns": [
|
|
53
|
+
{
|
|
54
|
+
"dont": "rendering a second copy of the nav for mobile",
|
|
55
|
+
"why": "Header already moves the one copy; two copies duplicate every link in the accessibility tree"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"dont": "driving the mobile layout from JS state",
|
|
59
|
+
"why": "the breakpoint is a media query — the browser already knows the answer, and duplicating that judgement is how the two drift"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"dont": "passing `open` without `onOpenChange`",
|
|
63
|
+
"why": "controlled with no handler means the toggle can never change it"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
7
66
|
"stylesheet": "src/styles/header.css",
|
|
8
67
|
"tokens": [
|
|
9
68
|
"--border-default",
|
package/dist/meta/Icon.json
CHANGED
|
@@ -4,6 +4,60 @@
|
|
|
4
4
|
"propsType": "IconProps",
|
|
5
5
|
"description": "Wrapper that applies the design system's icon sizing and accessibility\ndefaults to whatever icon component you hand it.\n\n import { Plus } from 'lucide-react';\n <Icon as={Plus} size=\"sm\" />\n <Icon as={Plus} label=\"Add item\" /> // meaningful, gets an a11y name\n\nTakes the icon as a prop rather than re-exporting a set: a barrel of a\nthousand-plus icons defeats tree-shaking in several bundlers, and pinning one\nicon library would force it on every consumer. You import the one icon you\nneed, from whichever library you use, and it is the only one bundled.",
|
|
6
6
|
"import": "import { Icon } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Applies the system's icon sizing and accessibility defaults to any SVG component you hand it. The package ships no icon set.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"rendering any icon inside this design system, from any library — lucide, heroicons, react-icons, your own SVG"
|
|
11
|
+
],
|
|
12
|
+
"useInstead": [
|
|
13
|
+
{
|
|
14
|
+
"when": "the graphic is a brand mark",
|
|
15
|
+
"use": "Logo or LogoMark"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"when": "you need a coloured status marker with text",
|
|
19
|
+
"use": "Badge"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"a11y": {
|
|
23
|
+
"guarantees": [
|
|
24
|
+
"with `label`, the icon gets `role=\"img\"` and that name",
|
|
25
|
+
"without `label`, it is `aria-hidden` and `focusable=\"false\"` — decoration, never read twice"
|
|
26
|
+
],
|
|
27
|
+
"requires": [
|
|
28
|
+
"`label` when the icon carries meaning on its own — an icon-only button, a standalone status marker"
|
|
29
|
+
],
|
|
30
|
+
"notes": [
|
|
31
|
+
"Omit `label` for an icon beside a visible text label. The text already names it."
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"antiPatterns": [
|
|
35
|
+
{
|
|
36
|
+
"dont": "`<Button startIcon={<Icon as={Plus} label=\"Add\" />}>Add</Button>`",
|
|
37
|
+
"do": "drop the `label`",
|
|
38
|
+
"why": "the button's own text names it; the labelled icon makes it announce twice"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"dont": "hardcoding `width`/`height` in px",
|
|
42
|
+
"do": "a `size` rung, or omit `size` to inherit the surrounding font size",
|
|
43
|
+
"why": "inheriting is what lets an icon sit correctly inside a Button without the Button knowing about it"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"dont": "assuming `size=\"xs\"` is 12px",
|
|
47
|
+
"why": "the ladder shifted in 2026-08: `xs` is 14, and `2xs` is the old 12. This raises no type error"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"sizes": {
|
|
51
|
+
"note": "The rungs map one-to-one onto the Semantics `icon-size/*` tokens. Any CSS length is also accepted for a one-off.",
|
|
52
|
+
"ladder": {
|
|
53
|
+
"2xs": 12,
|
|
54
|
+
"xs": 14,
|
|
55
|
+
"sm": 16,
|
|
56
|
+
"md": 20,
|
|
57
|
+
"lg": 24,
|
|
58
|
+
"xl": 32
|
|
59
|
+
}
|
|
60
|
+
},
|
|
7
61
|
"stylesheet": "src/styles/icon.css",
|
|
8
62
|
"tokens": [],
|
|
9
63
|
"props": {
|
package/dist/meta/Link.json
CHANGED
|
@@ -4,6 +4,73 @@
|
|
|
4
4
|
"propsType": "LinkProps",
|
|
5
5
|
"description": "Link — Figma `Link` (774:1516).\n\nRenders an `<a>` when given an `href` and a `<button>` otherwise, the same\njudgment `NavItem` makes: the element follows what the caller is actually\nbuilding rather than a separate `as` prop. A link that does not navigate is\na button, and shipping one as an anchor breaks middle-click, \"open in new\ntab\" and the screen-reader announcement all at once.\n\nNO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its\ntype from the text around it — `font-size: inherit` in the stylesheet, and\nicons sized in `em` so they scale with it. Figma has to draw its variants at\nsome concrete size (16/24, `type/body`), but that is one sample of an\ninheriting element, not a specification. Do not add a size ladder to match\nthe drawing.",
|
|
6
6
|
"import": "import { Link } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Navigation to somewhere else. Renders an <a> when given `href`, a <button> otherwise.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"the interaction takes the user to a different page, view or resource",
|
|
11
|
+
"the destination should support middle-click, open-in-new-tab and copy-link"
|
|
12
|
+
],
|
|
13
|
+
"useInstead": [
|
|
14
|
+
{
|
|
15
|
+
"when": "the interaction changes something rather than going somewhere",
|
|
16
|
+
"use": "Button",
|
|
17
|
+
"why": "a link that does not navigate breaks middle-click, open-in-new-tab and the screen-reader announcement all at once"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"when": "it is a primary navigation destination in a header or sidebar",
|
|
21
|
+
"use": "NavItem"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"variants": {
|
|
25
|
+
"variant": {
|
|
26
|
+
"inline": {
|
|
27
|
+
"use": "the default — a link inside a paragraph. Underlined in every state, because a link in body copy must not rely on colour alone (WCAG 1.4.1)"
|
|
28
|
+
},
|
|
29
|
+
"standalone": {
|
|
30
|
+
"use": "a link that already reads as interactive from its position — a card footer, an action row. Underlines on hover only"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"slots": {
|
|
35
|
+
"startIcon": {
|
|
36
|
+
"accepts": "Icon",
|
|
37
|
+
"note": "omit Icon's `label` — the link text already names it"
|
|
38
|
+
},
|
|
39
|
+
"endIcon": {
|
|
40
|
+
"accepts": "Icon",
|
|
41
|
+
"note": "the external-link or arrow affordance; same rule about `label`"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"a11y": {
|
|
45
|
+
"role": "link when `href` is set, button when it is not",
|
|
46
|
+
"guarantees": [
|
|
47
|
+
"the rendered element follows what the caller is actually building, so the announcement is always honest",
|
|
48
|
+
"`inline` stays underlined in every state, so it never depends on colour alone"
|
|
49
|
+
],
|
|
50
|
+
"requires": [
|
|
51
|
+
"link text that makes sense read on its own — screen-reader users navigate by a list of links"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"antiPatterns": [
|
|
55
|
+
{
|
|
56
|
+
"dont": "\"click here\" or \"read more\" as the whole link text",
|
|
57
|
+
"why": "out of context in a links list it names nothing"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"dont": "adding a size prop or wrapper to scale it",
|
|
61
|
+
"why": "a link is inline and inherits its type from the text around it, by design — icons are sized in `em` to follow"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"dont": "`target=\"_blank\"` with no warning in the text or endIcon",
|
|
65
|
+
"why": "an unannounced new tab disorients screen-reader and low-vision users"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"deprecated": [
|
|
69
|
+
{
|
|
70
|
+
"prop": "disabled",
|
|
71
|
+
"replacement": "isDisabled"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
7
74
|
"stylesheet": "src/styles/link.css",
|
|
8
75
|
"tokens": [
|
|
9
76
|
"--border-width-thick",
|