ionbase-ui 0.81.0 → 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.
Files changed (42) hide show
  1. package/dist/components/DatePicker.d.ts +1 -1
  2. package/dist/components/DatePicker.d.ts.map +1 -1
  3. package/dist/components/DatePicker.js +14 -6
  4. package/dist/components/DatePicker.js.map +1 -1
  5. package/dist/components/DateRangePicker.d.ts +1 -1
  6. package/dist/components/DateRangePicker.d.ts.map +1 -1
  7. package/dist/components/DateRangePicker.js +14 -6
  8. package/dist/components/DateRangePicker.js.map +1 -1
  9. package/dist/components/Menu.d.ts +87 -24
  10. package/dist/components/Menu.d.ts.map +1 -1
  11. package/dist/components/Menu.js +245 -28
  12. package/dist/components/Menu.js.map +1 -1
  13. package/dist/components/PageHeader.d.ts +59 -0
  14. package/dist/components/PageHeader.d.ts.map +1 -0
  15. package/dist/components/PageHeader.js +29 -0
  16. package/dist/components/PageHeader.js.map +1 -0
  17. package/dist/components/TimeField.d.ts +1 -1
  18. package/dist/components/TimeField.d.ts.map +1 -1
  19. package/dist/components/index.d.ts +4 -2
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components/index.js +2 -1
  22. package/dist/components/index.js.map +1 -1
  23. package/dist/figma-descriptions.json +86 -76
  24. package/dist/figma-map.json +142 -38
  25. package/dist/meta/DatePicker.json +3 -2
  26. package/dist/meta/DateRangePicker.json +3 -2
  27. package/dist/meta/Menu.json +201 -27
  28. package/dist/meta/MenuItem.json +137 -17
  29. package/dist/meta/MenuSection.json +90 -0
  30. package/dist/meta/MenuTrigger.json +177 -0
  31. package/dist/meta/PageHeader.json +169 -0
  32. package/dist/meta/TimeField.json +1 -1
  33. package/dist/meta/components.json +841 -109
  34. package/dist/meta/contrast.json +240 -32
  35. package/dist/meta/index.json +31 -3
  36. package/dist/meta/patterns/PageShell.json +18 -5
  37. package/dist/meta/patterns/index.json +2 -1
  38. package/dist/styles/index.css +1 -0
  39. package/dist/styles/menu.css +153 -32
  40. package/dist/styles/page-header.css +94 -0
  41. package/llms.txt +2 -2
  42. package/package.json +1 -1
@@ -1,15 +1,22 @@
1
1
  /*
2
2
  * Menu
3
3
  *
4
- * Measured from Figma `Menu` (85:22440) and `Menu Item`.
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 Figma has a component that says
12
- * otherwise.
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: left;
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-left: auto;
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
- @media (hover: hover) {
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 — hovering a selected row drops the tint rather than deepening
112
- * it. The check stays primary; everything else returns to the resting palette. */
113
- @media (hover: hover) {
114
- .ion-menu__item--selected:hover {
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-hovered='true'] .ion-menu__icon {
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:focus-visible {
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.81.0. 81 components.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ionbase-ui",
3
- "version": "0.81.0",
3
+ "version": "0.84.0",
4
4
  "description": "IonBase Design System — accessible React components, styles and design tokens in one package",
5
5
  "license": "MIT",
6
6
  "type": "module",