ionbase-ui 0.81.1 → 0.84.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/Menu.d.ts +87 -24
- package/dist/components/Menu.d.ts.map +1 -1
- package/dist/components/Menu.js +245 -28
- package/dist/components/Menu.js.map +1 -1
- package/dist/components/PageHeader.d.ts +59 -0
- package/dist/components/PageHeader.d.ts.map +1 -0
- package/dist/components/PageHeader.js +29 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/index.d.ts +4 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -1
- package/dist/components/index.js.map +1 -1
- package/dist/figma-descriptions.json +86 -76
- package/dist/figma-map.json +142 -38
- package/dist/meta/Menu.json +201 -27
- package/dist/meta/MenuItem.json +137 -17
- package/dist/meta/MenuSection.json +90 -0
- package/dist/meta/MenuTrigger.json +177 -0
- package/dist/meta/PageHeader.json +169 -0
- package/dist/meta/components.json +817 -87
- package/dist/meta/contrast.json +240 -32
- package/dist/meta/index.json +31 -3
- package/dist/meta/patterns/PageShell.json +18 -5
- package/dist/meta/patterns/index.json +2 -1
- package/dist/styles/index.css +1 -0
- package/dist/styles/menu.css +153 -32
- package/dist/styles/page-header.css +94 -0
- package/llms.txt +2 -2
- package/package.json +1 -1
package/dist/styles/menu.css
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Menu
|
|
3
3
|
*
|
|
4
|
-
* Measured from Figma `Menu` (
|
|
4
|
+
* Measured from Figma `Menu` (82:306), `Menu Item` (82:217) and
|
|
5
|
+
* `Menu Section Title` (1468:271), all on the Menu page.
|
|
5
6
|
*
|
|
6
7
|
* menu padding 6, gap 2, radius/xl, surface/default, 1px border/subtle
|
|
7
8
|
* item 40 tall, padding 8/12, gap 8, radius/sm
|
|
8
9
|
*
|
|
9
10
|
* Figma models the list only — there is no trigger, popover or positioning in
|
|
10
11
|
* the design, so there is none here. This is the surface a menu renders on;
|
|
11
|
-
* anchoring it is the caller's job until
|
|
12
|
-
*
|
|
12
|
+
* anchoring it is the caller's job until MenuTrigger exists.
|
|
13
|
+
*
|
|
14
|
+
* HIGHLIGHT FOLLOWS FOCUS, NOT THE POINTER
|
|
15
|
+
*
|
|
16
|
+
* In an ARIA menu, hovering a row moves focus to it (useMenuItem does this), so
|
|
17
|
+
* the keyboard and the pointer share one highlighted row. Figma's `Hover`
|
|
18
|
+
* therefore reads `data-focused`, not `:hover`. Styling both would light two
|
|
19
|
+
* rows at once when the pointer rests on one and the arrow keys move to another.
|
|
13
20
|
*
|
|
14
21
|
* SELECTED-HOVER IS NOT SELECTED PLUS HOVER
|
|
15
22
|
*
|
|
@@ -29,6 +36,11 @@
|
|
|
29
36
|
border-width: var(--border-width-default);
|
|
30
37
|
border-color: var(--border-subtle);
|
|
31
38
|
border-radius: var(--radius-xl);
|
|
39
|
+
|
|
40
|
+
/* Figma's Menu carries Shadow/lg (82:306) — it is drawn as a dropdown
|
|
41
|
+
* surface. Code shipped without it until 0.83.0, which only showed once the
|
|
42
|
+
* menu floated over a page. */
|
|
43
|
+
box-shadow: var(--ion-shadow-shadow-lg);
|
|
32
44
|
font-family: var(--font-family-sans);
|
|
33
45
|
list-style: none;
|
|
34
46
|
margin: 0;
|
|
@@ -50,9 +62,9 @@
|
|
|
50
62
|
font-size: var(--type-body);
|
|
51
63
|
line-height: var(--type-body-line-height);
|
|
52
64
|
font-weight: var(--font-weight-regular);
|
|
53
|
-
text-align:
|
|
54
|
-
width: 100%;
|
|
65
|
+
text-align: start;
|
|
55
66
|
cursor: pointer;
|
|
67
|
+
outline: none;
|
|
56
68
|
user-select: none;
|
|
57
69
|
transition: background-color var(--ion-duration-base) var(--ion-ease-out);
|
|
58
70
|
}
|
|
@@ -75,7 +87,7 @@
|
|
|
75
87
|
/* The check keeps its slot whether or not it is showing, so rows do not
|
|
76
88
|
* reflow as the selection moves down the list. */
|
|
77
89
|
.ion-menu__check {
|
|
78
|
-
margin-
|
|
90
|
+
margin-inline-start: auto;
|
|
79
91
|
visibility: hidden;
|
|
80
92
|
}
|
|
81
93
|
|
|
@@ -84,13 +96,7 @@
|
|
|
84
96
|
min-width: 0;
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
.ion-menu__item:hover {
|
|
89
|
-
background-color: var(--surface-hover);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.ion-menu__item[data-hovered='true'] {
|
|
99
|
+
.ion-menu__item[data-focused='true'] {
|
|
94
100
|
background-color: var(--surface-hover);
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -108,43 +114,158 @@
|
|
|
108
114
|
visibility: visible;
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
/* See the header —
|
|
112
|
-
* it. The check stays primary; everything else returns to the resting
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
background-color: var(--surface-hover);
|
|
116
|
-
color: var(--text-secondary);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.ion-menu__item--selected:hover .ion-menu__icon {
|
|
120
|
-
color: var(--icon-default);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.ion-menu__item--selected[data-hovered='true'] {
|
|
117
|
+
/* See the header — highlighting a selected row drops the tint rather than
|
|
118
|
+
* deepening it. The check stays primary; everything else returns to the resting
|
|
119
|
+
* palette. */
|
|
120
|
+
.ion-menu__item--selected[data-focused='true'] {
|
|
125
121
|
background-color: var(--surface-hover);
|
|
126
122
|
color: var(--text-secondary);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
.ion-menu__item--selected[data-
|
|
125
|
+
.ion-menu__item--selected[data-focused='true'] .ion-menu__icon {
|
|
130
126
|
color: var(--icon-default);
|
|
131
127
|
}
|
|
132
128
|
|
|
133
|
-
.ion-menu__item
|
|
129
|
+
.ion-menu__item[data-focus-visible='true'] {
|
|
134
130
|
outline: var(--border-width-thick) solid var(--border-focus);
|
|
135
131
|
outline-offset: calc(-1 * var(--border-width-thick));
|
|
136
132
|
}
|
|
137
133
|
|
|
138
|
-
.ion-menu__item:disabled,
|
|
139
134
|
.ion-menu__item[data-disabled='true'] {
|
|
140
135
|
background-color: transparent;
|
|
141
136
|
color: var(--text-disabled);
|
|
142
137
|
cursor: not-allowed;
|
|
143
138
|
}
|
|
144
139
|
|
|
145
|
-
.ion-menu__item:disabled .ion-menu__icon,
|
|
146
|
-
.ion-menu__item:disabled .ion-menu__check,
|
|
147
140
|
.ion-menu__item[data-disabled='true'] .ion-menu__icon,
|
|
148
141
|
.ion-menu__item[data-disabled='true'] .ion-menu__check {
|
|
149
142
|
color: var(--icon-disabled);
|
|
150
143
|
}
|
|
144
|
+
|
|
145
|
+
/* ------------------------------------------------------------- submenus
|
|
146
|
+
*
|
|
147
|
+
* Figma draws no submenu row. The chevron takes the check's place at the end
|
|
148
|
+
* of the row — a submenu parent is never itself checkable — and the row keeps
|
|
149
|
+
* its highlight while its submenu is open, so the eye can find its way back.
|
|
150
|
+
*/
|
|
151
|
+
.ion-menu__chevron {
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
flex-shrink: 0;
|
|
155
|
+
margin-inline-start: auto;
|
|
156
|
+
color: var(--icon-default);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ion-menu__chevron svg {
|
|
160
|
+
width: var(--icon-size-sm);
|
|
161
|
+
height: var(--icon-size-sm);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ion-menu__chevron:dir(rtl) svg {
|
|
165
|
+
transform: scaleX(-1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.ion-menu__item--submenu[data-open='true'] {
|
|
169
|
+
background-color: var(--surface-hover);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ------------------------------------------------------------- floating
|
|
173
|
+
*
|
|
174
|
+
* The wrapper positions and paints nothing: Menu is already the surface. Its
|
|
175
|
+
* width is Figma's 240 as a floor, so a menu of short labels does not shrink
|
|
176
|
+
* to a sliver under a wide trigger, and a long one can still grow.
|
|
177
|
+
*
|
|
178
|
+
* z-index is Popover's, for Popover's reason: a menu opened from inside a
|
|
179
|
+
* dialog has to sit above it, and a toast confirming its action above both.
|
|
180
|
+
*/
|
|
181
|
+
.ion-menu-popover {
|
|
182
|
+
--ion-menu-min-width: 240px;
|
|
183
|
+
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
z-index: 1150;
|
|
187
|
+
outline: none;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.ion-menu-popover > .ion-menu {
|
|
191
|
+
min-width: var(--ion-menu-min-width);
|
|
192
|
+
min-height: 0;
|
|
193
|
+
overflow-y: auto;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ion-menu-popover__underlay {
|
|
197
|
+
position: fixed;
|
|
198
|
+
inset: 0;
|
|
199
|
+
z-index: 1140;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ------------------------------------------------------------- sections
|
|
203
|
+
*
|
|
204
|
+
* A section is an <li role="presentation"> holding a heading and a nested
|
|
205
|
+
* <ul role="group">, so both carry the list's own flex column and gap —
|
|
206
|
+
* otherwise rows inside a section would sit 2px closer together than rows
|
|
207
|
+
* outside one.
|
|
208
|
+
*/
|
|
209
|
+
.ion-menu__section {
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-direction: column;
|
|
212
|
+
gap: var(--spacing-2);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ion-menu__group {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-direction: column;
|
|
218
|
+
gap: var(--spacing-2);
|
|
219
|
+
list-style: none;
|
|
220
|
+
margin: 0;
|
|
221
|
+
padding: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* Figma `Menu Section Title` (1468:271): caption in text/tertiary, then a
|
|
226
|
+
* 1px border/strong rule filling the rest of the row, with 8/12/4 insets so
|
|
227
|
+
* the caption lines up with the row labels beneath it.
|
|
228
|
+
*
|
|
229
|
+
* It was drawn from this stylesheet on 25 Sep 2026. Before that, 0.82.0 had
|
|
230
|
+
* mapped MenuSection to a same-named heading on the Side Menu page — a
|
|
231
|
+
* pre-Sidebar side-nav drawing with a 7px gap and no insets, not a menu part.
|
|
232
|
+
*/
|
|
233
|
+
.ion-menu__section-title {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: var(--spacing-8);
|
|
237
|
+
padding: var(--spacing-8) var(--spacing-12) var(--spacing-4);
|
|
238
|
+
color: var(--text-tertiary);
|
|
239
|
+
font-size: var(--type-caption);
|
|
240
|
+
line-height: var(--type-caption-line-height);
|
|
241
|
+
font-weight: var(--font-weight-regular);
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ion-menu__section-title::after {
|
|
246
|
+
content: '';
|
|
247
|
+
flex: 1 1 auto;
|
|
248
|
+
min-width: var(--spacing-8);
|
|
249
|
+
height: var(--border-width-default);
|
|
250
|
+
background-color: var(--border-strong);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* An untitled section after the first is set off by a rule alone. Same line as
|
|
254
|
+
* the title's, so a menu mixing the two draws one kind of divider. */
|
|
255
|
+
.ion-menu__separator {
|
|
256
|
+
height: var(--border-width-default);
|
|
257
|
+
margin: var(--spacing-4) var(--spacing-12);
|
|
258
|
+
background-color: var(--border-strong);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@media (forced-colors: active) {
|
|
262
|
+
.ion-menu__item[data-focused='true'] {
|
|
263
|
+
outline: var(--border-width-thick) solid Highlight;
|
|
264
|
+
outline-offset: calc(-1 * var(--border-width-thick));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.ion-menu__section-title::after,
|
|
268
|
+
.ion-menu__separator {
|
|
269
|
+
background-color: CanvasText;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PageHeader
|
|
3
|
+
*
|
|
4
|
+
* Promoted from the demo, where `.demo-page__header` and `.demo-run-header`
|
|
5
|
+
* drew it twice with a 16 gap between title and actions, 4 between title and
|
|
6
|
+
* description. Kept.
|
|
7
|
+
*
|
|
8
|
+
* THE TITLE IS h4-SIZED, ON PURPOSE
|
|
9
|
+
*
|
|
10
|
+
* It is the page's h1 in the outline and an h4 on the type scale. An app page
|
|
11
|
+
* sits under a Header and beside a Sidebar; an h1-sized title there reads as a
|
|
12
|
+
* marketing page. Every demo screen made this call independently, and so do
|
|
13
|
+
* Carbon and Lightning. The level is semantic; the size is the product's.
|
|
14
|
+
*
|
|
15
|
+
* ACTIONS ALIGN TO THE TITLE, NOT THE DESCRIPTION
|
|
16
|
+
*
|
|
17
|
+
* `flex-start`, so the buttons sit on the title's line however long the
|
|
18
|
+
* description runs. The demo's overview used `flex-end`, and its buttons
|
|
19
|
+
* drifted down to the description's last line as the copy grew. When the
|
|
20
|
+
* row is too narrow for both, the actions wrap beneath and align to the start.
|
|
21
|
+
*/
|
|
22
|
+
.ion-page-header {
|
|
23
|
+
--ion-page-header-heading-basis: 280px;
|
|
24
|
+
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--spacing-16);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
font-family: var(--font-family-sans);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ion-page-header__main {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-wrap: wrap;
|
|
35
|
+
align-items: flex-start;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
gap: var(--spacing-16);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* The heading grows from a fixed basis, not from its content. With `auto` a
|
|
42
|
+
* long description asks for the whole row, so the actions wrapped beneath at
|
|
43
|
+
* 720px with room to spare — the test `ActionsAlignToTheTitle` caught it. From
|
|
44
|
+
* a basis, the row only wraps when it is narrower than the basis plus the
|
|
45
|
+
* actions: a phone, not a long sentence. The basis is declared on the root.
|
|
46
|
+
*/
|
|
47
|
+
.ion-page-header__heading {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex: 1 1 var(--ion-page-header-heading-basis);
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: var(--spacing-4);
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ion-page-header__title-row {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: var(--spacing-8);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ion-page-header__title {
|
|
63
|
+
margin: 0;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
color: var(--text-default);
|
|
66
|
+
font-size: var(--type-h4);
|
|
67
|
+
line-height: var(--type-h4-line-height);
|
|
68
|
+
font-weight: var(--font-weight-semibold);
|
|
69
|
+
overflow-wrap: anywhere;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ion-page-header__status {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-wrap: wrap;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: var(--spacing-8);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ion-page-header__description {
|
|
80
|
+
margin: 0;
|
|
81
|
+
max-width: 72ch;
|
|
82
|
+
color: var(--text-secondary);
|
|
83
|
+
font-size: var(--type-body);
|
|
84
|
+
line-height: var(--type-body-line-height);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ion-page-header__actions {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex: 0 1 auto;
|
|
90
|
+
min-width: 0;
|
|
91
|
+
flex-wrap: wrap;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: var(--spacing-8);
|
|
94
|
+
}
|
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> IonBase is a React design system for enterprise SaaS, built to be consumed by an agent rather than read by a developer. Every component ships a machine-readable contract carrying the judgement a type signature cannot: when to use it, what to use instead, the anti-patterns, and what it guarantees for accessibility versus what it requires of you.
|
|
4
4
|
|
|
5
|
-
Version 0.
|
|
5
|
+
Version 0.84.0. 84 components.
|
|
6
6
|
|
|
7
7
|
## Read these, in this order
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ Both are importable: `ionbase-ui/meta/index` and `ionbase-ui/meta/<Name>.json`.
|
|
|
13
13
|
|
|
14
14
|
## Components
|
|
15
15
|
|
|
16
|
-
`Accordion`, `AccordionItem`, `AgentActivity`, `AgentActivityStep`, `AgentStop`, `Alert`, `ApprovalGate`, `Avatar`, `AvatarGradient`, `AvatarGroup`, `Badge`, `Breadcrumb`, `BreadcrumbItem`, `Button`, `Card`, `ChartLegend`, `ChartTooltip`, `Checkbox`, `Citation`, `CitationList`, `CitationListItem`, `Combobox`, `CommandPalette`, `ConfidenceIndicator`, `DatePicker`, `DateRangePicker`, `Divider`, `Drawer`, `EmptyState`, `FileUpload`, `FullCard`, `Header`, `Icon`, `Input`, `Kbd`, `Link`, `Logo`, `LogoMark`, `Menu`, `MenuItem`, `Modal`, `NavItem`, `NumberInput`, `Pagination`, `PhoneInput`, `Popover`, `ProgressBar`, `PromptInput`, `Radio`, `RadioGroup`, `ScrollProgress`, `SegmentedControl`, `SegmentedControlItem`, `Select`, `SettingRow`, `Sidebar`, `SidebarItem`, `SidebarSection`, `Skeleton`, `Spinner`, `StatGroup`, `StatTile`, `Stepper`, `StepperStep`, `StreamingText`, `TabItem`, `Table`, `TableBody`, `TableCell`, `TableHead`, `TableRow`, `Tabs`, `Tag`, `TagGroup`, `Textarea`, `TimeField`, `Toast`, `ToastProvider`, `Toggle`, `ToolCall`, `Tooltip`
|
|
16
|
+
`Accordion`, `AccordionItem`, `AgentActivity`, `AgentActivityStep`, `AgentStop`, `Alert`, `ApprovalGate`, `Avatar`, `AvatarGradient`, `AvatarGroup`, `Badge`, `Breadcrumb`, `BreadcrumbItem`, `Button`, `Card`, `ChartLegend`, `ChartTooltip`, `Checkbox`, `Citation`, `CitationList`, `CitationListItem`, `Combobox`, `CommandPalette`, `ConfidenceIndicator`, `DatePicker`, `DateRangePicker`, `Divider`, `Drawer`, `EmptyState`, `FileUpload`, `FullCard`, `Header`, `Icon`, `Input`, `Kbd`, `Link`, `Logo`, `LogoMark`, `Menu`, `MenuItem`, `MenuSection`, `MenuTrigger`, `Modal`, `NavItem`, `NumberInput`, `PageHeader`, `Pagination`, `PhoneInput`, `Popover`, `ProgressBar`, `PromptInput`, `Radio`, `RadioGroup`, `ScrollProgress`, `SegmentedControl`, `SegmentedControlItem`, `Select`, `SettingRow`, `Sidebar`, `SidebarItem`, `SidebarSection`, `Skeleton`, `Spinner`, `StatGroup`, `StatTile`, `Stepper`, `StepperStep`, `StreamingText`, `TabItem`, `Table`, `TableBody`, `TableCell`, `TableHead`, `TableRow`, `Tabs`, `Tag`, `TagGroup`, `Textarea`, `TimeField`, `Toast`, `ToastProvider`, `Toggle`, `ToolCall`, `Tooltip`
|
|
17
17
|
|
|
18
18
|
## Patterns
|
|
19
19
|
|