ionbase-ui 0.51.0 → 0.59.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/LICENSE +21 -0
- package/dist/components/Accordion.d.ts +54 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +68 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/Breadcrumb.d.ts +45 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +31 -0
- package/dist/components/Breadcrumb.js.map +1 -0
- package/dist/components/Checkbox.d.ts +2 -1
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox.js +4 -2
- package/dist/components/Checkbox.js.map +1 -1
- package/dist/components/Combobox.d.ts +86 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox.js +261 -0
- package/dist/components/Combobox.js.map +1 -0
- package/dist/components/Drawer.d.ts +52 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +66 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/FileUpload.d.ts +79 -0
- package/dist/components/FileUpload.d.ts.map +1 -0
- package/dist/components/FileUpload.js +218 -0
- package/dist/components/FileUpload.js.map +1 -0
- package/dist/components/ProgressBar.d.ts +46 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +52 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/Select.d.ts +9 -2
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/Select.js.map +1 -1
- package/dist/components/Skeleton.d.ts +39 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +50 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/Spinner.d.ts +45 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +44 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Toggle.d.ts +2 -1
- package/dist/components/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle.js +4 -2
- package/dist/components/Toggle.js.map +1 -1
- package/dist/components/index.d.ts +16 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +8 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/resolve-selection.d.ts +36 -0
- package/dist/components/resolve-selection.d.ts.map +1 -0
- package/dist/components/resolve-selection.js +14 -0
- package/dist/components/resolve-selection.js.map +1 -0
- package/dist/figma-descriptions.json +201 -146
- package/dist/figma-map.json +2296 -1684
- package/dist/meta/Accordion.json +123 -0
- package/dist/meta/AccordionItem.json +90 -0
- package/dist/meta/Breadcrumb.json +87 -0
- package/dist/meta/BreadcrumbItem.json +78 -0
- package/dist/meta/Checkbox.json +18 -1
- package/dist/meta/Combobox.json +265 -0
- package/dist/meta/Drawer.json +234 -0
- package/dist/meta/EmptyState.json +1 -1
- package/dist/meta/FileUpload.json +246 -0
- package/dist/meta/PhoneInput.json +39 -39
- package/dist/meta/Popover.json +3 -3
- package/dist/meta/ProgressBar.json +166 -0
- package/dist/meta/Select.json +4 -3
- package/dist/meta/Skeleton.json +120 -0
- package/dist/meta/Spinner.json +123 -0
- package/dist/meta/Toggle.json +18 -1
- package/dist/meta/Tooltip.json +3 -3
- package/dist/meta/components.json +1837 -270
- package/dist/meta/contrast.json +2873 -194
- package/dist/meta/index.json +128 -7
- package/dist/meta/patterns/index.json +1 -1
- package/dist/styles/accordion.css +102 -0
- package/dist/styles/breadcrumb.css +72 -0
- package/dist/styles/combobox.css +154 -0
- package/dist/styles/drawer.css +181 -0
- package/dist/styles/empty-state.css +16 -4
- package/dist/styles/file-upload.css +236 -0
- package/dist/styles/index.css +8 -0
- package/dist/styles/progress-bar.css +107 -0
- package/dist/styles/skeleton.css +76 -0
- package/dist/styles/spinner.css +70 -0
- package/llms.txt +2 -2
- package/package.json +19 -19
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Accordion",
|
|
3
|
+
"source": "src/components/Accordion.tsx",
|
|
4
|
+
"propsType": "AccordionProps",
|
|
5
|
+
"description": "Accordion — sections that collapse, with the heading structure intact.\n\nWHY THE TRIGGER IS A BUTTON INSIDE A HEADING\n\nBoth parts are load-bearing and the obvious implementations drop one of them.\nThe heading is how a screen-reader user navigates a long page — pressing `h`\nmoves between sections, and an accordion built from `div`s removes every one\nof those stops. The button is what makes the section operable by keyboard and\nannounced as expandable; a heading with a click handler is neither.\n\nSo the shape is `<h3><button aria-expanded aria-controls>`, which is the only\narrangement that keeps document structure AND operability.\n\n`headingLevel` is a prop with no safe default beyond 3, because the right\nlevel depends on the document around it and nothing here can see that. Two\naccordions at the wrong level produce a page whose outline is nonsense while\nlooking perfectly fine.\n\nTHE PANEL IS NOT UNMOUNTED\n\nCollapsed sections keep their DOM and are hidden with `hidden`. Unmounting\nwould lose form state in a collapsed section — the classic multi-step-form\nbug where answers vanish when a section is folded away — and would break\nin-page search, which cannot find text that is not there.",
|
|
6
|
+
"import": "import { Accordion } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Sections that collapse, with the document's heading structure intact. Holds the open set and the heading level.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a page has several long sections and most users need one of them",
|
|
11
|
+
"optional or advanced settings should be present but not in the way",
|
|
12
|
+
"an FAQ or a reference list where scanning titles is the primary action"
|
|
13
|
+
],
|
|
14
|
+
"useInstead": [
|
|
15
|
+
{
|
|
16
|
+
"when": "the sections are alternatives and only one is ever relevant",
|
|
17
|
+
"use": "Tabs",
|
|
18
|
+
"why": "tabs say \"pick one\"; an accordion says \"all of these exist, open what you need\""
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"when": "hiding the content would hide something the user must act on",
|
|
22
|
+
"use": "render it directly",
|
|
23
|
+
"why": "a required field inside a collapsed section is a form that cannot be completed without exploration"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"slots": {
|
|
27
|
+
"children": {
|
|
28
|
+
"accepts": "AccordionItem"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"a11y": {
|
|
32
|
+
"requires": [
|
|
33
|
+
"`headingLevel` that fits the surrounding document — the default of 3 is a guess this component cannot verify"
|
|
34
|
+
],
|
|
35
|
+
"notes": [
|
|
36
|
+
"each section's trigger is a button inside a heading, which is the only arrangement that keeps both document structure and operability",
|
|
37
|
+
"collapsed panels are hidden rather than unmounted, so form state survives and in-page search still finds the text"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"antiPatterns": [
|
|
41
|
+
{
|
|
42
|
+
"dont": "allowsMultiple on a long FAQ",
|
|
43
|
+
"do": "leave it off, so opening one section closes the last",
|
|
44
|
+
"why": "with every section openable at once the page has no maximum height, and a user who opens six is scrolling a wall of text looking for the one they wanted. Reach for it when sections are settings or reference material a user may need to compare side by side"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"dont": "headingLevel left at 3 inside a section already under an h4",
|
|
48
|
+
"do": "headingLevel={5}",
|
|
49
|
+
"why": "the page looks correct and its outline is nonsense — a heading-level jump is invisible to sighted users and disorienting to everyone navigating by headings"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"dont": "an accordion holding a single section",
|
|
53
|
+
"do": "a heading and the content",
|
|
54
|
+
"why": "one collapsible section is a control that exists to hide the only thing on screen"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"stylesheet": "src/styles/accordion.css",
|
|
58
|
+
"tokens": [
|
|
59
|
+
"--border-focus",
|
|
60
|
+
"--border-subtle",
|
|
61
|
+
"--border-width-default",
|
|
62
|
+
"--border-width-thick",
|
|
63
|
+
"--font-family-sans",
|
|
64
|
+
"--font-weight-medium",
|
|
65
|
+
"--icon-disabled",
|
|
66
|
+
"--icon-secondary",
|
|
67
|
+
"--icon-size-md",
|
|
68
|
+
"--ion-duration-base",
|
|
69
|
+
"--ion-ease-out",
|
|
70
|
+
"--radius-xs",
|
|
71
|
+
"--spacing-12",
|
|
72
|
+
"--spacing-16",
|
|
73
|
+
"--spacing-4",
|
|
74
|
+
"--text-default",
|
|
75
|
+
"--text-disabled",
|
|
76
|
+
"--text-primary",
|
|
77
|
+
"--text-secondary",
|
|
78
|
+
"--type-body",
|
|
79
|
+
"--type-body-line-height"
|
|
80
|
+
],
|
|
81
|
+
"props": {
|
|
82
|
+
"allowsMultiple": {
|
|
83
|
+
"type": "boolean | undefined",
|
|
84
|
+
"required": false,
|
|
85
|
+
"origin": "own",
|
|
86
|
+
"description": "Let more than one section be open at once."
|
|
87
|
+
},
|
|
88
|
+
"defaultExpandedKeys": {
|
|
89
|
+
"type": "string[] | undefined",
|
|
90
|
+
"required": false,
|
|
91
|
+
"origin": "own",
|
|
92
|
+
"description": "Uncontrolled: which sections start open."
|
|
93
|
+
},
|
|
94
|
+
"expandedKeys": {
|
|
95
|
+
"type": "string[] | undefined",
|
|
96
|
+
"required": false,
|
|
97
|
+
"origin": "own",
|
|
98
|
+
"description": "Controlled: which sections are open. Pass `onExpandedChange` with it."
|
|
99
|
+
},
|
|
100
|
+
"onExpandedChange": {
|
|
101
|
+
"type": "((keys: string[]) => void) | undefined",
|
|
102
|
+
"required": false,
|
|
103
|
+
"origin": "own"
|
|
104
|
+
},
|
|
105
|
+
"headingLevel": {
|
|
106
|
+
"type": "AccordionHeadingLevel | undefined",
|
|
107
|
+
"required": false,
|
|
108
|
+
"origin": "own",
|
|
109
|
+
"description": "The level each section's heading renders at. Must fit the surrounding\ndocument — a panel inside an `h2` section wants `3`."
|
|
110
|
+
},
|
|
111
|
+
"children": {
|
|
112
|
+
"type": "React.ReactNode",
|
|
113
|
+
"required": false,
|
|
114
|
+
"origin": "own"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"propCounts": {
|
|
118
|
+
"own": 6,
|
|
119
|
+
"aria": 0,
|
|
120
|
+
"dom": 276,
|
|
121
|
+
"other": 0
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "AccordionItem",
|
|
3
|
+
"source": "src/components/Accordion.tsx",
|
|
4
|
+
"propsType": "AccordionItemProps",
|
|
5
|
+
"import": "import { AccordionItem } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "One collapsible section: an always-visible title that toggles a region beneath it.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"inside an Accordion, once per section"
|
|
10
|
+
],
|
|
11
|
+
"slots": {
|
|
12
|
+
"title": {
|
|
13
|
+
"accepts": "text",
|
|
14
|
+
"note": "always visible, so it must say what is inside — \"Advanced\" tells the user nothing about whether to open it"
|
|
15
|
+
},
|
|
16
|
+
"children": {
|
|
17
|
+
"accepts": "any content"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"a11y": {
|
|
21
|
+
"requires": [
|
|
22
|
+
"a stable `id`, which is the section's identity in the open set — an index breaks the moment the list reorders"
|
|
23
|
+
],
|
|
24
|
+
"notes": [
|
|
25
|
+
"the trigger carries aria-expanded and aria-controls; the panel is a region labelled by the trigger",
|
|
26
|
+
"the panel keeps its DOM when collapsed, so a half-filled form inside one does not lose its answers"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"antiPatterns": [
|
|
30
|
+
{
|
|
31
|
+
"dont": "a required form field inside a collapsed section",
|
|
32
|
+
"do": "keep required fields outside, or expand the section when validation fails",
|
|
33
|
+
"why": "a user cannot fix an error they cannot see, and the browser cannot focus a hidden invalid field"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"stylesheet": "src/styles/accordion.css",
|
|
37
|
+
"tokens": [
|
|
38
|
+
"--border-focus",
|
|
39
|
+
"--border-subtle",
|
|
40
|
+
"--border-width-default",
|
|
41
|
+
"--border-width-thick",
|
|
42
|
+
"--font-family-sans",
|
|
43
|
+
"--font-weight-medium",
|
|
44
|
+
"--icon-disabled",
|
|
45
|
+
"--icon-secondary",
|
|
46
|
+
"--icon-size-md",
|
|
47
|
+
"--ion-duration-base",
|
|
48
|
+
"--ion-ease-out",
|
|
49
|
+
"--radius-xs",
|
|
50
|
+
"--spacing-12",
|
|
51
|
+
"--spacing-16",
|
|
52
|
+
"--spacing-4",
|
|
53
|
+
"--text-default",
|
|
54
|
+
"--text-disabled",
|
|
55
|
+
"--text-primary",
|
|
56
|
+
"--text-secondary",
|
|
57
|
+
"--type-body",
|
|
58
|
+
"--type-body-line-height"
|
|
59
|
+
],
|
|
60
|
+
"props": {
|
|
61
|
+
"id": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"required": true,
|
|
64
|
+
"origin": "own",
|
|
65
|
+
"description": "Stable identity for the open set. Required."
|
|
66
|
+
},
|
|
67
|
+
"title": {
|
|
68
|
+
"type": "React.ReactNode",
|
|
69
|
+
"required": true,
|
|
70
|
+
"origin": "own",
|
|
71
|
+
"description": "The always-visible label."
|
|
72
|
+
},
|
|
73
|
+
"isDisabled": {
|
|
74
|
+
"type": "boolean | undefined",
|
|
75
|
+
"required": false,
|
|
76
|
+
"origin": "own"
|
|
77
|
+
},
|
|
78
|
+
"children": {
|
|
79
|
+
"type": "React.ReactNode",
|
|
80
|
+
"required": false,
|
|
81
|
+
"origin": "own"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"propCounts": {
|
|
85
|
+
"own": 4,
|
|
86
|
+
"aria": 0,
|
|
87
|
+
"dom": 275,
|
|
88
|
+
"other": 0
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Breadcrumb",
|
|
3
|
+
"source": "src/components/Breadcrumb.tsx",
|
|
4
|
+
"propsType": "BreadcrumbProps",
|
|
5
|
+
"description": "Breadcrumb — where this page sits, and how to get back up.\n\nWHY AN ORDERED LIST INSIDE A NAMED LANDMARK\n\nThe trail is a sequence, and `ol` is what says so: a screen reader announces\n\"list, 4 items\" and the position within it, which is the entire content of a\nbreadcrumb. A row of `div`s with slashes conveys none of that, and the\nslashes themselves get read out as punctuation.\n\nThe separator here is drawn by CSS `::before` on each item after the first,\nso it is decoration that never enters the accessibility tree. Putting a \"/\"\nin the markup is the usual version of this component and the usual defect.\n\nTHE LAST CRUMB IS NOT A LINK\n\n`isCurrent` renders text with `aria-current=\"page\"`. A link to the page you\nare already on is a dead control: it announces as a link, invites a click,\nand does nothing. This is the most common breadcrumb bug and the reason the\nprop exists rather than being inferred from position — a trail whose last\ncrumb IS a link to somewhere else is legitimate, and inferring would break it.",
|
|
6
|
+
"import": "import { Breadcrumb } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "Where this page sits in the hierarchy, and how to get back up. An ordered list inside a named landmark.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a page sits more than one level deep and the parent is not otherwise reachable",
|
|
11
|
+
"a record view needs its collection one click away",
|
|
12
|
+
"the hierarchy itself is information the user needs — which project, which environment"
|
|
13
|
+
],
|
|
14
|
+
"useInstead": [
|
|
15
|
+
{
|
|
16
|
+
"when": "the destinations are siblings rather than ancestors",
|
|
17
|
+
"use": "Tabs",
|
|
18
|
+
"why": "a breadcrumb describes depth; tabs describe alternatives at the same depth"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"when": "the trail is the primary navigation for the whole app",
|
|
22
|
+
"use": "NavItem inside the app shell",
|
|
23
|
+
"why": "a breadcrumb supplements navigation, it does not replace it — it appears after a user has already gone somewhere"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"slots": {
|
|
27
|
+
"children": {
|
|
28
|
+
"accepts": "BreadcrumbItem",
|
|
29
|
+
"note": "ancestor first, current page last. Anything else and the trail reads backwards"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"a11y": {
|
|
33
|
+
"requires": [
|
|
34
|
+
"`label` when a page has more than one nav landmark — it defaults to \"Breadcrumb\", which is what screen-reader users listen for"
|
|
35
|
+
],
|
|
36
|
+
"notes": [
|
|
37
|
+
"the separator is drawn by CSS ::before and never enters the accessibility tree; a \"/\" in the markup is announced as punctuation between every crumb",
|
|
38
|
+
"the list is an `ol`, so the position and length are announced — which is the entire content of a breadcrumb"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"antiPatterns": [
|
|
42
|
+
{
|
|
43
|
+
"dont": "<div>Home / Projects / Invoice 12</div>",
|
|
44
|
+
"do": "<Breadcrumb><BreadcrumbItem href=\"/\">Home</BreadcrumbItem>…</Breadcrumb>",
|
|
45
|
+
"why": "a row of divs conveys neither sequence nor position, and the slashes are read aloud"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"dont": "a full trail on a top-level page",
|
|
49
|
+
"do": "omit it",
|
|
50
|
+
"why": "a breadcrumb with one crumb is furniture — it takes vertical space to say nothing"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"stylesheet": "src/styles/breadcrumb.css",
|
|
54
|
+
"tokens": [
|
|
55
|
+
"--border-focus",
|
|
56
|
+
"--border-width-thick",
|
|
57
|
+
"--font-family-sans",
|
|
58
|
+
"--font-weight-medium",
|
|
59
|
+
"--radius-xs",
|
|
60
|
+
"--spacing-8",
|
|
61
|
+
"--text-default",
|
|
62
|
+
"--text-secondary",
|
|
63
|
+
"--text-tertiary",
|
|
64
|
+
"--type-body-sm",
|
|
65
|
+
"--type-body-sm-line-height"
|
|
66
|
+
],
|
|
67
|
+
"props": {
|
|
68
|
+
"label": {
|
|
69
|
+
"type": "string | undefined",
|
|
70
|
+
"required": false,
|
|
71
|
+
"origin": "own",
|
|
72
|
+
"description": "Names the landmark. A page with more than one `nav` needs them told apart,\nand \"Breadcrumb\" is what screen-reader users are listening for."
|
|
73
|
+
},
|
|
74
|
+
"children": {
|
|
75
|
+
"type": "React.ReactNode",
|
|
76
|
+
"required": false,
|
|
77
|
+
"origin": "own",
|
|
78
|
+
"description": "`BreadcrumbItem` elements, ancestor first."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"propCounts": {
|
|
82
|
+
"own": 2,
|
|
83
|
+
"aria": 0,
|
|
84
|
+
"dom": 277,
|
|
85
|
+
"other": 0
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "BreadcrumbItem",
|
|
3
|
+
"source": "src/components/Breadcrumb.tsx",
|
|
4
|
+
"propsType": "BreadcrumbItemProps",
|
|
5
|
+
"import": "import { BreadcrumbItem } from 'ionbase-ui';",
|
|
6
|
+
"status": "stable",
|
|
7
|
+
"summary": "One crumb. A link to an ancestor, or the current page rendered as text.",
|
|
8
|
+
"useWhen": [
|
|
9
|
+
"inside a Breadcrumb, once per level"
|
|
10
|
+
],
|
|
11
|
+
"useInstead": [
|
|
12
|
+
{
|
|
13
|
+
"when": "outside a breadcrumb trail",
|
|
14
|
+
"use": "Link",
|
|
15
|
+
"why": "the crumb's density, colour and truncation are tuned for a trail and look wrong on their own"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"slots": {
|
|
19
|
+
"children": {
|
|
20
|
+
"accepts": "text",
|
|
21
|
+
"note": "the name of the level, not its path"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"a11y": {
|
|
25
|
+
"requires": [
|
|
26
|
+
"`isCurrent` on the last crumb, which renders it as text with aria-current=\"page\" rather than as a link"
|
|
27
|
+
],
|
|
28
|
+
"notes": [
|
|
29
|
+
"isCurrent is a prop rather than inferred from position: a trail whose last crumb legitimately links elsewhere exists, and inferring would break it"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"antiPatterns": [
|
|
33
|
+
{
|
|
34
|
+
"dont": "<BreadcrumbItem href=\"/invoices/12\">Invoice 12</BreadcrumbItem> for the page you are on",
|
|
35
|
+
"do": "<BreadcrumbItem isCurrent>Invoice 12</BreadcrumbItem>",
|
|
36
|
+
"why": "a link to the current page is a dead control — it announces as a link, invites a click and does nothing"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"stylesheet": "src/styles/breadcrumb.css",
|
|
40
|
+
"tokens": [
|
|
41
|
+
"--border-focus",
|
|
42
|
+
"--border-width-thick",
|
|
43
|
+
"--font-family-sans",
|
|
44
|
+
"--font-weight-medium",
|
|
45
|
+
"--radius-xs",
|
|
46
|
+
"--spacing-8",
|
|
47
|
+
"--text-default",
|
|
48
|
+
"--text-secondary",
|
|
49
|
+
"--text-tertiary",
|
|
50
|
+
"--type-body-sm",
|
|
51
|
+
"--type-body-sm-line-height"
|
|
52
|
+
],
|
|
53
|
+
"props": {
|
|
54
|
+
"href": {
|
|
55
|
+
"type": "string | undefined",
|
|
56
|
+
"required": false,
|
|
57
|
+
"origin": "own",
|
|
58
|
+
"description": "Omit on the current page — the last crumb is not a link."
|
|
59
|
+
},
|
|
60
|
+
"isCurrent": {
|
|
61
|
+
"type": "boolean | undefined",
|
|
62
|
+
"required": false,
|
|
63
|
+
"origin": "own",
|
|
64
|
+
"description": "The page the user is on. Marked `aria-current=\"page\"` and rendered as text\nrather than a link."
|
|
65
|
+
},
|
|
66
|
+
"children": {
|
|
67
|
+
"type": "React.ReactNode",
|
|
68
|
+
"required": false,
|
|
69
|
+
"origin": "own"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"propCounts": {
|
|
73
|
+
"own": 3,
|
|
74
|
+
"aria": 0,
|
|
75
|
+
"dom": 277,
|
|
76
|
+
"other": 0
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/meta/Checkbox.json
CHANGED
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
{
|
|
79
79
|
"dont": "a div with `role=\"checkbox\"`",
|
|
80
80
|
"why": "form association, the indeterminate property and label clicking are not free to reimplement"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"dont": "<Checkbox isSelected={on} onChange={(isSelected) => setOn(isSelected)} />",
|
|
84
|
+
"do": "<Checkbox isSelected={on} onSelectionChange={setOn} />",
|
|
85
|
+
"why": "both selection shapes work, but they pair: `isSelected` goes with `onSelectionChange`, which receives the boolean, and `checked` goes with `onChange`, which receives the change event. Passing the Aria selection prop with the DOM handler name is the one combination that does not type — `onChange` is still the event handler. There is no `CheckboxChange` type; a generated eval invented one."
|
|
81
86
|
}
|
|
82
87
|
],
|
|
83
88
|
"deprecated": [
|
|
@@ -184,10 +189,22 @@
|
|
|
184
189
|
"required": false,
|
|
185
190
|
"origin": "own",
|
|
186
191
|
"description": "Figma's `Show Label` + `Label`. Omit for a bare box."
|
|
192
|
+
},
|
|
193
|
+
"isSelected": {
|
|
194
|
+
"type": "boolean | undefined",
|
|
195
|
+
"required": false,
|
|
196
|
+
"origin": "own",
|
|
197
|
+
"description": "React Aria's name for `checked`. Wins when both are passed."
|
|
198
|
+
},
|
|
199
|
+
"onSelectionChange": {
|
|
200
|
+
"type": "((isSelected: boolean) => void) | undefined",
|
|
201
|
+
"required": false,
|
|
202
|
+
"origin": "own",
|
|
203
|
+
"description": "Receives the new selection state rather than the change event."
|
|
187
204
|
}
|
|
188
205
|
},
|
|
189
206
|
"propCounts": {
|
|
190
|
-
"own":
|
|
207
|
+
"own": 8,
|
|
191
208
|
"aria": 0,
|
|
192
209
|
"dom": 304,
|
|
193
210
|
"other": 0
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Combobox",
|
|
3
|
+
"source": "src/components/Combobox.tsx",
|
|
4
|
+
"propsType": "ComboboxProps",
|
|
5
|
+
"description": "Combobox — a text field that filters a list, with one selected value.\n\nDrawn in Figma as `Combobox` (1370:2359) — three sizes by seven states, the\nsame axes `Input` carries, because the field IS Input's box. The open list is\na second Figma component, `Combobox Menu`, with no React export behind it.\n\nWHY THIS IS NOT `Select` WITH A SEARCH BOX\n\n`Select` wraps a native `<select>`, whose list the browser owns: it cannot be\nfiltered, and its rows can only hold text. Everything above about twenty\noptions needs filtering, and `Select`'s own contract has pointed at \"a\ncombobox — not yet in this system\" since it was written. This is it.\n\nThe cost is that everything the native control gave away for free — keyboard\nhandling, the mobile picker, screen-reader semantics — now has to be built.\nReact Aria's `useComboBox` builds it: `role=\"combobox\"` with `aria-expanded`,\n`aria-controls` and `aria-activedescendant`, arrow keys that move a virtual\nfocus while the real focus stays in the input, and Escape that reverts.\n\nFILTERING IS LOCALE-AWARE, NOT `toLowerCase().includes()`\n\n`useFilter({ sensitivity: 'base' })` is `Intl.Collator` underneath, so\n\"resume\" matches \"résumé\" and Turkish dotted/dotless I behave the way a\nTurkish reader expects. The lowercase-and-includes version fails both, and\nfails them silently in exactly the locales least likely to be tested.",
|
|
6
|
+
"import": "import { Combobox } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "A text field that filters a list of options down to one selected value.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"the list is long enough that scanning it is worse than typing",
|
|
11
|
+
"the user picks exactly one value from a known set",
|
|
12
|
+
"the options carry a second line of context the native <select> cannot render"
|
|
13
|
+
],
|
|
14
|
+
"useInstead": [
|
|
15
|
+
{
|
|
16
|
+
"when": "the list is short and fully scannable",
|
|
17
|
+
"use": "Select",
|
|
18
|
+
"why": "the native control brings keyboard handling and the mobile platform picker for free; this rebuilds all of it"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"when": "there are two or three mutually exclusive choices worth showing at once",
|
|
22
|
+
"use": "RadioGroup"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"when": "the value is free text and the list is only a convenience",
|
|
26
|
+
"use": "Input",
|
|
27
|
+
"why": "or set `allowsCustomValue`, which is the same decision made explicitly"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"when": "the rows are commands rather than values",
|
|
31
|
+
"use": "Menu",
|
|
32
|
+
"why": "options are selected and stay selected; menu items are invoked and the menu closes"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"variants": {
|
|
36
|
+
"size": {
|
|
37
|
+
"sm": {
|
|
38
|
+
"use": "dense forms, table filters"
|
|
39
|
+
},
|
|
40
|
+
"md": {
|
|
41
|
+
"use": "the default"
|
|
42
|
+
},
|
|
43
|
+
"lg": {
|
|
44
|
+
"use": "prominent single-field forms"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"a11y": {
|
|
49
|
+
"guarantees": [
|
|
50
|
+
"role=combobox with aria-expanded, aria-controls and aria-activedescendant, from react-aria's useComboBox",
|
|
51
|
+
"real focus never leaves the input; arrow keys move a virtual focus through the list, which is what keeps typing and browsing possible at once",
|
|
52
|
+
"the popover is non-modal, so the page behind stays readable and focus stays in the field",
|
|
53
|
+
"Escape reverts to the last committed value rather than clearing the field",
|
|
54
|
+
"filtering is Intl.Collator-based, so accents and locale-specific casing behave correctly",
|
|
55
|
+
"the empty message is a sibling of the listbox, not an option in it, so it is never counted, focused or selectable",
|
|
56
|
+
"the menu matches the field's width, clamped to the space right of it so a full-bleed field cannot push the menu past the viewport edge"
|
|
57
|
+
],
|
|
58
|
+
"requires": [
|
|
59
|
+
"`label`, or an `aria-label` when no visible label is rendered",
|
|
60
|
+
"`emptyLabel` wording that says why nothing is listed, if the default is too terse"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"antiPatterns": [
|
|
64
|
+
{
|
|
65
|
+
"dont": "giving the disclosure button a tab stop",
|
|
66
|
+
"why": "the input already opens the list with ArrowDown. A second tab stop doubles the cost of tabbing past every combobox in a form and adds nothing"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"dont": "styling the focused option with :hover or :focus",
|
|
70
|
+
"why": "no option is ever either — focus is virtual. Use [data-focused], which react-aria sets for both pointer and keyboard"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"dont": "posting the input's text in a form",
|
|
74
|
+
"why": "the text is the label and the value is the key; pass `name` and the component posts the key in a hidden input"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"dont": "filtering with toLowerCase().includes()",
|
|
78
|
+
"why": "it fails on accents and on Turkish dotted/dotless I, silently, in the locales least likely to be tested"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"dont": "writing a width onto the menu from a ResizeObserver or any effect that runs while it is open",
|
|
82
|
+
"why": "a positioned popover that changes size feeds back into react-aria's positioning, and any scrollbar that results is read by useCloseOnScroll as a real scroll. The menu opens and closes within about a hundred milliseconds, which presents as a keyboard bug rather than a layout one"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"slots": {
|
|
86
|
+
"emptyLabel": {
|
|
87
|
+
"expects": "what to show when the filter matches nothing"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"stylesheet": "src/styles/combobox.css",
|
|
91
|
+
"tokens": [
|
|
92
|
+
"--border-subtle",
|
|
93
|
+
"--border-width-default",
|
|
94
|
+
"--font-family-sans",
|
|
95
|
+
"--font-weight-medium",
|
|
96
|
+
"--icon-disabled",
|
|
97
|
+
"--icon-secondary",
|
|
98
|
+
"--ion-duration-base",
|
|
99
|
+
"--ion-ease-out",
|
|
100
|
+
"--ion-input-icon-size",
|
|
101
|
+
"--ion-shadow-shadow-lg",
|
|
102
|
+
"--radius-md",
|
|
103
|
+
"--radius-sm",
|
|
104
|
+
"--spacing-12",
|
|
105
|
+
"--spacing-2",
|
|
106
|
+
"--spacing-4",
|
|
107
|
+
"--spacing-6",
|
|
108
|
+
"--spacing-8",
|
|
109
|
+
"--surface-hover",
|
|
110
|
+
"--surface-primary-subtle",
|
|
111
|
+
"--surface-raised",
|
|
112
|
+
"--text-default",
|
|
113
|
+
"--text-disabled",
|
|
114
|
+
"--text-tertiary",
|
|
115
|
+
"--type-body",
|
|
116
|
+
"--type-body-line-height",
|
|
117
|
+
"--type-body-sm",
|
|
118
|
+
"--type-body-sm-line-height"
|
|
119
|
+
],
|
|
120
|
+
"props": {
|
|
121
|
+
"options": {
|
|
122
|
+
"type": "readonly ComboboxOption[]",
|
|
123
|
+
"required": true,
|
|
124
|
+
"origin": "own",
|
|
125
|
+
"description": "The full option list. Filtering happens here, against what is typed."
|
|
126
|
+
},
|
|
127
|
+
"label": {
|
|
128
|
+
"type": "React.ReactNode",
|
|
129
|
+
"required": false,
|
|
130
|
+
"origin": "own",
|
|
131
|
+
"description": "Field label. Required for a usable control — see `a11y.requires`."
|
|
132
|
+
},
|
|
133
|
+
"description": {
|
|
134
|
+
"type": "React.ReactNode",
|
|
135
|
+
"required": false,
|
|
136
|
+
"origin": "own",
|
|
137
|
+
"description": "Helper text below the field."
|
|
138
|
+
},
|
|
139
|
+
"errorMessage": {
|
|
140
|
+
"type": "React.ReactNode",
|
|
141
|
+
"required": false,
|
|
142
|
+
"origin": "own",
|
|
143
|
+
"description": "Replaces the helper text when `isInvalid` is set."
|
|
144
|
+
},
|
|
145
|
+
"isInvalid": {
|
|
146
|
+
"type": "boolean | undefined",
|
|
147
|
+
"required": false,
|
|
148
|
+
"origin": "own"
|
|
149
|
+
},
|
|
150
|
+
"isDisabled": {
|
|
151
|
+
"type": "boolean | undefined",
|
|
152
|
+
"required": false,
|
|
153
|
+
"origin": "own"
|
|
154
|
+
},
|
|
155
|
+
"isReadOnly": {
|
|
156
|
+
"type": "boolean | undefined",
|
|
157
|
+
"required": false,
|
|
158
|
+
"origin": "own"
|
|
159
|
+
},
|
|
160
|
+
"size": {
|
|
161
|
+
"type": "ComboboxSize | undefined",
|
|
162
|
+
"required": false,
|
|
163
|
+
"origin": "own",
|
|
164
|
+
"description": "Matches Input's `Size` variant: Small, Medium, Large.",
|
|
165
|
+
"values": [
|
|
166
|
+
"sm",
|
|
167
|
+
"md",
|
|
168
|
+
"lg"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"placeholder": {
|
|
172
|
+
"type": "string | undefined",
|
|
173
|
+
"required": false,
|
|
174
|
+
"origin": "own"
|
|
175
|
+
},
|
|
176
|
+
"selectedKey": {
|
|
177
|
+
"type": "string | null | undefined",
|
|
178
|
+
"required": false,
|
|
179
|
+
"origin": "own",
|
|
180
|
+
"description": "Controlled selection. `null` means nothing is selected."
|
|
181
|
+
},
|
|
182
|
+
"defaultSelectedKey": {
|
|
183
|
+
"type": "string | undefined",
|
|
184
|
+
"required": false,
|
|
185
|
+
"origin": "own"
|
|
186
|
+
},
|
|
187
|
+
"onSelectionChange": {
|
|
188
|
+
"type": "((key: string | null) => void) | undefined",
|
|
189
|
+
"required": false,
|
|
190
|
+
"origin": "own"
|
|
191
|
+
},
|
|
192
|
+
"inputValue": {
|
|
193
|
+
"type": "string | undefined",
|
|
194
|
+
"required": false,
|
|
195
|
+
"origin": "own",
|
|
196
|
+
"description": "Controlled text. Usually only needed for async/remote filtering."
|
|
197
|
+
},
|
|
198
|
+
"defaultInputValue": {
|
|
199
|
+
"type": "string | undefined",
|
|
200
|
+
"required": false,
|
|
201
|
+
"origin": "own"
|
|
202
|
+
},
|
|
203
|
+
"onInputChange": {
|
|
204
|
+
"type": "((value: string) => void) | undefined",
|
|
205
|
+
"required": false,
|
|
206
|
+
"origin": "own"
|
|
207
|
+
},
|
|
208
|
+
"allowsCustomValue": {
|
|
209
|
+
"type": "boolean | undefined",
|
|
210
|
+
"required": false,
|
|
211
|
+
"origin": "own",
|
|
212
|
+
"description": "Accept text that matches no option. Off by default: a combobox whose value\nis a free string is a text field with suggestions, and the caller should\nhave to say that is what they want."
|
|
213
|
+
},
|
|
214
|
+
"menuTrigger": {
|
|
215
|
+
"type": "\"focus\" | \"input\" | \"manual\" | undefined",
|
|
216
|
+
"required": false,
|
|
217
|
+
"origin": "own",
|
|
218
|
+
"description": "What opens the list. `input` — react-aria's default and this one — opens it\non the first keystroke; the chevron opens it on demand. `focus` opens it\nthe moment the field is tabbed into, which is loud in a long form.",
|
|
219
|
+
"values": [
|
|
220
|
+
"focus",
|
|
221
|
+
"input",
|
|
222
|
+
"manual"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
"emptyLabel": {
|
|
226
|
+
"type": "React.ReactNode",
|
|
227
|
+
"required": false,
|
|
228
|
+
"origin": "own",
|
|
229
|
+
"description": "Shown in place of the list when nothing matches."
|
|
230
|
+
},
|
|
231
|
+
"buttonLabel": {
|
|
232
|
+
"type": "string | undefined",
|
|
233
|
+
"required": false,
|
|
234
|
+
"origin": "own",
|
|
235
|
+
"description": "Accessible label for the disclosure button."
|
|
236
|
+
},
|
|
237
|
+
"name": {
|
|
238
|
+
"type": "string | undefined",
|
|
239
|
+
"required": false,
|
|
240
|
+
"origin": "own",
|
|
241
|
+
"description": "Posts the selected value under this name, for an uncontrolled form."
|
|
242
|
+
},
|
|
243
|
+
"className": {
|
|
244
|
+
"type": "string | undefined",
|
|
245
|
+
"required": false,
|
|
246
|
+
"origin": "own"
|
|
247
|
+
},
|
|
248
|
+
"wrapperClassName": {
|
|
249
|
+
"type": "string | undefined",
|
|
250
|
+
"required": false,
|
|
251
|
+
"origin": "own"
|
|
252
|
+
},
|
|
253
|
+
"id": {
|
|
254
|
+
"type": "string | undefined",
|
|
255
|
+
"required": false,
|
|
256
|
+
"origin": "own"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"propCounts": {
|
|
260
|
+
"own": 23,
|
|
261
|
+
"aria": 0,
|
|
262
|
+
"dom": 0,
|
|
263
|
+
"other": 0
|
|
264
|
+
}
|
|
265
|
+
}
|