ionbase-ui 0.32.0 → 0.38.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 +39 -39
- package/dist/figma-map.json +25 -6
- package/dist/meta/Badge.json +52 -2
- package/dist/meta/EmptyState.json +237 -0
- package/dist/meta/components.json +290 -3
- package/dist/meta/contrast.json +10648 -2360
- 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/badge.css +87 -11
- package/dist/styles/empty-state.css +164 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/tokens/base.css +7 -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.38.0",
|
|
4
4
|
"generated": "by scripts/build-meta.mjs — do not edit; intent lives in meta/*.json",
|
|
5
5
|
"hooks": [
|
|
6
6
|
"useToast"
|
|
@@ -1593,6 +1593,25 @@
|
|
|
1593
1593
|
"information": {
|
|
1594
1594
|
"use": "a neutral fact worth marking: Draft, Scheduled"
|
|
1595
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
|
+
}
|
|
1596
1615
|
}
|
|
1597
1616
|
},
|
|
1598
1617
|
"slots": {
|
|
@@ -1635,13 +1654,21 @@
|
|
|
1635
1654
|
"--icon-information",
|
|
1636
1655
|
"--icon-primary",
|
|
1637
1656
|
"--icon-secondary",
|
|
1657
|
+
"--icon-size-2xs",
|
|
1658
|
+
"--icon-size-md",
|
|
1659
|
+
"--icon-size-sm",
|
|
1638
1660
|
"--icon-success",
|
|
1639
1661
|
"--icon-warning",
|
|
1640
1662
|
"--radius-full",
|
|
1663
|
+
"--radius-md",
|
|
1664
|
+
"--radius-sm",
|
|
1665
|
+
"--radius-xs",
|
|
1641
1666
|
"--spacing-2",
|
|
1667
|
+
"--spacing-20",
|
|
1668
|
+
"--spacing-24",
|
|
1669
|
+
"--spacing-32",
|
|
1642
1670
|
"--spacing-4",
|
|
1643
1671
|
"--spacing-6",
|
|
1644
|
-
"--spacing-8",
|
|
1645
1672
|
"--surface-error-subtle",
|
|
1646
1673
|
"--surface-information-subtle",
|
|
1647
1674
|
"--surface-page",
|
|
@@ -1654,6 +1681,8 @@
|
|
|
1654
1681
|
"--text-secondary",
|
|
1655
1682
|
"--text-success",
|
|
1656
1683
|
"--text-warning",
|
|
1684
|
+
"--type-body-sm",
|
|
1685
|
+
"--type-body-sm-line-height",
|
|
1657
1686
|
"--type-caption",
|
|
1658
1687
|
"--type-caption-line-height"
|
|
1659
1688
|
],
|
|
@@ -1672,6 +1701,27 @@
|
|
|
1672
1701
|
"information"
|
|
1673
1702
|
]
|
|
1674
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
|
+
},
|
|
1675
1725
|
"dot": {
|
|
1676
1726
|
"type": "boolean | undefined",
|
|
1677
1727
|
"required": false,
|
|
@@ -1691,7 +1741,7 @@
|
|
|
1691
1741
|
}
|
|
1692
1742
|
},
|
|
1693
1743
|
"propCounts": {
|
|
1694
|
-
"own":
|
|
1744
|
+
"own": 6,
|
|
1695
1745
|
"aria": 0,
|
|
1696
1746
|
"dom": 277,
|
|
1697
1747
|
"other": 0
|
|
@@ -2881,6 +2931,243 @@
|
|
|
2881
2931
|
"other": 0
|
|
2882
2932
|
}
|
|
2883
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
|
+
},
|
|
2884
3171
|
"FullCard": {
|
|
2885
3172
|
"name": "FullCard",
|
|
2886
3173
|
"source": "src/components/FullCard.tsx",
|