ionbase-ui 0.31.0 → 0.37.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/components/Badge.d.ts +13 -0
- package/dist/components/Badge.d.ts.map +1 -1
- package/dist/components/Badge.js +8 -2
- package/dist/components/Badge.js.map +1 -1
- package/dist/components/EmptyState.d.ts +99 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +51 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/figma-descriptions.json +75 -30
- package/dist/figma-map.json +455 -15
- package/dist/meta/AgentStop.json +2 -3
- package/dist/meta/Badge.json +52 -2
- package/dist/meta/EmptyState.json +237 -0
- package/dist/meta/components.json +292 -6
- package/dist/meta/contrast.json +18 -0
- package/dist/meta/index.json +29 -1
- package/dist/meta/patterns/AgentRun.json +136 -0
- package/dist/meta/patterns/AssistantAnswer.json +130 -0
- package/dist/meta/patterns/HumanApproval.json +139 -0
- package/dist/meta/patterns/index.json +55 -1
- package/dist/styles/agent-stop.css +28 -9
- package/dist/styles/badge.css +87 -11
- package/dist/styles/empty-state.css +169 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/tokens/base.css +7 -0
- package/dist/styles/tokens/typography.css +5 -0
- package/dist/tokens/index.d.ts +35 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +28 -0
- package/dist/tokens/index.js.map +1 -1
- package/llms.txt +3 -3
- package/package.json +4 -3
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "EmptyState",
|
|
3
|
+
"source": "src/components/EmptyState.tsx",
|
|
4
|
+
"propsType": "EmptyStateProps",
|
|
5
|
+
"description": "EmptyState — the state five of this system's nine patterns require and none\nof them could render.\n\n`DataTable`, `PageShell`, `SettingsPanel`, `AssistantAnswer` and\n`HumanApproval` all specify an empty state in their recipe. Until this\nexisted each consumer invented one, which is how a design system ends up\nwith four different ways to say \"nothing here\".\n\nREASON IS REQUIRED, AND HAS NO DEFAULT\n\nThe four reasons are genuinely different situations with different correct\nactions, and the failure mode is silent: an app that shows \"No invoices yet\n— Create your first invoice\" to a user whose filter excluded everything has\ntold them their records are gone. A default would make that the easy path,\nso there isn't one.\n\n`no-results` and `error` deliberately do NOT offer a create action in the\ncontract's guidance. The first needs the filter cleared; the second needs a\nretry, and offering \"Create\" over a failed fetch invites a duplicate.\n\nNOT A LIVE REGION\n\nAn empty state replaces content, so it is what the user reads next rather\nthan something announced over what they are reading. `role=\"status\"` here\nwould interrupt on every keystroke of a filter box. If a specific flow needs\nthe announcement, the caller owns that decision — the same reasoning\nStreamingText applies to `aria-live`.\n\n`error` is rendered as `role=\"alert\"` ONLY when the caller opts in via\n`role`, for the same reason: an error empty state arriving during typing is\nstill a read, not an interruption.\n\nNo `'use client'`: nothing here holds state. The buttons passed through\n`action` carry their own boundary.",
|
|
6
|
+
"import": "import { EmptyState } from 'ionbase-ui';",
|
|
7
|
+
"status": "stable",
|
|
8
|
+
"summary": "The state a region shows when it has nothing to show, and why — first run, no results, no access, or a failed fetch.",
|
|
9
|
+
"useWhen": [
|
|
10
|
+
"a list, table, panel or route has no content to render",
|
|
11
|
+
"a filter or search returns nothing",
|
|
12
|
+
"the account is not permitted to see what would be here",
|
|
13
|
+
"the request that would have filled the region failed"
|
|
14
|
+
],
|
|
15
|
+
"useInstead": [
|
|
16
|
+
{
|
|
17
|
+
"when": "content exists and something went wrong beside it",
|
|
18
|
+
"use": "Alert",
|
|
19
|
+
"why": "an Alert sits with the content; an EmptyState replaces it. If there is a table on screen, the message about it is an Alert"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"when": "the message is transient and the content is unaffected",
|
|
23
|
+
"use": "Toast"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"slots": {
|
|
27
|
+
"title": {
|
|
28
|
+
"accepts": "text",
|
|
29
|
+
"note": "required — it is the region's statement. Name the thing and the situation: \"No invoices match these filters\", not \"No results\""
|
|
30
|
+
},
|
|
31
|
+
"description": {
|
|
32
|
+
"accepts": "text",
|
|
33
|
+
"note": "one or two sentences: what would be here, and what puts it here"
|
|
34
|
+
},
|
|
35
|
+
"action": {
|
|
36
|
+
"accepts": "Button",
|
|
37
|
+
"note": "the action that RESOLVES this reason — clearing a filter for no-results, not creating a record"
|
|
38
|
+
},
|
|
39
|
+
"secondaryAction": {
|
|
40
|
+
"accepts": "Button or Link",
|
|
41
|
+
"note": "a lower-emphasis escape hatch — \"Contact an admin\""
|
|
42
|
+
},
|
|
43
|
+
"icon": {
|
|
44
|
+
"accepts": "Icon",
|
|
45
|
+
"note": "decorative only, rendered aria-hidden — the title already says what this is"
|
|
46
|
+
},
|
|
47
|
+
"children": {
|
|
48
|
+
"accepts": "any",
|
|
49
|
+
"note": "extra content between the description and the actions"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"variants": {
|
|
53
|
+
"reason": {
|
|
54
|
+
"first-run": {
|
|
55
|
+
"use": "nothing exists yet and the user has never created one",
|
|
56
|
+
"note": "the only reason where a create action is the right primary"
|
|
57
|
+
},
|
|
58
|
+
"no-results": {
|
|
59
|
+
"use": "records exist; the current filter or query matches none of them",
|
|
60
|
+
"note": "the action is clearing the filter. Offering \"Create\" here tells a user with 400 records and a typo that their data is gone"
|
|
61
|
+
},
|
|
62
|
+
"no-access": {
|
|
63
|
+
"use": "records exist and match, but this account may not see them",
|
|
64
|
+
"note": "name the permission or plan. \"Nothing here\" for a permissions problem sends the user to support with the wrong question"
|
|
65
|
+
},
|
|
66
|
+
"error": {
|
|
67
|
+
"use": "the request that would have filled the region failed",
|
|
68
|
+
"note": "the action is a retry. Never a create — offering one over a failed fetch invites a duplicate record"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"size": {
|
|
72
|
+
"inline": {
|
|
73
|
+
"use": "inside something that already has a frame — a table body, a card"
|
|
74
|
+
},
|
|
75
|
+
"panel": {
|
|
76
|
+
"use": "a region of a page. The default"
|
|
77
|
+
},
|
|
78
|
+
"page": {
|
|
79
|
+
"use": "a whole route with nothing in it"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"a11y": {
|
|
84
|
+
"guarantees": [
|
|
85
|
+
"the title renders as a real heading, so the region appears in the document outline",
|
|
86
|
+
"the icon is aria-hidden, so a screen reader is not told the same thing twice"
|
|
87
|
+
],
|
|
88
|
+
"requires": [
|
|
89
|
+
"a `title` that states the situation, since it is what a screen-reader user hears in place of the missing content",
|
|
90
|
+
"`headingLevel` matching the surrounding outline — h3 is the default, but only the page knows its real level"
|
|
91
|
+
],
|
|
92
|
+
"notes": [
|
|
93
|
+
"This is deliberately NOT a live region. An empty state replaces content, so it is what the user reads next rather than something announced over what they are reading — `role=\"status\"` here fires on every keystroke of a filter box. If a flow genuinely needs the announcement, pass `role` yourself.",
|
|
94
|
+
"`headingLevel` takes 2-6 and defaults to 3. It is a number rather than a variant because it is an outline decision, not a visual one — the size prop is what changes how the title looks.",
|
|
95
|
+
"`h1` is not available: an empty state describes a region, and the page's own title belongs to the page. Same call FullCard makes.",
|
|
96
|
+
"Under forced-colors the reason tint is discarded, so the region draws a border to stay distinguishable from the content it replaced."
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"antiPatterns": [
|
|
100
|
+
{
|
|
101
|
+
"dont": "using `first-run` for an empty filter result",
|
|
102
|
+
"do": "no-results",
|
|
103
|
+
"why": "it is the most common empty-state bug in enterprise software: a user with 400 invoices and a typo in their filter is told they have none, and offered a button to make one"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"dont": "\"No data\" or \"Nothing found\" as the title",
|
|
107
|
+
"why": "it names neither the thing nor the situation, so the user cannot tell whether to create a record, clear a filter, or ask for access"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"dont": "an EmptyState while the request is still in flight",
|
|
111
|
+
"why": "it asserts the data is absent before anything knows, then contradicts itself a moment later"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"dont": "a create action on `error`",
|
|
115
|
+
"why": "the fetch failed, so nobody knows whether the record already exists — this is how duplicates get made"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"dont": "an icon with its own `aria-label` or visible caption",
|
|
119
|
+
"why": "the slot is decorative and the title already carries the meaning; a labelled icon makes it announce twice"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"dont": "hiding the region entirely when it is empty",
|
|
123
|
+
"why": "a section that vanishes is indistinguishable from one that failed to render, and the user cannot tell that they are looking at everything there is"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"dont": "an EmptyState above a form nobody has filled in yet",
|
|
127
|
+
"why": "a form's empty state IS its initial state — see the Form pattern. A message saying the form is empty is noise on top of a form that is visibly empty"
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"stylesheet": "src/styles/empty-state.css",
|
|
131
|
+
"tokens": [
|
|
132
|
+
"--font-family-sans",
|
|
133
|
+
"--font-weight-medium",
|
|
134
|
+
"--icon-error",
|
|
135
|
+
"--icon-primary",
|
|
136
|
+
"--icon-size-md",
|
|
137
|
+
"--icon-size-xl",
|
|
138
|
+
"--icon-tertiary",
|
|
139
|
+
"--icon-warning",
|
|
140
|
+
"--ion-empty-state-gap",
|
|
141
|
+
"--ion-empty-state-icon",
|
|
142
|
+
"--ion-empty-state-icon-size",
|
|
143
|
+
"--ion-empty-state-pad-block",
|
|
144
|
+
"--ion-empty-state-title",
|
|
145
|
+
"--ion-empty-state-title-line-height",
|
|
146
|
+
"--radius-md",
|
|
147
|
+
"--spacing-12",
|
|
148
|
+
"--spacing-16",
|
|
149
|
+
"--spacing-24",
|
|
150
|
+
"--spacing-4",
|
|
151
|
+
"--spacing-48",
|
|
152
|
+
"--spacing-64",
|
|
153
|
+
"--spacing-8",
|
|
154
|
+
"--text-default",
|
|
155
|
+
"--text-secondary",
|
|
156
|
+
"--type-body",
|
|
157
|
+
"--type-body-line-height",
|
|
158
|
+
"--type-body-md",
|
|
159
|
+
"--type-body-md-line-height",
|
|
160
|
+
"--type-h3",
|
|
161
|
+
"--type-h3-line-height",
|
|
162
|
+
"--type-h4",
|
|
163
|
+
"--type-h4-line-height"
|
|
164
|
+
],
|
|
165
|
+
"props": {
|
|
166
|
+
"reason": {
|
|
167
|
+
"type": "EmptyStateReason",
|
|
168
|
+
"required": true,
|
|
169
|
+
"origin": "own",
|
|
170
|
+
"description": "Why there is nothing here. Required, and there is no default: every\ndefault would be a guess, and the wrong guess is the bug this component\nexists to prevent.",
|
|
171
|
+
"values": [
|
|
172
|
+
"error",
|
|
173
|
+
"first-run",
|
|
174
|
+
"no-results",
|
|
175
|
+
"no-access"
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
"title": {
|
|
179
|
+
"type": "React.ReactNode",
|
|
180
|
+
"required": true,
|
|
181
|
+
"origin": "own",
|
|
182
|
+
"description": "What is missing, in the user's words. Required — it is the accessible name\nof the region, and an empty state with no statement is a blank area with\npadding.\n\nName the thing and the situation: \"No invoices match these filters\", not\n\"No results\"."
|
|
183
|
+
},
|
|
184
|
+
"description": {
|
|
185
|
+
"type": "React.ReactNode",
|
|
186
|
+
"required": false,
|
|
187
|
+
"origin": "own",
|
|
188
|
+
"description": "One or two sentences: what would be here, and what puts it here."
|
|
189
|
+
},
|
|
190
|
+
"action": {
|
|
191
|
+
"type": "React.ReactNode",
|
|
192
|
+
"required": false,
|
|
193
|
+
"origin": "own",
|
|
194
|
+
"description": "The action that resolves this state. For `no-results` that is clearing the\nfilter, NOT creating a record — see `useInstead` in the contract."
|
|
195
|
+
},
|
|
196
|
+
"secondaryAction": {
|
|
197
|
+
"type": "React.ReactNode",
|
|
198
|
+
"required": false,
|
|
199
|
+
"origin": "own",
|
|
200
|
+
"description": "A lower-emphasis escape hatch beside `action` — \"Contact an admin\"."
|
|
201
|
+
},
|
|
202
|
+
"icon": {
|
|
203
|
+
"type": "React.ReactNode",
|
|
204
|
+
"required": false,
|
|
205
|
+
"origin": "own",
|
|
206
|
+
"description": "Decorative mark above the title. Rendered `aria-hidden`: the title already\nsays what this is, and an icon that repeats it announces twice."
|
|
207
|
+
},
|
|
208
|
+
"size": {
|
|
209
|
+
"type": "EmptyStateSize | undefined",
|
|
210
|
+
"required": false,
|
|
211
|
+
"origin": "own",
|
|
212
|
+
"description": "Defaults to `panel`.",
|
|
213
|
+
"values": [
|
|
214
|
+
"inline",
|
|
215
|
+
"page",
|
|
216
|
+
"panel"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"headingLevel": {
|
|
220
|
+
"type": "EmptyStateHeadingLevel | undefined",
|
|
221
|
+
"required": false,
|
|
222
|
+
"origin": "own",
|
|
223
|
+
"description": "Heading level for the title. Defaults to `h3`; only the page knows its own\noutline, so it is a prop rather than a constant."
|
|
224
|
+
},
|
|
225
|
+
"children": {
|
|
226
|
+
"type": "React.ReactNode",
|
|
227
|
+
"required": false,
|
|
228
|
+
"origin": "own"
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"propCounts": {
|
|
232
|
+
"own": 9,
|
|
233
|
+
"aria": 0,
|
|
234
|
+
"dom": 276,
|
|
235
|
+
"other": 0
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "ionbase-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"generated": "by scripts/build-meta.mjs — do not edit; intent lives in meta/*.json",
|
|
5
5
|
"hooks": [
|
|
6
6
|
"useToast"
|
|
@@ -323,7 +323,6 @@
|
|
|
323
323
|
"--spacing-16",
|
|
324
324
|
"--spacing-20",
|
|
325
325
|
"--spacing-32",
|
|
326
|
-
"--spacing-4",
|
|
327
326
|
"--spacing-40",
|
|
328
327
|
"--spacing-48",
|
|
329
328
|
"--spacing-8",
|
|
@@ -335,9 +334,9 @@
|
|
|
335
334
|
"--text-error",
|
|
336
335
|
"--text-secondary",
|
|
337
336
|
"--type-body",
|
|
338
|
-
"--type-body-lg",
|
|
339
|
-
"--type-body-lg-line-height",
|
|
340
337
|
"--type-body-line-height",
|
|
338
|
+
"--type-body-md",
|
|
339
|
+
"--type-body-md-line-height",
|
|
341
340
|
"--type-body-sm",
|
|
342
341
|
"--type-body-sm-line-height"
|
|
343
342
|
],
|
|
@@ -1594,6 +1593,25 @@
|
|
|
1594
1593
|
"information": {
|
|
1595
1594
|
"use": "a neutral fact worth marking: Draft, Scheduled"
|
|
1596
1595
|
}
|
|
1596
|
+
},
|
|
1597
|
+
"size": {
|
|
1598
|
+
"sm": {
|
|
1599
|
+
"use": "the default. 20px tall on caption type — a marker beside a row, a cell or a title"
|
|
1600
|
+
},
|
|
1601
|
+
"md": {
|
|
1602
|
+
"use": "24px on body-sm. Use when the badge sits in body copy and a caption-sized label would read as a footnote"
|
|
1603
|
+
},
|
|
1604
|
+
"lg": {
|
|
1605
|
+
"use": "32px. A page-level status the eye should reach before the prose, not a marker in a list"
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
"shape": {
|
|
1609
|
+
"pill": {
|
|
1610
|
+
"use": "the default. Fully round at every size"
|
|
1611
|
+
},
|
|
1612
|
+
"rounded": {
|
|
1613
|
+
"use": "a squarer corner that steps with the size — 4/6/8. Use when the badge sits in a grid of rectangular things and a pill reads as the odd one out"
|
|
1614
|
+
}
|
|
1597
1615
|
}
|
|
1598
1616
|
},
|
|
1599
1617
|
"slots": {
|
|
@@ -1636,13 +1654,21 @@
|
|
|
1636
1654
|
"--icon-information",
|
|
1637
1655
|
"--icon-primary",
|
|
1638
1656
|
"--icon-secondary",
|
|
1657
|
+
"--icon-size-2xs",
|
|
1658
|
+
"--icon-size-md",
|
|
1659
|
+
"--icon-size-sm",
|
|
1639
1660
|
"--icon-success",
|
|
1640
1661
|
"--icon-warning",
|
|
1641
1662
|
"--radius-full",
|
|
1663
|
+
"--radius-md",
|
|
1664
|
+
"--radius-sm",
|
|
1665
|
+
"--radius-xs",
|
|
1642
1666
|
"--spacing-2",
|
|
1667
|
+
"--spacing-20",
|
|
1668
|
+
"--spacing-24",
|
|
1669
|
+
"--spacing-32",
|
|
1643
1670
|
"--spacing-4",
|
|
1644
1671
|
"--spacing-6",
|
|
1645
|
-
"--spacing-8",
|
|
1646
1672
|
"--surface-error-subtle",
|
|
1647
1673
|
"--surface-information-subtle",
|
|
1648
1674
|
"--surface-page",
|
|
@@ -1655,6 +1681,8 @@
|
|
|
1655
1681
|
"--text-secondary",
|
|
1656
1682
|
"--text-success",
|
|
1657
1683
|
"--text-warning",
|
|
1684
|
+
"--type-body-sm",
|
|
1685
|
+
"--type-body-sm-line-height",
|
|
1658
1686
|
"--type-caption",
|
|
1659
1687
|
"--type-caption-line-height"
|
|
1660
1688
|
],
|
|
@@ -1673,6 +1701,27 @@
|
|
|
1673
1701
|
"information"
|
|
1674
1702
|
]
|
|
1675
1703
|
},
|
|
1704
|
+
"size": {
|
|
1705
|
+
"type": "BadgeSize | undefined",
|
|
1706
|
+
"required": false,
|
|
1707
|
+
"origin": "own",
|
|
1708
|
+
"description": "Figma's `Size` variant. Heights are 20 / 24 / 32, pinned rather than\nderived from padding. `sm` is the default and is the size this component\nshipped with before the ramp existed.",
|
|
1709
|
+
"values": [
|
|
1710
|
+
"sm",
|
|
1711
|
+
"md",
|
|
1712
|
+
"lg"
|
|
1713
|
+
]
|
|
1714
|
+
},
|
|
1715
|
+
"shape": {
|
|
1716
|
+
"type": "BadgeShape | undefined",
|
|
1717
|
+
"required": false,
|
|
1718
|
+
"origin": "own",
|
|
1719
|
+
"description": "Figma's `Shape` variant. `rounded` steps its corner with the size so the\nradius stays proportional; `pill` is fully round at every size.",
|
|
1720
|
+
"values": [
|
|
1721
|
+
"pill",
|
|
1722
|
+
"rounded"
|
|
1723
|
+
]
|
|
1724
|
+
},
|
|
1676
1725
|
"dot": {
|
|
1677
1726
|
"type": "boolean | undefined",
|
|
1678
1727
|
"required": false,
|
|
@@ -1692,7 +1741,7 @@
|
|
|
1692
1741
|
}
|
|
1693
1742
|
},
|
|
1694
1743
|
"propCounts": {
|
|
1695
|
-
"own":
|
|
1744
|
+
"own": 6,
|
|
1696
1745
|
"aria": 0,
|
|
1697
1746
|
"dom": 277,
|
|
1698
1747
|
"other": 0
|
|
@@ -2882,6 +2931,243 @@
|
|
|
2882
2931
|
"other": 0
|
|
2883
2932
|
}
|
|
2884
2933
|
},
|
|
2934
|
+
"EmptyState": {
|
|
2935
|
+
"name": "EmptyState",
|
|
2936
|
+
"source": "src/components/EmptyState.tsx",
|
|
2937
|
+
"propsType": "EmptyStateProps",
|
|
2938
|
+
"description": "EmptyState — the state five of this system's nine patterns require and none\nof them could render.\n\n`DataTable`, `PageShell`, `SettingsPanel`, `AssistantAnswer` and\n`HumanApproval` all specify an empty state in their recipe. Until this\nexisted each consumer invented one, which is how a design system ends up\nwith four different ways to say \"nothing here\".\n\nREASON IS REQUIRED, AND HAS NO DEFAULT\n\nThe four reasons are genuinely different situations with different correct\nactions, and the failure mode is silent: an app that shows \"No invoices yet\n— Create your first invoice\" to a user whose filter excluded everything has\ntold them their records are gone. A default would make that the easy path,\nso there isn't one.\n\n`no-results` and `error` deliberately do NOT offer a create action in the\ncontract's guidance. The first needs the filter cleared; the second needs a\nretry, and offering \"Create\" over a failed fetch invites a duplicate.\n\nNOT A LIVE REGION\n\nAn empty state replaces content, so it is what the user reads next rather\nthan something announced over what they are reading. `role=\"status\"` here\nwould interrupt on every keystroke of a filter box. If a specific flow needs\nthe announcement, the caller owns that decision — the same reasoning\nStreamingText applies to `aria-live`.\n\n`error` is rendered as `role=\"alert\"` ONLY when the caller opts in via\n`role`, for the same reason: an error empty state arriving during typing is\nstill a read, not an interruption.\n\nNo `'use client'`: nothing here holds state. The buttons passed through\n`action` carry their own boundary.",
|
|
2939
|
+
"import": "import { EmptyState } from 'ionbase-ui';",
|
|
2940
|
+
"status": "stable",
|
|
2941
|
+
"summary": "The state a region shows when it has nothing to show, and why — first run, no results, no access, or a failed fetch.",
|
|
2942
|
+
"useWhen": [
|
|
2943
|
+
"a list, table, panel or route has no content to render",
|
|
2944
|
+
"a filter or search returns nothing",
|
|
2945
|
+
"the account is not permitted to see what would be here",
|
|
2946
|
+
"the request that would have filled the region failed"
|
|
2947
|
+
],
|
|
2948
|
+
"useInstead": [
|
|
2949
|
+
{
|
|
2950
|
+
"when": "content exists and something went wrong beside it",
|
|
2951
|
+
"use": "Alert",
|
|
2952
|
+
"why": "an Alert sits with the content; an EmptyState replaces it. If there is a table on screen, the message about it is an Alert"
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
"when": "the message is transient and the content is unaffected",
|
|
2956
|
+
"use": "Toast"
|
|
2957
|
+
}
|
|
2958
|
+
],
|
|
2959
|
+
"slots": {
|
|
2960
|
+
"title": {
|
|
2961
|
+
"accepts": "text",
|
|
2962
|
+
"note": "required — it is the region's statement. Name the thing and the situation: \"No invoices match these filters\", not \"No results\""
|
|
2963
|
+
},
|
|
2964
|
+
"description": {
|
|
2965
|
+
"accepts": "text",
|
|
2966
|
+
"note": "one or two sentences: what would be here, and what puts it here"
|
|
2967
|
+
},
|
|
2968
|
+
"action": {
|
|
2969
|
+
"accepts": "Button",
|
|
2970
|
+
"note": "the action that RESOLVES this reason — clearing a filter for no-results, not creating a record"
|
|
2971
|
+
},
|
|
2972
|
+
"secondaryAction": {
|
|
2973
|
+
"accepts": "Button or Link",
|
|
2974
|
+
"note": "a lower-emphasis escape hatch — \"Contact an admin\""
|
|
2975
|
+
},
|
|
2976
|
+
"icon": {
|
|
2977
|
+
"accepts": "Icon",
|
|
2978
|
+
"note": "decorative only, rendered aria-hidden — the title already says what this is"
|
|
2979
|
+
},
|
|
2980
|
+
"children": {
|
|
2981
|
+
"accepts": "any",
|
|
2982
|
+
"note": "extra content between the description and the actions"
|
|
2983
|
+
}
|
|
2984
|
+
},
|
|
2985
|
+
"variants": {
|
|
2986
|
+
"reason": {
|
|
2987
|
+
"first-run": {
|
|
2988
|
+
"use": "nothing exists yet and the user has never created one",
|
|
2989
|
+
"note": "the only reason where a create action is the right primary"
|
|
2990
|
+
},
|
|
2991
|
+
"no-results": {
|
|
2992
|
+
"use": "records exist; the current filter or query matches none of them",
|
|
2993
|
+
"note": "the action is clearing the filter. Offering \"Create\" here tells a user with 400 records and a typo that their data is gone"
|
|
2994
|
+
},
|
|
2995
|
+
"no-access": {
|
|
2996
|
+
"use": "records exist and match, but this account may not see them",
|
|
2997
|
+
"note": "name the permission or plan. \"Nothing here\" for a permissions problem sends the user to support with the wrong question"
|
|
2998
|
+
},
|
|
2999
|
+
"error": {
|
|
3000
|
+
"use": "the request that would have filled the region failed",
|
|
3001
|
+
"note": "the action is a retry. Never a create — offering one over a failed fetch invites a duplicate record"
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
"size": {
|
|
3005
|
+
"inline": {
|
|
3006
|
+
"use": "inside something that already has a frame — a table body, a card"
|
|
3007
|
+
},
|
|
3008
|
+
"panel": {
|
|
3009
|
+
"use": "a region of a page. The default"
|
|
3010
|
+
},
|
|
3011
|
+
"page": {
|
|
3012
|
+
"use": "a whole route with nothing in it"
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
},
|
|
3016
|
+
"a11y": {
|
|
3017
|
+
"guarantees": [
|
|
3018
|
+
"the title renders as a real heading, so the region appears in the document outline",
|
|
3019
|
+
"the icon is aria-hidden, so a screen reader is not told the same thing twice"
|
|
3020
|
+
],
|
|
3021
|
+
"requires": [
|
|
3022
|
+
"a `title` that states the situation, since it is what a screen-reader user hears in place of the missing content",
|
|
3023
|
+
"`headingLevel` matching the surrounding outline — h3 is the default, but only the page knows its real level"
|
|
3024
|
+
],
|
|
3025
|
+
"notes": [
|
|
3026
|
+
"This is deliberately NOT a live region. An empty state replaces content, so it is what the user reads next rather than something announced over what they are reading — `role=\"status\"` here fires on every keystroke of a filter box. If a flow genuinely needs the announcement, pass `role` yourself.",
|
|
3027
|
+
"`headingLevel` takes 2-6 and defaults to 3. It is a number rather than a variant because it is an outline decision, not a visual one — the size prop is what changes how the title looks.",
|
|
3028
|
+
"`h1` is not available: an empty state describes a region, and the page's own title belongs to the page. Same call FullCard makes.",
|
|
3029
|
+
"Under forced-colors the reason tint is discarded, so the region draws a border to stay distinguishable from the content it replaced."
|
|
3030
|
+
]
|
|
3031
|
+
},
|
|
3032
|
+
"antiPatterns": [
|
|
3033
|
+
{
|
|
3034
|
+
"dont": "using `first-run` for an empty filter result",
|
|
3035
|
+
"do": "no-results",
|
|
3036
|
+
"why": "it is the most common empty-state bug in enterprise software: a user with 400 invoices and a typo in their filter is told they have none, and offered a button to make one"
|
|
3037
|
+
},
|
|
3038
|
+
{
|
|
3039
|
+
"dont": "\"No data\" or \"Nothing found\" as the title",
|
|
3040
|
+
"why": "it names neither the thing nor the situation, so the user cannot tell whether to create a record, clear a filter, or ask for access"
|
|
3041
|
+
},
|
|
3042
|
+
{
|
|
3043
|
+
"dont": "an EmptyState while the request is still in flight",
|
|
3044
|
+
"why": "it asserts the data is absent before anything knows, then contradicts itself a moment later"
|
|
3045
|
+
},
|
|
3046
|
+
{
|
|
3047
|
+
"dont": "a create action on `error`",
|
|
3048
|
+
"why": "the fetch failed, so nobody knows whether the record already exists — this is how duplicates get made"
|
|
3049
|
+
},
|
|
3050
|
+
{
|
|
3051
|
+
"dont": "an icon with its own `aria-label` or visible caption",
|
|
3052
|
+
"why": "the slot is decorative and the title already carries the meaning; a labelled icon makes it announce twice"
|
|
3053
|
+
},
|
|
3054
|
+
{
|
|
3055
|
+
"dont": "hiding the region entirely when it is empty",
|
|
3056
|
+
"why": "a section that vanishes is indistinguishable from one that failed to render, and the user cannot tell that they are looking at everything there is"
|
|
3057
|
+
},
|
|
3058
|
+
{
|
|
3059
|
+
"dont": "an EmptyState above a form nobody has filled in yet",
|
|
3060
|
+
"why": "a form's empty state IS its initial state — see the Form pattern. A message saying the form is empty is noise on top of a form that is visibly empty"
|
|
3061
|
+
}
|
|
3062
|
+
],
|
|
3063
|
+
"stylesheet": "src/styles/empty-state.css",
|
|
3064
|
+
"tokens": [
|
|
3065
|
+
"--font-family-sans",
|
|
3066
|
+
"--font-weight-medium",
|
|
3067
|
+
"--icon-error",
|
|
3068
|
+
"--icon-primary",
|
|
3069
|
+
"--icon-size-md",
|
|
3070
|
+
"--icon-size-xl",
|
|
3071
|
+
"--icon-tertiary",
|
|
3072
|
+
"--icon-warning",
|
|
3073
|
+
"--ion-empty-state-gap",
|
|
3074
|
+
"--ion-empty-state-icon",
|
|
3075
|
+
"--ion-empty-state-icon-size",
|
|
3076
|
+
"--ion-empty-state-pad-block",
|
|
3077
|
+
"--ion-empty-state-title",
|
|
3078
|
+
"--ion-empty-state-title-line-height",
|
|
3079
|
+
"--radius-md",
|
|
3080
|
+
"--spacing-12",
|
|
3081
|
+
"--spacing-16",
|
|
3082
|
+
"--spacing-24",
|
|
3083
|
+
"--spacing-4",
|
|
3084
|
+
"--spacing-48",
|
|
3085
|
+
"--spacing-64",
|
|
3086
|
+
"--spacing-8",
|
|
3087
|
+
"--text-default",
|
|
3088
|
+
"--text-secondary",
|
|
3089
|
+
"--type-body",
|
|
3090
|
+
"--type-body-line-height",
|
|
3091
|
+
"--type-body-md",
|
|
3092
|
+
"--type-body-md-line-height",
|
|
3093
|
+
"--type-h3",
|
|
3094
|
+
"--type-h3-line-height",
|
|
3095
|
+
"--type-h4",
|
|
3096
|
+
"--type-h4-line-height"
|
|
3097
|
+
],
|
|
3098
|
+
"props": {
|
|
3099
|
+
"reason": {
|
|
3100
|
+
"type": "EmptyStateReason",
|
|
3101
|
+
"required": true,
|
|
3102
|
+
"origin": "own",
|
|
3103
|
+
"description": "Why there is nothing here. Required, and there is no default: every\ndefault would be a guess, and the wrong guess is the bug this component\nexists to prevent.",
|
|
3104
|
+
"values": [
|
|
3105
|
+
"error",
|
|
3106
|
+
"first-run",
|
|
3107
|
+
"no-results",
|
|
3108
|
+
"no-access"
|
|
3109
|
+
]
|
|
3110
|
+
},
|
|
3111
|
+
"title": {
|
|
3112
|
+
"type": "React.ReactNode",
|
|
3113
|
+
"required": true,
|
|
3114
|
+
"origin": "own",
|
|
3115
|
+
"description": "What is missing, in the user's words. Required — it is the accessible name\nof the region, and an empty state with no statement is a blank area with\npadding.\n\nName the thing and the situation: \"No invoices match these filters\", not\n\"No results\"."
|
|
3116
|
+
},
|
|
3117
|
+
"description": {
|
|
3118
|
+
"type": "React.ReactNode",
|
|
3119
|
+
"required": false,
|
|
3120
|
+
"origin": "own",
|
|
3121
|
+
"description": "One or two sentences: what would be here, and what puts it here."
|
|
3122
|
+
},
|
|
3123
|
+
"action": {
|
|
3124
|
+
"type": "React.ReactNode",
|
|
3125
|
+
"required": false,
|
|
3126
|
+
"origin": "own",
|
|
3127
|
+
"description": "The action that resolves this state. For `no-results` that is clearing the\nfilter, NOT creating a record — see `useInstead` in the contract."
|
|
3128
|
+
},
|
|
3129
|
+
"secondaryAction": {
|
|
3130
|
+
"type": "React.ReactNode",
|
|
3131
|
+
"required": false,
|
|
3132
|
+
"origin": "own",
|
|
3133
|
+
"description": "A lower-emphasis escape hatch beside `action` — \"Contact an admin\"."
|
|
3134
|
+
},
|
|
3135
|
+
"icon": {
|
|
3136
|
+
"type": "React.ReactNode",
|
|
3137
|
+
"required": false,
|
|
3138
|
+
"origin": "own",
|
|
3139
|
+
"description": "Decorative mark above the title. Rendered `aria-hidden`: the title already\nsays what this is, and an icon that repeats it announces twice."
|
|
3140
|
+
},
|
|
3141
|
+
"size": {
|
|
3142
|
+
"type": "EmptyStateSize | undefined",
|
|
3143
|
+
"required": false,
|
|
3144
|
+
"origin": "own",
|
|
3145
|
+
"description": "Defaults to `panel`.",
|
|
3146
|
+
"values": [
|
|
3147
|
+
"inline",
|
|
3148
|
+
"page",
|
|
3149
|
+
"panel"
|
|
3150
|
+
]
|
|
3151
|
+
},
|
|
3152
|
+
"headingLevel": {
|
|
3153
|
+
"type": "EmptyStateHeadingLevel | undefined",
|
|
3154
|
+
"required": false,
|
|
3155
|
+
"origin": "own",
|
|
3156
|
+
"description": "Heading level for the title. Defaults to `h3`; only the page knows its own\noutline, so it is a prop rather than a constant."
|
|
3157
|
+
},
|
|
3158
|
+
"children": {
|
|
3159
|
+
"type": "React.ReactNode",
|
|
3160
|
+
"required": false,
|
|
3161
|
+
"origin": "own"
|
|
3162
|
+
}
|
|
3163
|
+
},
|
|
3164
|
+
"propCounts": {
|
|
3165
|
+
"own": 9,
|
|
3166
|
+
"aria": 0,
|
|
3167
|
+
"dom": 276,
|
|
3168
|
+
"other": 0
|
|
3169
|
+
}
|
|
3170
|
+
},
|
|
2885
3171
|
"FullCard": {
|
|
2886
3172
|
"name": "FullCard",
|
|
2887
3173
|
"source": "src/components/FullCard.tsx",
|
package/dist/meta/contrast.json
CHANGED
|
@@ -4997,6 +4997,24 @@
|
|
|
4997
4997
|
"variant": "primary-soft"
|
|
4998
4998
|
}
|
|
4999
4999
|
}
|
|
5000
|
+
},
|
|
5001
|
+
{
|
|
5002
|
+
"fg": "--text-link",
|
|
5003
|
+
"bg": "--surface-primary-subtle-hover",
|
|
5004
|
+
"mode": "Dark",
|
|
5005
|
+
"kind": "defect",
|
|
5006
|
+
"ratio": 4.25,
|
|
5007
|
+
"reason": "Citation on a hovered primary-subtle surface. The THIRD component to land on #0e2d8b in Dark — surface/primary-tint and surface/primary-subtle/hover both resolve to it, and text/link and text/primary both read 4.25:1 against it. One primitive shift fixes all three. Undocumented until 3 Sep 2026, when the gate was made to count its own deferred findings instead of a comment claiming there were three.",
|
|
5008
|
+
"affects": [
|
|
5009
|
+
"Citation"
|
|
5010
|
+
],
|
|
5011
|
+
"surfacedBy": "Citation, hover",
|
|
5012
|
+
"appliesTo": {
|
|
5013
|
+
"component": "Citation",
|
|
5014
|
+
"props": {
|
|
5015
|
+
"href": "set"
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5000
5018
|
}
|
|
5001
5019
|
],
|
|
5002
5020
|
"skipped": []
|
package/dist/meta/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "ionbase-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
|
|
5
5
|
"hooks": [
|
|
6
6
|
"useToast"
|
|
@@ -170,6 +170,15 @@
|
|
|
170
170
|
"warning",
|
|
171
171
|
"error",
|
|
172
172
|
"information"
|
|
173
|
+
],
|
|
174
|
+
"size": [
|
|
175
|
+
"sm",
|
|
176
|
+
"md",
|
|
177
|
+
"lg"
|
|
178
|
+
],
|
|
179
|
+
"shape": [
|
|
180
|
+
"pill",
|
|
181
|
+
"rounded"
|
|
173
182
|
]
|
|
174
183
|
},
|
|
175
184
|
"detail": "dist/meta/Badge.json",
|
|
@@ -262,6 +271,25 @@
|
|
|
262
271
|
"detail": "dist/meta/Divider.json",
|
|
263
272
|
"hasIntent": true
|
|
264
273
|
},
|
|
274
|
+
"EmptyState": {
|
|
275
|
+
"summary": "The state a region shows when it has nothing to show, and why — first run, no results, no access, or a failed fetch.",
|
|
276
|
+
"status": "stable",
|
|
277
|
+
"variants": {
|
|
278
|
+
"reason": [
|
|
279
|
+
"error",
|
|
280
|
+
"first-run",
|
|
281
|
+
"no-results",
|
|
282
|
+
"no-access"
|
|
283
|
+
],
|
|
284
|
+
"size": [
|
|
285
|
+
"inline",
|
|
286
|
+
"page",
|
|
287
|
+
"panel"
|
|
288
|
+
]
|
|
289
|
+
},
|
|
290
|
+
"detail": "dist/meta/EmptyState.json",
|
|
291
|
+
"hasIntent": true
|
|
292
|
+
},
|
|
265
293
|
"FullCard": {
|
|
266
294
|
"summary": "A full-bleed row: a text column beside a framed media panel, mirrored by `alignment`.",
|
|
267
295
|
"status": "stable",
|