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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/Accordion.d.ts +54 -0
  3. package/dist/components/Accordion.d.ts.map +1 -0
  4. package/dist/components/Accordion.js +68 -0
  5. package/dist/components/Accordion.js.map +1 -0
  6. package/dist/components/Breadcrumb.d.ts +45 -0
  7. package/dist/components/Breadcrumb.d.ts.map +1 -0
  8. package/dist/components/Breadcrumb.js +31 -0
  9. package/dist/components/Breadcrumb.js.map +1 -0
  10. package/dist/components/Checkbox.d.ts +2 -1
  11. package/dist/components/Checkbox.d.ts.map +1 -1
  12. package/dist/components/Checkbox.js +4 -2
  13. package/dist/components/Checkbox.js.map +1 -1
  14. package/dist/components/Combobox.d.ts +86 -0
  15. package/dist/components/Combobox.d.ts.map +1 -0
  16. package/dist/components/Combobox.js +261 -0
  17. package/dist/components/Combobox.js.map +1 -0
  18. package/dist/components/Drawer.d.ts +52 -0
  19. package/dist/components/Drawer.d.ts.map +1 -0
  20. package/dist/components/Drawer.js +66 -0
  21. package/dist/components/Drawer.js.map +1 -0
  22. package/dist/components/FileUpload.d.ts +79 -0
  23. package/dist/components/FileUpload.d.ts.map +1 -0
  24. package/dist/components/FileUpload.js +218 -0
  25. package/dist/components/FileUpload.js.map +1 -0
  26. package/dist/components/ProgressBar.d.ts +46 -0
  27. package/dist/components/ProgressBar.d.ts.map +1 -0
  28. package/dist/components/ProgressBar.js +52 -0
  29. package/dist/components/ProgressBar.js.map +1 -0
  30. package/dist/components/Select.d.ts +9 -2
  31. package/dist/components/Select.d.ts.map +1 -1
  32. package/dist/components/Select.js.map +1 -1
  33. package/dist/components/Skeleton.d.ts +39 -0
  34. package/dist/components/Skeleton.d.ts.map +1 -0
  35. package/dist/components/Skeleton.js +50 -0
  36. package/dist/components/Skeleton.js.map +1 -0
  37. package/dist/components/Spinner.d.ts +45 -0
  38. package/dist/components/Spinner.d.ts.map +1 -0
  39. package/dist/components/Spinner.js +44 -0
  40. package/dist/components/Spinner.js.map +1 -0
  41. package/dist/components/Toggle.d.ts +2 -1
  42. package/dist/components/Toggle.d.ts.map +1 -1
  43. package/dist/components/Toggle.js +4 -2
  44. package/dist/components/Toggle.js.map +1 -1
  45. package/dist/components/index.d.ts +16 -0
  46. package/dist/components/index.d.ts.map +1 -1
  47. package/dist/components/index.js +8 -0
  48. package/dist/components/index.js.map +1 -1
  49. package/dist/components/resolve-selection.d.ts +36 -0
  50. package/dist/components/resolve-selection.d.ts.map +1 -0
  51. package/dist/components/resolve-selection.js +14 -0
  52. package/dist/components/resolve-selection.js.map +1 -0
  53. package/dist/figma-descriptions.json +201 -146
  54. package/dist/figma-map.json +2296 -1684
  55. package/dist/meta/Accordion.json +123 -0
  56. package/dist/meta/AccordionItem.json +90 -0
  57. package/dist/meta/Breadcrumb.json +87 -0
  58. package/dist/meta/BreadcrumbItem.json +78 -0
  59. package/dist/meta/Checkbox.json +18 -1
  60. package/dist/meta/Combobox.json +265 -0
  61. package/dist/meta/Drawer.json +234 -0
  62. package/dist/meta/EmptyState.json +1 -1
  63. package/dist/meta/FileUpload.json +246 -0
  64. package/dist/meta/PhoneInput.json +39 -39
  65. package/dist/meta/Popover.json +3 -3
  66. package/dist/meta/ProgressBar.json +166 -0
  67. package/dist/meta/Select.json +4 -3
  68. package/dist/meta/Skeleton.json +120 -0
  69. package/dist/meta/Spinner.json +123 -0
  70. package/dist/meta/Toggle.json +18 -1
  71. package/dist/meta/Tooltip.json +3 -3
  72. package/dist/meta/components.json +1837 -270
  73. package/dist/meta/contrast.json +2873 -194
  74. package/dist/meta/index.json +128 -7
  75. package/dist/meta/patterns/index.json +1 -1
  76. package/dist/styles/accordion.css +102 -0
  77. package/dist/styles/breadcrumb.css +72 -0
  78. package/dist/styles/combobox.css +154 -0
  79. package/dist/styles/drawer.css +181 -0
  80. package/dist/styles/empty-state.css +16 -4
  81. package/dist/styles/file-upload.css +236 -0
  82. package/dist/styles/index.css +8 -0
  83. package/dist/styles/progress-bar.css +107 -0
  84. package/dist/styles/skeleton.css +76 -0
  85. package/dist/styles/spinner.css +70 -0
  86. package/llms.txt +2 -2
  87. package/package.json +19 -19
@@ -0,0 +1,166 @@
1
+ {
2
+ "name": "ProgressBar",
3
+ "source": "src/components/ProgressBar.tsx",
4
+ "propsType": "ProgressBarProps",
5
+ "description": "ProgressBar — a determinate or indeterminate measure of work.\n\nDETERMINATE AND INDETERMINATE ARE THE SAME COMPONENT ON PURPOSE\n\nWork that starts unmeasurable and becomes measurable is the common case — a\nupload that does not know its size until the first chunk lands. Splitting\nthem into two components would make that transition a swap, which remounts\nthe node and loses the live region with it.\n\nOmitting `value` is what makes it indeterminate. `aria-valuenow` is then\nomitted too, which is exactly what the ARIA spec asks for and is the detail\nhand-rolled progress bars get wrong most often — a bar reporting\n`aria-valuenow=\"0\"` forever tells a screen reader the work is stuck at zero,\nnot that it is unmeasured.\n\nWHY `label` IS REQUIRED\n\n`role=\"progressbar\"` announces a number. Without a name the user hears \"42\npercent\" with no indication of what is at 42 percent, which is worse than\nsilence because it sounds like information.",
6
+ "import": "import { ProgressBar } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "A determinate or indeterminate measure of work, with the name of the work attached. Omitting `value` is what makes it indeterminate.",
9
+ "useWhen": [
10
+ "work reports a fraction complete — an upload, an import, a multi-step job",
11
+ "work will become measurable but is not yet, and the bar should not remount when it does",
12
+ "a quota or usage figure is better read as a bar than a number"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "the wait is short and unmeasurable",
17
+ "use": "Spinner",
18
+ "why": "an indeterminate bar occupies a block of layout to say what a spinner says inline"
19
+ },
20
+ {
21
+ "when": "the region is being replaced by content",
22
+ "use": "Skeleton"
23
+ }
24
+ ],
25
+ "variants": {
26
+ "intent": {
27
+ "primary": {
28
+ "use": "the default — work proceeding normally"
29
+ },
30
+ "success": {
31
+ "use": "work that finished, where the bar stays on screen as a record"
32
+ },
33
+ "warning": {
34
+ "use": "a quota approaching its limit",
35
+ "note": "warning describes the VALUE, not a failure of the bar"
36
+ },
37
+ "error": {
38
+ "use": "a quota exceeded, or a job that failed part-way",
39
+ "note": "the bar is still reporting correctly — reserve this for the measured thing being bad"
40
+ }
41
+ },
42
+ "size": {
43
+ "sm": {
44
+ "use": "inside a row or a dense table cell"
45
+ },
46
+ "md": {
47
+ "use": "the default"
48
+ }
49
+ }
50
+ },
51
+ "a11y": {
52
+ "requires": [
53
+ "`label` — it is required, because role=\"progressbar\" announces a number and a number with no noun sounds like information while carrying none",
54
+ "`valueText` when a percentage is not what a person wants read aloud — \"3 of 12 files\""
55
+ ],
56
+ "notes": [
57
+ "indeterminate omits aria-valuenow entirely, as the ARIA spec asks; a bar reporting aria-valuenow=0 forever says the work is stuck rather than unmeasured",
58
+ "value is clamped to 0..max, so a caller cannot render a bar past its own end"
59
+ ]
60
+ },
61
+ "antiPatterns": [
62
+ {
63
+ "dont": "<ProgressBar label=\"Loading\" value={0} /> while the total is unknown",
64
+ "do": "<ProgressBar label=\"Loading\" />",
65
+ "why": "value={0} announces \"0 percent\" indefinitely. Omitting value is how you say \"not measurable yet\""
66
+ },
67
+ {
68
+ "dont": "swapping a Spinner for a ProgressBar once the size is known",
69
+ "do": "one ProgressBar, with `value` appearing when it becomes known",
70
+ "why": "the swap remounts the node and takes the live region with it, so the transition is silent to a screen reader"
71
+ },
72
+ {
73
+ "dont": "intent=\"error\" because the request failed",
74
+ "do": "leave the bar and put the failure in an Alert",
75
+ "why": "intent describes the value being measured. A failed request is not a red measurement, it is a different message"
76
+ }
77
+ ],
78
+ "stylesheet": "src/styles/progress-bar.css",
79
+ "tokens": [
80
+ "--border-width-default",
81
+ "--font-family-sans",
82
+ "--ion-duration-base",
83
+ "--ion-duration-slow",
84
+ "--ion-ease-in-out",
85
+ "--ion-ease-out",
86
+ "--ion-progress-bar-fill",
87
+ "--ion-progress-bar-height",
88
+ "--ion-progress-bar-pct",
89
+ "--radius-full",
90
+ "--spacing-4",
91
+ "--spacing-8",
92
+ "--surface-error",
93
+ "--surface-muted",
94
+ "--surface-primary",
95
+ "--surface-success",
96
+ "--surface-warning",
97
+ "--text-default",
98
+ "--text-secondary",
99
+ "--type-body-sm",
100
+ "--type-body-sm-line-height"
101
+ ],
102
+ "props": {
103
+ "label": {
104
+ "type": "string",
105
+ "required": true,
106
+ "origin": "own",
107
+ "description": "What is progressing. Required — a bare bar announces a number and no noun."
108
+ },
109
+ "value": {
110
+ "type": "number | undefined",
111
+ "required": false,
112
+ "origin": "own",
113
+ "description": "0 to `max`. Omit for an indeterminate bar."
114
+ },
115
+ "max": {
116
+ "type": "number | undefined",
117
+ "required": false,
118
+ "origin": "own",
119
+ "description": "Defaults to 100."
120
+ },
121
+ "intent": {
122
+ "type": "ProgressBarIntent | undefined",
123
+ "required": false,
124
+ "origin": "own",
125
+ "values": [
126
+ "primary",
127
+ "success",
128
+ "warning",
129
+ "error"
130
+ ]
131
+ },
132
+ "size": {
133
+ "type": "ProgressBarSize | undefined",
134
+ "required": false,
135
+ "origin": "own",
136
+ "values": [
137
+ "sm",
138
+ "md"
139
+ ]
140
+ },
141
+ "isLabelVisible": {
142
+ "type": "boolean | undefined",
143
+ "required": false,
144
+ "origin": "own",
145
+ "description": "Show the label as text above the track."
146
+ },
147
+ "isValueVisible": {
148
+ "type": "boolean | undefined",
149
+ "required": false,
150
+ "origin": "own",
151
+ "description": "Show the percentage beside the label. Determinate bars only."
152
+ },
153
+ "valueText": {
154
+ "type": "string | undefined",
155
+ "required": false,
156
+ "origin": "own",
157
+ "description": "Spoken instead of the percentage — \"3 of 12 files\". A percentage is rarely\nthe thing a person wants read aloud."
158
+ }
159
+ },
160
+ "propCounts": {
161
+ "own": 8,
162
+ "aria": 0,
163
+ "dom": 277,
164
+ "other": 0
165
+ }
166
+ }
@@ -13,7 +13,8 @@
13
13
  "useInstead": [
14
14
  {
15
15
  "when": "the list is long enough to need search or type-ahead beyond the native behaviour",
16
- "use": "a combobox — not yet in this system"
16
+ "use": "Combobox",
17
+ "why": "a native <select> list is the browser's, and cannot be filtered"
17
18
  },
18
19
  {
19
20
  "when": "the rows need icons, avatars or two lines of text",
@@ -126,10 +127,10 @@
126
127
  "default": "md"
127
128
  },
128
129
  "options": {
129
- "type": "SelectOption[] | undefined",
130
+ "type": "readonly SelectOption[] | undefined",
130
131
  "required": false,
131
132
  "origin": "own",
132
- "description": "The options to render. Omit and pass `children` for grouped options."
133
+ "description": "The options to render. Omit and pass `children` for grouped options.\n\n`readonly` because an options list is almost always a module-level constant,\nand `as const` on one produces a readonly array that a mutable parameter\nrejects. The component only ever maps over it, so demanding a mutable array\nbought nothing and cost callers a spread or a cast."
133
134
  },
134
135
  "placeholder": {
135
136
  "type": "string | undefined",
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "Skeleton",
3
+ "source": "src/components/Skeleton.tsx",
4
+ "propsType": "SkeletonProps",
5
+ "description": "Skeleton — the shape of content that has not arrived.\n\nIT IS HIDDEN FROM ASSISTIVE TECHNOLOGY, ALWAYS, AND THAT IS THE WHOLE POINT\n\nA skeleton is a picture of content, and a screen reader cannot use a picture\nof content. Announcing it produces a stream of empty boxes between the user\nand the thing they asked for. So every skeleton is `aria-hidden`, with no way\nto opt out — a prop for that would only ever be used by mistake.\n\nWHICH MEANS THE CALLER OWES THE ANNOUNCEMENT, AND THIS COMPONENT CANNOT DO IT\n\nHiding the placeholder is only half an answer: something still has to tell a\nscreen-reader user that the region is loading. That belongs on the region\nbeing replaced — `aria-busy=\"true\"` while it loads — because only the caller\nknows where that region starts and ends. `Spinner` is the other half when the\nwait deserves an announcement of its own.\n\n <section aria-busy={isLoading}>\n {isLoading ? <Skeleton lines={3} /> : <Rows data={data} />}\n </section>\n\nThis is the one component in the system whose correct use REQUIRES something\nof the caller that the type system cannot check, which is why it is stated\nhere, in the contract, and in the story.",
6
+ "import": "import { Skeleton } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The shape of content that has not arrived. Always hidden from assistive technology — the caller owns the announcement.",
9
+ "useWhen": [
10
+ "a region will be replaced by content whose shape is known in advance",
11
+ "the layout would otherwise jump when the content lands",
12
+ "several regions load independently and a single page spinner would hide that"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "the shape of what is coming is not known",
17
+ "use": "Spinner",
18
+ "why": "a skeleton that does not match what replaces it is a worse promise than no promise"
19
+ },
20
+ {
21
+ "when": "the region is empty because there is nothing to show",
22
+ "use": "EmptyState",
23
+ "why": "a skeleton that never resolves is how an empty list looks like a broken one"
24
+ }
25
+ ],
26
+ "variants": {
27
+ "variant": {
28
+ "text": {
29
+ "use": "lines of copy",
30
+ "note": "height comes from the line box, so the block occupies what the real paragraph will. With `lines`, the last is drawn short because real paragraphs end mid-measure"
31
+ },
32
+ "circle": {
33
+ "use": "an avatar or an icon slot"
34
+ },
35
+ "rect": {
36
+ "use": "a card, a thumbnail, a chart"
37
+ }
38
+ }
39
+ },
40
+ "a11y": {
41
+ "requires": [
42
+ "`aria-busy=\"true\"` on the region being replaced, set by the CALLER — this component cannot do it, because only the caller knows where the region starts and ends"
43
+ ],
44
+ "notes": [
45
+ "every skeleton is aria-hidden with no opt-out: a screen reader cannot use a picture of content, and announcing it puts a stream of empty boxes between the user and what they asked for",
46
+ "prefers-reduced-motion stops the pulse outright, unlike Spinner and ProgressBar — a skeleton conveys nothing a static block does not, so there is no signal to preserve"
47
+ ]
48
+ },
49
+ "antiPatterns": [
50
+ {
51
+ "dont": "<Skeleton /> inside a region with no aria-busy",
52
+ "do": "<section aria-busy={isLoading}>{isLoading ? <Skeleton lines={3} /> : <Rows />}</section>",
53
+ "why": "the skeleton is hidden, so without aria-busy a screen-reader user is told nothing at all — the region simply reads as empty"
54
+ },
55
+ {
56
+ "dont": "a skeleton whose shape does not match the content that replaces it",
57
+ "do": "match the line count and block sizes to the real thing",
58
+ "why": "the entire benefit is that the layout does not move; a mismatched skeleton pays the cost and keeps the jump"
59
+ },
60
+ {
61
+ "dont": "leaving skeletons on screen when a request fails",
62
+ "do": "render EmptyState with reason=\"error\"",
63
+ "why": "a skeleton that never resolves is indistinguishable from a hung page"
64
+ }
65
+ ],
66
+ "stylesheet": "src/styles/skeleton.css",
67
+ "tokens": [
68
+ "--border-width-default",
69
+ "--icon-size-lg",
70
+ "--ion-duration-slow",
71
+ "--ion-ease-in-out",
72
+ "--ion-skeleton-height",
73
+ "--ion-skeleton-radius",
74
+ "--ion-skeleton-width",
75
+ "--radius-full",
76
+ "--radius-md",
77
+ "--radius-sm",
78
+ "--spacing-16",
79
+ "--spacing-64",
80
+ "--spacing-8",
81
+ "--surface-muted",
82
+ "--type-body-line-height"
83
+ ],
84
+ "props": {
85
+ "variant": {
86
+ "type": "SkeletonVariant | undefined",
87
+ "required": false,
88
+ "origin": "own",
89
+ "values": [
90
+ "circle",
91
+ "text",
92
+ "rect"
93
+ ]
94
+ },
95
+ "width": {
96
+ "type": "string | undefined",
97
+ "required": false,
98
+ "origin": "own",
99
+ "description": "Any CSS length. Defaults to filling the width available."
100
+ },
101
+ "height": {
102
+ "type": "string | undefined",
103
+ "required": false,
104
+ "origin": "own",
105
+ "description": "Any CSS length. `text` derives its height from the line box instead."
106
+ },
107
+ "lines": {
108
+ "type": "number | undefined",
109
+ "required": false,
110
+ "origin": "own",
111
+ "description": "`text` only: how many lines to draw. The last is drawn short."
112
+ }
113
+ },
114
+ "propCounts": {
115
+ "own": 4,
116
+ "aria": 0,
117
+ "dom": 277,
118
+ "other": 0
119
+ }
120
+ }
@@ -0,0 +1,123 @@
1
+ {
2
+ "name": "Spinner",
3
+ "source": "src/components/Spinner.tsx",
4
+ "propsType": "SpinnerProps",
5
+ "description": "Spinner — an indeterminate wait, announced once.\n\nWHY THIS IS NOT A DIV WITH A CSS ANIMATION\n\nThe obvious implementation is inaccessible in a specific and common way: a\nspinning graphic with no text is silence to a screen reader, so the user is\ntold nothing happened. Generated code in this repo's own eval corpus reached\nfor a bare `role=\"status\"` in 48 files, which is the right instinct and only\nhalf the job — the role creates a live region, but an empty one announces\nnothing at all.\n\nSo the default carries a label. It is visually hidden, it lives inside the\nlive region, and it is what makes \"Loading\" reach the user who cannot see the\nring.\n\n`isDecorative` exists because the opposite mistake is just as easy. A spinner\ninside a button whose label already changed to \"Saving…\" would announce\ntwice, and two live regions racing is worse than one. Decorative renders no\nrole at all rather than `aria-hidden` on a live region, which browsers treat\ninconsistently.\n\nMOTION\n\n`prefers-reduced-motion` slows the rotation rather than stopping it. A\nstationary spinner is not a calmer spinner — it is a broken one, and it\nremoves the only signal a sighted user has that the wait is still live.",
6
+ "import": "import { Spinner } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "An indeterminate wait, announced once. A ring plus a politely-announced label, sized off the icon ramp.",
9
+ "useWhen": [
10
+ "work is in flight and its duration cannot be measured",
11
+ "a control has started something and must show it is working",
12
+ "a small region is waiting and a skeleton would be more scaffolding than content"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "the work has a measurable fraction complete",
17
+ "use": "ProgressBar",
18
+ "why": "a spinner over measurable work discards information the user wants — a spinner and a 90%-complete upload look identical"
19
+ },
20
+ {
21
+ "when": "a region is being replaced by content with a known shape",
22
+ "use": "Skeleton",
23
+ "why": "a skeleton holds the layout so it does not jump when content lands; a spinner leaves a hole that then reflows"
24
+ }
25
+ ],
26
+ "variants": {
27
+ "size": {
28
+ "sm": {
29
+ "use": "inside a control, or beside a line of text"
30
+ },
31
+ "md": {
32
+ "use": "the default — a panel or a section"
33
+ },
34
+ "lg": {
35
+ "use": "a whole route or a full-page wait"
36
+ }
37
+ }
38
+ },
39
+ "a11y": {
40
+ "requires": [
41
+ "a `label` naming what is loading — it is announced politely and is the only thing a screen-reader user receives",
42
+ "`isDecorative` when something else already announces the wait, so two live regions do not race"
43
+ ],
44
+ "notes": [
45
+ "the ring is aria-hidden; the label carries the meaning",
46
+ "prefers-reduced-motion slows the rotation rather than stopping it — a stationary spinner is indistinguishable from a hung one"
47
+ ]
48
+ },
49
+ "antiPatterns": [
50
+ {
51
+ "dont": "<div className=\"spinner\" role=\"status\" />",
52
+ "do": "<Spinner label=\"Loading invoices\" />",
53
+ "why": "role=\"status\" creates a live region; an EMPTY live region announces nothing. This exact half-measure appeared in 48 files of this repo's eval corpus"
54
+ },
55
+ {
56
+ "dont": "<Button isDisabled>Saving… <Spinner /></Button>",
57
+ "do": "<Button isDisabled>Saving… <Spinner isDecorative /></Button>",
58
+ "why": "the button label already says it. Two live regions announcing the same wait talk over each other"
59
+ },
60
+ {
61
+ "dont": "a Spinner for an upload that reports bytes transferred",
62
+ "do": "ProgressBar with value",
63
+ "why": "throwing away a known percentage makes a finite wait feel unbounded"
64
+ }
65
+ ],
66
+ "stylesheet": "src/styles/spinner.css",
67
+ "tokens": [
68
+ "--border-primary-strong",
69
+ "--border-subtle",
70
+ "--border-width-thick",
71
+ "--font-family-sans",
72
+ "--icon-size-lg",
73
+ "--icon-size-md",
74
+ "--icon-size-sm",
75
+ "--ion-duration-slow",
76
+ "--ion-ease-linear",
77
+ "--ion-spinner-head",
78
+ "--ion-spinner-size",
79
+ "--ion-spinner-track",
80
+ "--radius-full",
81
+ "--spacing-8",
82
+ "--text-secondary",
83
+ "--type-body-sm",
84
+ "--type-body-sm-line-height"
85
+ ],
86
+ "props": {
87
+ "size": {
88
+ "type": "SpinnerSize | undefined",
89
+ "required": false,
90
+ "origin": "own",
91
+ "description": "Matches the icon size ramp: sm, md, lg.",
92
+ "values": [
93
+ "sm",
94
+ "md",
95
+ "lg"
96
+ ]
97
+ },
98
+ "label": {
99
+ "type": "string | undefined",
100
+ "required": false,
101
+ "origin": "own",
102
+ "description": "What is loading. Announced politely; visible only with `isLabelVisible`."
103
+ },
104
+ "isLabelVisible": {
105
+ "type": "boolean | undefined",
106
+ "required": false,
107
+ "origin": "own",
108
+ "description": "Render the label as text beside the ring as well as announcing it."
109
+ },
110
+ "isDecorative": {
111
+ "type": "boolean | undefined",
112
+ "required": false,
113
+ "origin": "own",
114
+ "description": "The spinner conveys nothing on its own — something else already says the\nregion is busy. Renders inert: no role, no live region, hidden from AT."
115
+ }
116
+ },
117
+ "propCounts": {
118
+ "own": 4,
119
+ "aria": 0,
120
+ "dom": 277,
121
+ "other": 0
122
+ }
123
+ }
@@ -72,6 +72,11 @@
72
72
  {
73
73
  "dont": "a button with `aria-pressed` for a setting",
74
74
  "why": "a switch is a form value; the checkbox gives form association and `:checked` for free"
75
+ },
76
+ {
77
+ "dont": "<Toggle isSelected={on} onChange={(isSelected) => setOn(isSelected)} />",
78
+ "do": "<Toggle isSelected={on} onSelectionChange={setOn} />",
79
+ "why": "as Checkbox: `isSelected` pairs with `onSelectionChange` and `checked` pairs with `onChange(event)`. Toggle extends the input element, so `onChange` keeps the DOM signature it has always had."
75
80
  }
76
81
  ],
77
82
  "deprecated": [
@@ -167,10 +172,22 @@
167
172
  "type": "React.ReactNode",
168
173
  "required": false,
169
174
  "origin": "own"
175
+ },
176
+ "isSelected": {
177
+ "type": "boolean | undefined",
178
+ "required": false,
179
+ "origin": "own",
180
+ "description": "React Aria's name for `checked`. Wins when both are passed."
181
+ },
182
+ "onSelectionChange": {
183
+ "type": "((isSelected: boolean) => void) | undefined",
184
+ "required": false,
185
+ "origin": "own",
186
+ "description": "Receives the new selection state rather than the change event."
170
187
  }
171
188
  },
172
189
  "propCounts": {
173
- "own": 5,
190
+ "own": 7,
174
191
  "aria": 0,
175
192
  "dom": 304,
176
193
  "other": 0
@@ -113,10 +113,10 @@
113
113
  "origin": "own",
114
114
  "description": "Matches the Figma `Placement` variant, and names where the TOOLTIP sits —\nnot where the arrow points. `top` is above the trigger, with the arrow on\nthe tooltip's bottom edge.\n\nTreated as a preference, not a guarantee: it flips to the opposite side\nwhen there is no room, which is the behaviour Figma cannot draw.",
115
115
  "values": [
116
- "right",
117
- "left",
118
116
  "top",
119
- "bottom"
117
+ "bottom",
118
+ "right",
119
+ "left"
120
120
  ]
121
121
  },
122
122
  "delay": {