ionbase-ui 0.22.0 → 0.25.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 (56) hide show
  1. package/dist/components/AgentActivity.d.ts +41 -0
  2. package/dist/components/AgentActivity.d.ts.map +1 -0
  3. package/dist/components/AgentActivity.js +84 -0
  4. package/dist/components/AgentActivity.js.map +1 -0
  5. package/dist/components/AgentStop.d.ts +58 -0
  6. package/dist/components/AgentStop.d.ts.map +1 -0
  7. package/dist/components/AgentStop.js +82 -0
  8. package/dist/components/AgentStop.js.map +1 -0
  9. package/dist/components/ApprovalGate.d.ts +72 -0
  10. package/dist/components/ApprovalGate.d.ts.map +1 -0
  11. package/dist/components/ApprovalGate.js +68 -0
  12. package/dist/components/ApprovalGate.js.map +1 -0
  13. package/dist/components/Citation.d.ts +51 -0
  14. package/dist/components/Citation.d.ts.map +1 -0
  15. package/dist/components/Citation.js +41 -0
  16. package/dist/components/Citation.js.map +1 -0
  17. package/dist/components/ConfidenceIndicator.d.ts +37 -0
  18. package/dist/components/ConfidenceIndicator.d.ts.map +1 -0
  19. package/dist/components/ConfidenceIndicator.js +41 -0
  20. package/dist/components/ConfidenceIndicator.js.map +1 -0
  21. package/dist/components/StreamingText.d.ts +47 -0
  22. package/dist/components/StreamingText.d.ts.map +1 -0
  23. package/dist/components/StreamingText.js +44 -0
  24. package/dist/components/StreamingText.js.map +1 -0
  25. package/dist/components/index.d.ts +12 -0
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +6 -0
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/figma-descriptions.json +147 -0
  30. package/dist/figma-map.json +3 -1
  31. package/dist/meta/AgentActivity.json +103 -0
  32. package/dist/meta/AgentActivityStep.json +117 -0
  33. package/dist/meta/AgentStop.json +164 -0
  34. package/dist/meta/ApprovalGate.json +255 -0
  35. package/dist/meta/Avatar.json +2 -2
  36. package/dist/meta/AvatarGroup.json +2 -2
  37. package/dist/meta/Citation.json +101 -0
  38. package/dist/meta/CitationList.json +94 -0
  39. package/dist/meta/CitationListItem.json +88 -0
  40. package/dist/meta/ConfidenceIndicator.json +115 -0
  41. package/dist/meta/Input.json +2 -2
  42. package/dist/meta/PhoneInput.json +38 -38
  43. package/dist/meta/StreamingText.json +99 -0
  44. package/dist/meta/components.json +2125 -989
  45. package/dist/meta/contrast.json +1086 -32
  46. package/dist/meta/index.json +100 -5
  47. package/dist/meta/patterns/index.json +1 -1
  48. package/dist/styles/agent-activity.css +95 -0
  49. package/dist/styles/agent-stop.css +109 -0
  50. package/dist/styles/approval-gate.css +181 -0
  51. package/dist/styles/citation.css +93 -0
  52. package/dist/styles/confidence-indicator.css +97 -0
  53. package/dist/styles/index.css +34 -0
  54. package/dist/styles/streaming-text.css +56 -0
  55. package/llms.txt +2 -2
  56. package/package.json +4 -3
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "AgentStop",
3
+ "source": "src/components/AgentStop.tsx",
4
+ "propsType": "AgentStopProps",
5
+ "description": "AgentStop — the always-visible way to end a run.\n\nNOT OPTIONAL, AND NOT BEHIND A MENU. A user who cannot stop an agent is\nwatching it, not supervising it. Human oversight of an automated process is\na compliance surface in regulated contexts, not a UX preference, and the\ncontrol that provides it has to be present and reachable at the moment it is\nwanted — not two clicks into an overflow menu.\n\nWHY IT IS NOT JUST `<Button variant=\"destructive\">`\n\nThree things it owns that a Button does not:\n\n It keeps its place while stopping. A control that vanishes the instant it\n is pressed leaves the user unsure whether the press registered, and the\n run is usually still going. It stays, relabels, and disables — the layout\n does not move.\n\n It announces. The label change is only announced to a screen reader if the\n button happens to hold focus, which it usually does not when a run was\n started elsewhere. A polite live region says \"Stopping\" once.\n\n It is not destructive-red. Stopping is a normal, expected, reversible-in-\n spirit action — you can run it again. Colouring it like `delete` teaches\n hesitation about the one control that must never be hesitated over.\n\nWHAT IT DOES NOT DO. It does not stop anything. It reports intent; the\ncaller aborts the request, closes the stream and settles the state. A stop\nbutton that resolves optimistically while tokens keep arriving is worse than\nnone, because it lies about a guarantee the user is relying on.",
6
+ "import": "import { AgentStop } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The always-visible way to end a running agent. Not optional, and not behind a menu.",
9
+ "useWhen": [
10
+ "anything runs long enough that a person might want it to stop — a generation, a tool chain, a batch job",
11
+ "the run was started by the user and is still going"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "the action is instant and there is nothing to interrupt",
16
+ "use": "Button"
17
+ },
18
+ {
19
+ "when": "you are asking permission before something happens rather than ending something already happening",
20
+ "use": "ApprovalGate"
21
+ },
22
+ {
23
+ "when": "the thing to undo has already finished",
24
+ "use": "Toast",
25
+ "why": "an undo affordance belongs with the confirmation of what was done"
26
+ }
27
+ ],
28
+ "variants": {
29
+ "size": {
30
+ "sm": {
31
+ "use": "inside a toolbar or beside an inline activity row"
32
+ },
33
+ "md": {
34
+ "use": "the default"
35
+ },
36
+ "lg": {
37
+ "use": "a full-width run surface where stopping is the primary available action"
38
+ }
39
+ }
40
+ },
41
+ "a11y": {
42
+ "role": "button",
43
+ "guarantees": [
44
+ "the control keeps its place while stopping — it relabels and disables rather than disappearing, so the layout does not move under the pointer",
45
+ "the transition to stopping is announced once through a polite live region, because the label change alone is only heard if the button happens to hold focus",
46
+ "`stopOnEscape` ignores Escape raised inside a dialog, so closing a modal never cancels a background run"
47
+ ],
48
+ "requires": [
49
+ "keeping it rendered and visible for the whole run — a stop control that scrolls away or hides in an overflow menu is the failure this component exists to prevent"
50
+ ],
51
+ "notes": [
52
+ "It does not stop anything. It reports intent; aborting the request and settling the state is the caller's. A stop that resolves optimistically while output keeps arriving lies about the one guarantee the user is relying on."
53
+ ]
54
+ },
55
+ "antiPatterns": [
56
+ {
57
+ "dont": "putting the stop control in an overflow menu",
58
+ "why": "a user who cannot reach it is watching the agent, not supervising it"
59
+ },
60
+ {
61
+ "dont": "disabling it while the run is going",
62
+ "why": "the only correct disabled state is `isStopping` — after the request, never before"
63
+ },
64
+ {
65
+ "dont": "unmounting it the moment it is pressed",
66
+ "do": "pass `isStopping`",
67
+ "why": "the run is usually still going, and a control that vanishes leaves the user unsure the press registered"
68
+ },
69
+ {
70
+ "dont": "styling it as destructive",
71
+ "why": "stopping is normal and repeatable; red teaches hesitation about the one control that must never be hesitated over"
72
+ },
73
+ {
74
+ "dont": "`stopOnEscape` on a page with modals over the run",
75
+ "why": "Escape already means dismiss to every overlay in this system"
76
+ }
77
+ ],
78
+ "stylesheet": "src/styles/agent-stop.css",
79
+ "tokens": [
80
+ "--border-default",
81
+ "--border-error",
82
+ "--border-focus",
83
+ "--border-strong",
84
+ "--border-width-default",
85
+ "--border-width-thick",
86
+ "--font-family-sans",
87
+ "--font-weight-medium",
88
+ "--icon-size-md",
89
+ "--icon-size-sm",
90
+ "--icon-size-xs",
91
+ "--ion-duration-base",
92
+ "--ion-ease-out",
93
+ "--radius-md",
94
+ "--spacing-12",
95
+ "--spacing-16",
96
+ "--spacing-20",
97
+ "--spacing-32",
98
+ "--spacing-4",
99
+ "--spacing-40",
100
+ "--spacing-48",
101
+ "--spacing-8",
102
+ "--surface-default",
103
+ "--surface-error-subtle",
104
+ "--surface-error-subtle-hover",
105
+ "--surface-muted",
106
+ "--text-default",
107
+ "--text-error",
108
+ "--text-secondary",
109
+ "--type-body",
110
+ "--type-body-lg",
111
+ "--type-body-lg-line-height",
112
+ "--type-body-line-height",
113
+ "--type-body-sm",
114
+ "--type-body-sm-line-height"
115
+ ],
116
+ "props": {
117
+ "onStop": {
118
+ "type": "() => void",
119
+ "required": true,
120
+ "origin": "own",
121
+ "description": "Called when the user asks the run to stop."
122
+ },
123
+ "isStopping": {
124
+ "type": "boolean | undefined",
125
+ "required": false,
126
+ "origin": "own",
127
+ "description": "The request is in flight. The control stays visible and keeps its place;\nit does not disappear while the thing it cancels is still running."
128
+ },
129
+ "label": {
130
+ "type": "string | undefined",
131
+ "required": false,
132
+ "origin": "own",
133
+ "description": "Visible label."
134
+ },
135
+ "stoppingLabel": {
136
+ "type": "string | undefined",
137
+ "required": false,
138
+ "origin": "own",
139
+ "description": "Visible label once `isStopping` is set."
140
+ },
141
+ "size": {
142
+ "type": "AgentStopSize | undefined",
143
+ "required": false,
144
+ "origin": "own",
145
+ "values": [
146
+ "sm",
147
+ "md",
148
+ "lg"
149
+ ]
150
+ },
151
+ "stopOnEscape": {
152
+ "type": "boolean | undefined",
153
+ "required": false,
154
+ "origin": "own",
155
+ "description": "Bind Escape, at the document, to stop the run.\n\nOff by default and deliberately so: Escape already means \"dismiss the\nthing in front of me\" to every modal, popover and menu in this system, and\na document-level handler would cancel a background run when the user meant\nto close a dialog. Turn it on only where the run IS the foreground task."
156
+ }
157
+ },
158
+ "propCounts": {
159
+ "own": 6,
160
+ "aria": 0,
161
+ "dom": 286,
162
+ "other": 0
163
+ }
164
+ }
@@ -0,0 +1,255 @@
1
+ {
2
+ "name": "ApprovalGate",
3
+ "source": "src/components/ApprovalGate.tsx",
4
+ "propsType": "ApprovalGateProps",
5
+ "description": "ApprovalGate — a proposed action, held until a person decides.\n\nThe human-in-the-loop control. An agent proposes; this is where a person\napproves, rejects, or amends before anything happens. In regulated contexts\ndemonstrable human oversight is a compliance surface rather than a nicety,\nwhich is why this is a first-class component and not a Modal with two\nbuttons in it.\n\nIT ENFORCES NOTHING, AND SAYING SO IS THE POINT\n\nThis renders a decision. It does not gate execution — the caller does, by not\nacting until `onApprove` fires. A component that *looked* like it enforced a\npolicy would be the worst possible thing to ship here: teams would rely on a\nguarantee that lives entirely in their own call site. `risk` changes emphasis\nand nothing else, for the same reason.\n\nNOT A MODAL, DELIBERATELY\n\nA modal steals focus and hides the page. But the page is the evidence: the\nuser needs the plan, the diff, the tool call and the context in front of them\nwhile deciding. This sits inline, keeps everything visible, and never\ntraps focus — an approval a user was rushed through is not oversight.\n\nNEITHER BUTTON IS FOCUSED ON MOUNT. Autofocusing approve turns a decision\ninto an Enter keypress on a page the user has not read. Focus is moved to the\nregion's heading instead, so a screen-reader user lands on what is being\nasked rather than on the answer.\n\nTHE RESOLVED STATES ARE NOT DECORATION. `approved`, `rejected` and `expired`\nreplace the actions with what happened, so the record of the decision stays\non the page. `expired` exists because an approval request that nobody answers\nis the common real outcome, and a gate that sits pending for ever is\nindistinguishable from one that is broken.",
6
+ "import": "import { ApprovalGate } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "A proposed action held until a person approves, rejects or amends it. It renders the decision; it does not enforce it.",
9
+ "useWhen": [
10
+ "an agent proposes something with consequences — spending, sending, deleting, deploying, changing permissions",
11
+ "a regulated or audited process needs demonstrable human oversight at a specific point",
12
+ "the proposal is worth showing in full, in context, before it runs"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "the action is already happening and needs stopping",
17
+ "use": "AgentStop"
18
+ },
19
+ {
20
+ "when": "a person initiated the action themselves and only needs to confirm it",
21
+ "use": "Modal",
22
+ "why": "confirming your own request is a dialog; reviewing something proposed for you is a decision"
23
+ },
24
+ {
25
+ "when": "you are reporting what happened rather than asking",
26
+ "use": "Alert"
27
+ }
28
+ ],
29
+ "variants": {
30
+ "risk": {
31
+ "low": {
32
+ "use": "reversible and cheap — a draft, a local edit. Neutral surface"
33
+ },
34
+ "medium": {
35
+ "use": "the default. Real but recoverable consequences"
36
+ },
37
+ "high": {
38
+ "use": "spending, sending externally, deleting, granting access. Warning surface, deliberately not error"
39
+ }
40
+ },
41
+ "status": {
42
+ "pending": {
43
+ "use": "the default, and the only state with actions"
44
+ },
45
+ "approved": {
46
+ "use": "resolved — the actions are replaced by the record"
47
+ },
48
+ "rejected": {
49
+ "use": "resolved, declined"
50
+ },
51
+ "expired": {
52
+ "use": "nobody answered. The common real outcome, and the reason a gate cannot sit pending for ever"
53
+ }
54
+ }
55
+ },
56
+ "slots": {
57
+ "title": {
58
+ "accepts": "text",
59
+ "note": "required, and the region's accessible name. Name the action and its object — \"Delete 14 projects\", never \"Confirm action\""
60
+ },
61
+ "children": {
62
+ "accepts": "any",
63
+ "note": "the evidence: the plan, the diff, the tool call. This is what the person is deciding about"
64
+ },
65
+ "resolution": {
66
+ "accepts": "text",
67
+ "note": "shown in place of the actions once resolved; defaults to the status"
68
+ }
69
+ },
70
+ "a11y": {
71
+ "role": "region, named by its title",
72
+ "guarantees": [
73
+ "neither button is focused on mount — autofocusing approve turns a decision into an Enter keypress on an unread page",
74
+ "the title is programmatically focusable, so the caller can move focus to the question rather than to an answer",
75
+ "reject precedes approve in the DOM, so a keyboard reaches the safe answer first",
76
+ "resolution is announced once through a polite live region",
77
+ "focus is never trapped: an approval a user was rushed through is not oversight"
78
+ ],
79
+ "requires": [
80
+ "not acting until `onApprove` fires — this component gates nothing on its own",
81
+ "a `title` that names the action and its object"
82
+ ],
83
+ "notes": [
84
+ "`risk` changes emphasis and nothing else. It does not add confirmation, does not disable anything, and must never be read as a policy the component enforces."
85
+ ]
86
+ },
87
+ "antiPatterns": [
88
+ {
89
+ "dont": "treating the gate as the enforcement point",
90
+ "why": "it renders a decision. If your code acts before `onApprove`, there is no gate — only a picture of one"
91
+ },
92
+ {
93
+ "dont": "putting it in a Modal",
94
+ "why": "the page is the evidence; hiding it to ask about it removes what the decision needs"
95
+ },
96
+ {
97
+ "dont": "autofocusing the approve button",
98
+ "why": "Enter on an unread page is not oversight, and it is exactly what an audit will look for"
99
+ },
100
+ {
101
+ "dont": "an approve-only gate with no reject",
102
+ "why": "a decision with one answer is a notification wearing a button"
103
+ },
104
+ {
105
+ "dont": "`onEdit` when the proposal cannot actually be amended",
106
+ "why": "an edit button that discards the edit is worse than no edit button"
107
+ },
108
+ {
109
+ "dont": "leaving gates pending indefinitely",
110
+ "do": "the `expired` status",
111
+ "why": "a gate nobody answered is indistinguishable from one that is broken"
112
+ }
113
+ ],
114
+ "stylesheet": "src/styles/approval-gate.css",
115
+ "tokens": [
116
+ "--border-default",
117
+ "--border-disabled",
118
+ "--border-focus",
119
+ "--border-information",
120
+ "--border-strong",
121
+ "--border-success",
122
+ "--border-warning-strong",
123
+ "--border-width-default",
124
+ "--border-width-thick",
125
+ "--border-width-thicker",
126
+ "--font-family-sans",
127
+ "--font-weight-medium",
128
+ "--font-weight-semibold",
129
+ "--icon-information",
130
+ "--icon-secondary",
131
+ "--icon-size-md",
132
+ "--icon-success",
133
+ "--icon-tertiary",
134
+ "--icon-warning",
135
+ "--ion-approval-accent",
136
+ "--ion-approval-border",
137
+ "--ion-approval-icon",
138
+ "--ion-approval-surface",
139
+ "--ion-duration-base",
140
+ "--ion-ease-out",
141
+ "--radius-md",
142
+ "--radius-sm",
143
+ "--radius-xs",
144
+ "--spacing-12",
145
+ "--spacing-16",
146
+ "--spacing-32",
147
+ "--spacing-4",
148
+ "--spacing-8",
149
+ "--surface-default",
150
+ "--surface-disabled",
151
+ "--surface-hover",
152
+ "--surface-information-subtle",
153
+ "--surface-muted",
154
+ "--surface-primary",
155
+ "--surface-primary-hover",
156
+ "--surface-warning-subtle",
157
+ "--text-default",
158
+ "--text-disabled",
159
+ "--text-information",
160
+ "--text-on-color",
161
+ "--text-secondary",
162
+ "--text-success",
163
+ "--text-warning",
164
+ "--type-body",
165
+ "--type-body-line-height",
166
+ "--type-body-sm",
167
+ "--type-body-sm-line-height"
168
+ ],
169
+ "props": {
170
+ "title": {
171
+ "type": "React.ReactNode",
172
+ "required": true,
173
+ "origin": "own",
174
+ "description": "What is being approved, stated as the action and its object — \"Delete 14\nprojects\", not \"Confirm action\". Required: it is the region's accessible\nname, and a decision the user cannot name is not a decision."
175
+ },
176
+ "children": {
177
+ "type": "React.ReactNode",
178
+ "required": false,
179
+ "origin": "own",
180
+ "description": "Why it stopped here, and what happens on approval."
181
+ },
182
+ "risk": {
183
+ "type": "ApprovalGateRisk | undefined",
184
+ "required": false,
185
+ "origin": "own",
186
+ "description": "How much is at stake. Drives emphasis only — it does NOT change what the\ncomponent enforces, because the component enforces nothing.",
187
+ "values": [
188
+ "low",
189
+ "medium",
190
+ "high"
191
+ ]
192
+ },
193
+ "status": {
194
+ "type": "ApprovalGateStatus | undefined",
195
+ "required": false,
196
+ "origin": "own",
197
+ "description": "Where the decision has got to. `pending` is the only state with buttons.",
198
+ "values": [
199
+ "pending",
200
+ "approved",
201
+ "rejected",
202
+ "expired"
203
+ ]
204
+ },
205
+ "onApprove": {
206
+ "type": "(() => void) | undefined",
207
+ "required": false,
208
+ "origin": "own"
209
+ },
210
+ "onReject": {
211
+ "type": "(() => void) | undefined",
212
+ "required": false,
213
+ "origin": "own"
214
+ },
215
+ "onEdit": {
216
+ "type": "(() => void) | undefined",
217
+ "required": false,
218
+ "origin": "own",
219
+ "description": "Offer \"edit\" as a third way out. Omit it when the proposal cannot be\namended — an approve/reject pair the user cannot influence is honest;\nan edit button that discards their edit is not."
220
+ },
221
+ "approveLabel": {
222
+ "type": "string | undefined",
223
+ "required": false,
224
+ "origin": "own"
225
+ },
226
+ "rejectLabel": {
227
+ "type": "string | undefined",
228
+ "required": false,
229
+ "origin": "own"
230
+ },
231
+ "editLabel": {
232
+ "type": "string | undefined",
233
+ "required": false,
234
+ "origin": "own"
235
+ },
236
+ "isSubmitting": {
237
+ "type": "boolean | undefined",
238
+ "required": false,
239
+ "origin": "own",
240
+ "description": "A decision is in flight. Both actions disable; neither disappears."
241
+ },
242
+ "resolution": {
243
+ "type": "React.ReactNode",
244
+ "required": false,
245
+ "origin": "own",
246
+ "description": "Shown in place of the actions once the decision is made."
247
+ }
248
+ },
249
+ "propCounts": {
250
+ "own": 12,
251
+ "aria": 0,
252
+ "dom": 275,
253
+ "other": 0
254
+ }
255
+ }
@@ -113,10 +113,10 @@
113
113
  "origin": "own",
114
114
  "description": "Matches the Figma `Size` variant: Mini 24, Small 32, Medium 40, Large 48.",
115
115
  "values": [
116
- "mini",
117
116
  "sm",
118
117
  "md",
119
- "lg"
118
+ "lg",
119
+ "mini"
120
120
  ]
121
121
  },
122
122
  "shape": {
@@ -103,10 +103,10 @@
103
103
  "required": false,
104
104
  "origin": "own",
105
105
  "values": [
106
- "mini",
107
106
  "sm",
108
107
  "md",
109
- "lg"
108
+ "lg",
109
+ "mini"
110
110
  ]
111
111
  },
112
112
  "max": {
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "Citation",
3
+ "source": "src/components/Citation.tsx",
4
+ "propsType": "CitationProps",
5
+ "description": "Citation — the inline marker.\n\nTHE MARKER IS NOT THE NAME. Rendered naively, a superscript \"1\" announces as\n\"link, 1\", which tells a screen-reader user nothing about whether to follow\nit. The visible marker stays a number; the accessible name is\n\"Source 1: <source>\". The number is for the sighted reader's eye and the\nsentence is for everyone.\n\nNOT A TOOLTIP. A tooltip cannot be reached on touch and closes on the way to\nit; attribution has to survive both. It is a real link, or a real\nnon-interactive marker when there is nowhere to go.\n\nWITHOUT `href` IT IS NOT A LINK. A citation to a phone call or an internal\ndocument has no address, and rendering a dead anchor for it puts an\nunfollowable link in the page's link list. It becomes a plain marked-up\nreference instead.",
6
+ "import": "import { Citation } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The inline source marker. The visible number is for the eye; the accessible name is the sentence.",
9
+ "useWhen": [
10
+ "a claim in generated text is drawn from a specific source the reader can check",
11
+ "attribution has to survive being read aloud, on touch, and in a links list"
12
+ ],
13
+ "useInstead": [
14
+ {
15
+ "when": "the source needs a full row with a passage",
16
+ "use": "CitationListItem"
17
+ },
18
+ {
19
+ "when": "it is an ordinary link in prose",
20
+ "use": "Link"
21
+ },
22
+ {
23
+ "when": "you were about to put the source in a hover-only hint",
24
+ "use": "Popover",
25
+ "why": "a tooltip cannot be reached on touch and closes on the way to it"
26
+ }
27
+ ],
28
+ "a11y": {
29
+ "role": "link when `href` is set, note otherwise",
30
+ "guarantees": [
31
+ "the accessible name is \"Source <index>: <source>\", never the bare number — a superscript 1 announces as \"link, 1\", which tells a screen-reader user nothing about whether to follow it",
32
+ "without `href` it renders a marker rather than a dead anchor, so an unfollowable link never enters the page's link list"
33
+ ],
34
+ "requires": [
35
+ "`source` naming what is being cited",
36
+ "an `index` matching the footer row, if there is one"
37
+ ]
38
+ },
39
+ "antiPatterns": [
40
+ {
41
+ "dont": "a bare superscript number with no accessible name",
42
+ "why": "\"link, 1\" is not attribution"
43
+ },
44
+ {
45
+ "dont": "an empty `href` for a source with no address",
46
+ "do": "omit `href`",
47
+ "why": "a dead anchor is announced as a link and goes nowhere"
48
+ },
49
+ {
50
+ "dont": "putting the source only in a tooltip",
51
+ "why": "undiscoverable on touch, and gone before it can be read"
52
+ }
53
+ ],
54
+ "stylesheet": "src/styles/citation.css",
55
+ "tokens": [
56
+ "--border-focus",
57
+ "--border-width-thick",
58
+ "--font-family-sans",
59
+ "--font-weight-medium",
60
+ "--font-weight-semibold",
61
+ "--radius-xs",
62
+ "--spacing-16",
63
+ "--spacing-2",
64
+ "--spacing-4",
65
+ "--spacing-8",
66
+ "--surface-primary-subtle",
67
+ "--surface-primary-subtle-hover",
68
+ "--text-link",
69
+ "--text-tertiary",
70
+ "--type-body-sm",
71
+ "--type-body-sm-line-height",
72
+ "--type-caption",
73
+ "--type-caption-line-height"
74
+ ],
75
+ "props": {
76
+ "index": {
77
+ "type": "string | number",
78
+ "required": true,
79
+ "origin": "own",
80
+ "description": "The marker shown inline — usually a number matching the footer list."
81
+ },
82
+ "source": {
83
+ "type": "string",
84
+ "required": true,
85
+ "origin": "own",
86
+ "description": "What is being cited. Required: it is the link's accessible name, and a\ncitation a reader cannot identify without following it is not attribution."
87
+ },
88
+ "href": {
89
+ "type": "string | undefined",
90
+ "required": false,
91
+ "origin": "own",
92
+ "description": "Where it goes. Omit for a source with no address — a document, a call."
93
+ }
94
+ },
95
+ "propCounts": {
96
+ "own": 3,
97
+ "aria": 0,
98
+ "dom": 284,
99
+ "other": 0
100
+ }
101
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "CitationList",
3
+ "source": "src/components/Citation.tsx",
4
+ "propsType": "CitationListProps",
5
+ "description": "The footer list. An `<ol>` so the numbering is structural rather than typed\ninto each row, and so a screen reader can say how many sources there are\nbefore the reader commits to hearing them.",
6
+ "import": "import { CitationList } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "The footer list of sources, as an ordered list so the count and order are structural.",
9
+ "useWhen": [
10
+ "several claims cite sources and the reader should be able to see them all at once"
11
+ ],
12
+ "useInstead": [
13
+ {
14
+ "when": "there is one source and it belongs in the sentence",
15
+ "use": "Link"
16
+ },
17
+ {
18
+ "when": "it is tabular data with more than a source per row",
19
+ "use": "Table"
20
+ }
21
+ ],
22
+ "composition": {
23
+ "order": [
24
+ "CitationList",
25
+ "CitationListItem"
26
+ ],
27
+ "example": "<CitationList><CitationListItem index={1} source=\"Q3 filing\" href=\"/q3\">Revenue rose 4%.</CitationListItem></CitationList>"
28
+ },
29
+ "slots": {
30
+ "label": {
31
+ "accepts": "text",
32
+ "note": "rendered as plain text, not a heading element — it should not enter the document outline as a section"
33
+ },
34
+ "children": {
35
+ "accepts": "CitationListItem"
36
+ }
37
+ },
38
+ "a11y": {
39
+ "guarantees": [
40
+ "an <ol>, so a screen reader can say how many sources there are before the reader commits to hearing them",
41
+ "list markers are hidden in CSS while the semantics stay, because the visible number must match the inline Citation's exactly"
42
+ ]
43
+ },
44
+ "antiPatterns": [
45
+ {
46
+ "dont": "a <div> stack of sources",
47
+ "why": "the count and the ordering are the useful part, and only a list carries them"
48
+ },
49
+ {
50
+ "dont": "numbers that disagree with the inline markers",
51
+ "why": "the marker is a promise about where to look"
52
+ }
53
+ ],
54
+ "stylesheet": "src/styles/citation.css",
55
+ "tokens": [
56
+ "--border-focus",
57
+ "--border-width-thick",
58
+ "--font-family-sans",
59
+ "--font-weight-medium",
60
+ "--font-weight-semibold",
61
+ "--radius-xs",
62
+ "--spacing-16",
63
+ "--spacing-2",
64
+ "--spacing-4",
65
+ "--spacing-8",
66
+ "--surface-primary-subtle",
67
+ "--surface-primary-subtle-hover",
68
+ "--text-link",
69
+ "--text-tertiary",
70
+ "--type-body-sm",
71
+ "--type-body-sm-line-height",
72
+ "--type-caption",
73
+ "--type-caption-line-height"
74
+ ],
75
+ "props": {
76
+ "children": {
77
+ "type": "React.ReactNode",
78
+ "required": false,
79
+ "origin": "own"
80
+ },
81
+ "label": {
82
+ "type": "React.ReactNode",
83
+ "required": false,
84
+ "origin": "own",
85
+ "description": "Heading above the list. Rendered as plain text, not a heading element."
86
+ }
87
+ },
88
+ "propCounts": {
89
+ "own": 2,
90
+ "aria": 0,
91
+ "dom": 280,
92
+ "other": 0
93
+ }
94
+ }