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,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.81.0",
3
+ "version": "0.84.0",
4
4
  "figmaFile": "gaLbGd0QNb1fUl6BjSpfBA",
5
5
  "figmaExported": "2026-09-25",
6
6
  "usage": "Given a Figma node id or component name, look it up here for the React component, its import, and how each Figma property maps onto a real prop. Every mapping is verified against both the Figma export and the TypeScript API on each build.",
@@ -1129,37 +1129,52 @@
1129
1129
  "docs": "components/menu/index.html.md",
1130
1130
  "props": {
1131
1131
  "Type": {
1132
- "ignore": "Single and Multi describe how many items may be selected, which is the caller's state. Menu renders the surface and holds no selection of its own."
1132
+ "prop": "selectionMode",
1133
+ "values": {
1134
+ "Single": "single",
1135
+ "Multi": "multiple"
1136
+ },
1137
+ "note": "selectionMode none — an action menu with no check slot — is not drawn; every Figma menu shows a selection."
1133
1138
  }
1134
1139
  }
1135
1140
  },
1136
- "639:2634": {
1141
+ "82:217": {
1137
1142
  "figmaComponent": "Menu Item",
1138
- "figmaNodeId": "639:2634",
1139
- "figmaPage": "Side Menu",
1143
+ "figmaNodeId": "82:217",
1144
+ "figmaPage": "Menu",
1140
1145
  "component": "MenuItem",
1141
1146
  "import": "import { MenuItem } from 'ionbase-ui';",
1142
1147
  "docs": "components/menu-item/index.html.md",
1143
1148
  "props": {
1144
- "Status": {
1145
- "prop": "isSelected",
1146
- "when": {
1147
- "Selected": true,
1148
- "Selected Primary": true
1149
- },
1150
- "note": "Hover is CSS. Selected Primary is a second selected styling Figma draws and code does not yet ship."
1151
- },
1152
- "Text#639:12": {
1149
+ "Label#82:0": {
1153
1150
  "prop": "children"
1154
1151
  },
1155
- "Show Icon#639:16": {
1156
- "prop": "icon"
1152
+ "Leading Icon#82:12": {
1153
+ "prop": "icon",
1154
+ "note": "wrap in Icon; omit Icon's label, the row text names it"
1157
1155
  },
1158
- "Select Icon#639:20": {
1159
- "prop": "icon"
1156
+ "Show Leading Icon#82:6": {
1157
+ "prop": "icon",
1158
+ "note": "an absent prop is the switch, so false means omit icon"
1159
+ },
1160
+ "State": {
1161
+ "prop": "isDisabled",
1162
+ "when": {
1163
+ "Disabled": true
1164
+ },
1165
+ "note": "Hover is the highlighted row, which the keyboard and the pointer share. Selected and Selected-hover are the row's key in Menu's selectedKeys, not an item prop. A row that opens a submenu is not drawn: code puts a chevron where the check sits."
1160
1166
  }
1161
1167
  }
1162
1168
  },
1169
+ "1468:271": {
1170
+ "figmaComponent": "Menu Section Title",
1171
+ "figmaNodeId": "1468:271",
1172
+ "figmaPage": "Menu",
1173
+ "component": "MenuSection",
1174
+ "import": "import { MenuSection } from 'ionbase-ui';",
1175
+ "docs": "components/menu-section/index.html.md",
1176
+ "props": {}
1177
+ },
1163
1178
  "792:1537": {
1164
1179
  "figmaComponent": "Modal",
1165
1180
  "figmaNodeId": "792:1537",
@@ -1268,6 +1283,42 @@
1268
1283
  }
1269
1284
  }
1270
1285
  },
1286
+ "1473:242": {
1287
+ "figmaComponent": "Page Header",
1288
+ "figmaNodeId": "1473:242",
1289
+ "figmaPage": "Page Header",
1290
+ "component": "PageHeader",
1291
+ "import": "import { PageHeader } from 'ionbase-ui';",
1292
+ "docs": "components/page-header/index.html.md",
1293
+ "props": {
1294
+ "Title#1473:0": {
1295
+ "prop": "title"
1296
+ },
1297
+ "Description#1473:1": {
1298
+ "prop": "description"
1299
+ },
1300
+ "Show Description#1473:4": {
1301
+ "prop": "description",
1302
+ "note": "an absent prop is the switch, so false means omit description"
1303
+ },
1304
+ "Show Breadcrumb#1473:2": {
1305
+ "prop": "breadcrumb",
1306
+ "note": "pass a Breadcrumb; false means omit breadcrumb"
1307
+ },
1308
+ "Show Status#1473:3": {
1309
+ "prop": "status",
1310
+ "note": "a Badge for the record's state; false means omit status"
1311
+ },
1312
+ "Show Actions#1473:5": {
1313
+ "prop": "actions",
1314
+ "note": "Buttons, most important last, and a MenuTrigger with an icon-only Button for the rest; false means omit actions"
1315
+ },
1316
+ "Show Below#1473:6": {
1317
+ "prop": "children",
1318
+ "note": "the row beneath — the page's Tabs or its filters; false means no children"
1319
+ }
1320
+ }
1321
+ },
1271
1322
  "1291:503": {
1272
1323
  "figmaComponent": "Pagination",
1273
1324
  "figmaNodeId": "1291:503",
@@ -3431,37 +3482,52 @@
3431
3482
  "docs": "components/menu/index.html.md",
3432
3483
  "props": {
3433
3484
  "Type": {
3434
- "ignore": "Single and Multi describe how many items may be selected, which is the caller's state. Menu renders the surface and holds no selection of its own."
3485
+ "prop": "selectionMode",
3486
+ "values": {
3487
+ "Single": "single",
3488
+ "Multi": "multiple"
3489
+ },
3490
+ "note": "selectionMode none — an action menu with no check slot — is not drawn; every Figma menu shows a selection."
3435
3491
  }
3436
3492
  }
3437
3493
  },
3438
3494
  "Menu Item": {
3439
3495
  "figmaComponent": "Menu Item",
3440
- "figmaNodeId": "639:2634",
3441
- "figmaPage": "Side Menu",
3496
+ "figmaNodeId": "82:217",
3497
+ "figmaPage": "Menu",
3442
3498
  "component": "MenuItem",
3443
3499
  "import": "import { MenuItem } from 'ionbase-ui';",
3444
3500
  "docs": "components/menu-item/index.html.md",
3445
3501
  "props": {
3446
- "Status": {
3447
- "prop": "isSelected",
3448
- "when": {
3449
- "Selected": true,
3450
- "Selected Primary": true
3451
- },
3452
- "note": "Hover is CSS. Selected Primary is a second selected styling Figma draws and code does not yet ship."
3453
- },
3454
- "Text#639:12": {
3502
+ "Label#82:0": {
3455
3503
  "prop": "children"
3456
3504
  },
3457
- "Show Icon#639:16": {
3458
- "prop": "icon"
3505
+ "Leading Icon#82:12": {
3506
+ "prop": "icon",
3507
+ "note": "wrap in Icon; omit Icon's label, the row text names it"
3459
3508
  },
3460
- "Select Icon#639:20": {
3461
- "prop": "icon"
3509
+ "Show Leading Icon#82:6": {
3510
+ "prop": "icon",
3511
+ "note": "an absent prop is the switch, so false means omit icon"
3512
+ },
3513
+ "State": {
3514
+ "prop": "isDisabled",
3515
+ "when": {
3516
+ "Disabled": true
3517
+ },
3518
+ "note": "Hover is the highlighted row, which the keyboard and the pointer share. Selected and Selected-hover are the row's key in Menu's selectedKeys, not an item prop. A row that opens a submenu is not drawn: code puts a chevron where the check sits."
3462
3519
  }
3463
3520
  }
3464
3521
  },
3522
+ "Menu Section Title": {
3523
+ "figmaComponent": "Menu Section Title",
3524
+ "figmaNodeId": "1468:271",
3525
+ "figmaPage": "Menu",
3526
+ "component": "MenuSection",
3527
+ "import": "import { MenuSection } from 'ionbase-ui';",
3528
+ "docs": "components/menu-section/index.html.md",
3529
+ "props": {}
3530
+ },
3465
3531
  "Modal": {
3466
3532
  "figmaComponent": "Modal",
3467
3533
  "figmaNodeId": "792:1537",
@@ -3570,6 +3636,42 @@
3570
3636
  }
3571
3637
  }
3572
3638
  },
3639
+ "Page Header": {
3640
+ "figmaComponent": "Page Header",
3641
+ "figmaNodeId": "1473:242",
3642
+ "figmaPage": "Page Header",
3643
+ "component": "PageHeader",
3644
+ "import": "import { PageHeader } from 'ionbase-ui';",
3645
+ "docs": "components/page-header/index.html.md",
3646
+ "props": {
3647
+ "Title#1473:0": {
3648
+ "prop": "title"
3649
+ },
3650
+ "Description#1473:1": {
3651
+ "prop": "description"
3652
+ },
3653
+ "Show Description#1473:4": {
3654
+ "prop": "description",
3655
+ "note": "an absent prop is the switch, so false means omit description"
3656
+ },
3657
+ "Show Breadcrumb#1473:2": {
3658
+ "prop": "breadcrumb",
3659
+ "note": "pass a Breadcrumb; false means omit breadcrumb"
3660
+ },
3661
+ "Show Status#1473:3": {
3662
+ "prop": "status",
3663
+ "note": "a Badge for the record's state; false means omit status"
3664
+ },
3665
+ "Show Actions#1473:5": {
3666
+ "prop": "actions",
3667
+ "note": "Buttons, most important last, and a MenuTrigger with an icon-only Button for the rest; false means omit actions"
3668
+ },
3669
+ "Show Below#1473:6": {
3670
+ "prop": "children",
3671
+ "note": "the row beneath — the page's Tabs or its filters; false means no children"
3672
+ }
3673
+ }
3674
+ },
3573
3675
  "Pagination": {
3574
3676
  "figmaComponent": "Pagination",
3575
3677
  "figmaNodeId": "1291:503",
@@ -4616,15 +4718,16 @@
4616
4718
  "Chart Legend Item": "One entry of Chart Legend — `items[n]` in code, `{ label, series }`. Its Shape follows the legend’s `shape`; Series picks chart/1…8. Internal to the legend.",
4617
4719
  "Chart Tooltip Row": "One entry of Chart Tooltip — `rows[n]` in code, `{ label, value, series }`. Series picks the swatch from chart/1…8, exactly as `series` does. Internal to the tooltip the way Pagination Item is internal to Pagination.",
4618
4720
  "Combobox Menu": "The list Combobox opens. It is not a separate export: the React component renders its own menu, because the two are one control and splitting them would let a caller mount a listbox with no combobox to own it. Drawn separately in Figma because a frame cannot both hug a field and overlay a list on top of it.",
4721
+ "Command Palette Item": "One row of the palette, drawn so Active and Disabled can be seen. There is no code export behind it and there should not be: rows are built from the palette's `commands` array, where the Label, Description, Icon and shortcut are fields of one command — `label`, `description`, `icon`, `shortcut` — and Active is the keyboard's position, not a prop. The same call Combobox Menu gets.",
4619
4722
  "Form Field": "Label, helper text and error wiring are built into Input and Select rather than a separate wrapper — the wrapper is how Figma composes them, not how React does.",
4620
4723
  "Line": "An internal part of the Scroll Progress rail, not a component a caller composes.",
4621
- "Menu Section Title": "No code counterpart yet. It is a heading inside a menu surface; nothing renders it.",
4622
4724
  "Pagination Item": "The cell inside Pagination — a page number, an arrow or the truncation ellipsis. Figma needs it as its own set to hold the Size x State matrix; a caller never places one, so it is internal to Pagination the way Line and Progress are internal to Scroll Progress.",
4623
4725
  "Progress": "As Line — internal to ScrollProgress.",
4624
4726
  "Progress Heading": "As Line — internal to ScrollProgress.",
4625
4727
  "Screen Frame": "The frame FullCard draws around its media slot. The component owns it, so there is nothing for a caller to place.",
4626
- "Table/Cell Text": "TableCell covers Figma's Table Cell and Cell Text together; the two are never used apart in the design. Recorded in TableCell's contract.",
4627
- "Command Palette Item": "One row of the palette, drawn so Active and Disabled can be seen. There is no code export behind it and there should not be: rows are built from the palette's `commands` array, where the Label, Description, Icon and shortcut are fields of one command — `label`, `description`, `icon`, `shortcut` — and Active is the keyboard's position, not a prop. The same call Combobox Menu gets."
4728
+ "Side Menu Item": "A 32-tall side-navigation row on the Side Menu page, which predates Sidebar. It shared the name Menu Item with the real one (82:217) until 25 Sep 2026, and because the export keyed on the name it silently replaced it — the mapping, the gates and the Dev Mode block all pointed here. Sidebar Item is the side-navigation row code ships.",
4729
+ "Side Menu Section Title": "The section heading of the same pre-Sidebar side menu. Menu Section Title (1468:271) is the menu's own, drawn on the Menu page; Sidebar Section is the side navigation's.",
4730
+ "Table/Cell Text": "TableCell covers Figma's Table Cell and Cell Text together; the two are never used apart in the design. Recorded in TableCell's contract."
4628
4731
  },
4629
4732
  "codeUnmapped": {
4630
4733
  "DateRangePicker": "Drawn, but not as a set of its own: its field is Date Picker Type=Range and its calendar is Calendar Mode=Range. A Figma set maps to exactly one code component, and splitting one field into two sets to satisfy that would draw the same box twice. Read Date Picker’s Type note for the handoff.",
@@ -4635,6 +4738,7 @@
4635
4738
  "Table": "Figma draws Table Row and Table Cell; the table itself is the caller assembling them. Density, the one property Table owns, is mapped on Table Cell.",
4636
4739
  "TableBody": "As TableHead.",
4637
4740
  "TableHead": "As Table — a code-side grouping element. Figma expresses a header row as a Table Row variant, not a separate component.",
4638
- "ToastProvider": "Figma draws Toast, which is mapped. The provider is placement, queueing and the live region — runtime behaviour with nothing to draw."
4741
+ "ToastProvider": "Figma draws Toast, which is mapped. The provider is placement, queueing and the live region — runtime behaviour with nothing to draw.",
4742
+ "MenuTrigger": "Composition, not a drawing: a Button followed by a Menu, both already drawn and mapped. What MenuTrigger adds — open state, positioning, aria-haspopup and aria-expanded, focus on open and return on close — is runtime behaviour a static frame cannot hold, the same reason RadioGroup is here."
4639
4743
  }
4640
4744
  }
@@ -56,7 +56,8 @@
56
56
  "every day cell carries the full date as its accessible name in the reader's own language, so the narrow weekday headers are never load-bearing",
57
57
  "unavailable and disabled days are distinguished visually and in the accessible name, not by colour alone",
58
58
  "today is marked with a dot rather than a ring, so it cannot be mistaken for a second selected day",
59
- "the selected fill and the segment focus box are redeclared under forced-colors, where background-color is discarded"
59
+ "the selected fill and the segment focus box are redeclared under forced-colors, where background-color is discarded",
60
+ "a typed date outside `minValue`/`maxValue` turns the field invalid and names the bound in the reader's own format, without `isInvalid` — the value is kept, not silently clamped"
60
61
  ],
61
62
  "requires": [
62
63
  "`label`, or an `aria-label` when no visible label is rendered",
@@ -164,7 +165,7 @@
164
165
  "type": "React.ReactNode",
165
166
  "required": false,
166
167
  "origin": "own",
167
- "description": "Replaces the helper text when `isInvalid` is set."
168
+ "description": "Replaces the helper text while the field is invalid — `isInvalid`, or its own bounds check."
168
169
  },
169
170
  "isInvalid": {
170
171
  "type": "boolean | undefined",
@@ -49,7 +49,8 @@
49
49
  "preset rows are buttons in a labelled group carrying aria-pressed, so 'which shortcut is active' is answerable without seeing the tint",
50
50
  "the selected preset is marked by weight as well as by background, so it does not rely on colour alone",
51
51
  "range endpoints and the band between them keep their fill under forced-colors, where background-color is otherwise discarded",
52
- "the rail stacks above the grids below 32rem rather than being hidden, because shortcuts matter most on the device where dragging across two months is worst"
52
+ "the rail stacks above the grids below 32rem rather than being hidden, because shortcuts matter most on the device where dragging across two months is worst",
53
+ "a typed date outside `minValue`/`maxValue`, or an end before the start, turns the field invalid with React Aria's localized message, without `isInvalid` — the value is kept, not silently clamped"
53
54
  ],
54
55
  "requires": [
55
56
  "`label`, or an `aria-label` when no visible label is rendered",
@@ -150,7 +151,7 @@
150
151
  "type": "React.ReactNode",
151
152
  "required": false,
152
153
  "origin": "own",
153
- "description": "Replaces the helper text when `isInvalid` is set."
154
+ "description": "Replaces the helper text while the field is invalid — `isInvalid`, or its own bounds check."
154
155
  },
155
156
  "isInvalid": {
156
157
  "type": "boolean | undefined",
@@ -2,65 +2,108 @@
2
2
  "name": "Menu",
3
3
  "source": "src/components/Menu.tsx",
4
4
  "propsType": "MenuProps",
5
- "description": "Menu is the list surface, not a popover.\n\nFigma models the list alone — there is no trigger, anchor or open state in\nthe design — so this renders the surface and nothing else. Positioning it\nagainst a button is the caller's job until Figma has a component that says\nhow. Building a popover here would be inventing design, and it would be the\nhard half to unpick later.\n\n`role=\"menu\"` is deliberately NOT set. A real ARIA menu owes the user\nroving-tabindex arrow navigation, typeahead and focus containment; claiming\nthe role without them is worse for a screen-reader user than an honest list,\nbecause it promises interactions that are not there. When the popover exists,\nthe role comes with it.",
5
+ "description": "Menu — Figma `Menu` (82:306), `Menu Item` and `Menu Section Title`.\n\nA real ARIA menu: `role=\"menu\"`, one tab stop, arrow keys, Home and End,\ntypeahead, and disabled rows listed but skipped. Figma's `Type` is\n`selectionMode` — Single draws one check, Multi draws several — and with\na selection mode the rows become `menuitemradio` / `menuitemcheckbox` and\nannounce their checked state.\n\nOn its own it is the surface only, rendered in flow. Inside a MenuTrigger it\nfloats, opens and closes, is named by its trigger, and can open submenus.",
6
6
  "import": "import { Menu } from 'ionbase-ui';",
7
7
  "status": "stable",
8
- "summary": "The list surface only — no trigger, no anchor, no open state. Positioning it is the caller's job.",
8
+ "summary": "An ARIA menu: one tab stop, arrow keys, typeahead, and optional single or multiple selection. Children are MenuItem and MenuSection. Inside a MenuTrigger it floats, opens and closes, and can open submenus; on its own it renders in flow.",
9
9
  "useWhen": [
10
- "rendering a list of actions or options as a surface you have already positioned",
11
- "the rows need icons, a selected check, or two lines — more than a native <select> can hold"
10
+ "a list of actions or choices for the thing the user is on — row actions, a workspace switcher, a sort order",
11
+ "the rows need icons, a selected check, or grouping under headings — more than a native <select> can hold"
12
12
  ],
13
13
  "useInstead": [
14
14
  {
15
- "when": "you want a self-contained dropdown with a trigger and open state",
16
- "use": "Popover",
17
- "why": "Menu is the surface alone; Popover owns the trigger, positioning, focus containment and Escape"
15
+ "when": "a button opens it",
16
+ "use": "MenuTrigger",
17
+ "why": "it gives the trigger aria-haspopup and aria-expanded, opens on ArrowDown, and closes on an action and returns focus — a Popover round a Menu does none of that"
18
18
  },
19
19
  {
20
- "when": "one value is picked from a modest list of plain text options",
20
+ "when": "one value is picked for a form field from a modest list of plain text options",
21
21
  "use": "Select",
22
- "why": "the native picker brings keyboard handling, type-ahead and the mobile platform picker for free"
22
+ "why": "a form value is a listbox, not a menu; the native picker brings type-ahead and the mobile platform picker for free"
23
23
  },
24
24
  {
25
- "when": "it is primary navigation rather than a dropdown",
26
- "use": "NavItem"
25
+ "when": "the user types to narrow a long list down to one value",
26
+ "use": "Combobox"
27
+ },
28
+ {
29
+ "when": "it is primary navigation rather than a list of actions",
30
+ "use": "NavItem",
31
+ "why": "a menu's rows are actions; links to pages belong in the document's navigation, where a screen reader's landmarks find them"
32
+ },
33
+ {
34
+ "when": "any action or page should be reachable by searching",
35
+ "use": "CommandPalette"
27
36
  }
28
37
  ],
29
38
  "composition": {
30
39
  "order": [
31
40
  "Menu",
41
+ "MenuSection",
32
42
  "MenuItem"
33
43
  ],
34
- "example": "<Menu><MenuItem icon={<Icon as={Copy} />}>Duplicate</MenuItem><MenuItem isSelected>Archive</MenuItem></Menu>"
44
+ "note": "MenuItem and MenuSection are collection elements: Menu reads them from its DIRECT children to build the collection, and neither renders anything itself. Every MenuItem needs a `key`, and `onAction`, `selectedKeys` and `disabledKeys` all speak in those keys. To open it from a button, put the Button and the Menu inside a MenuTrigger.",
45
+ "example": "<Menu aria-label=\"Row actions\" onAction={(key) => run(key)}><MenuSection title=\"Edit\"><MenuItem key=\"rename\" icon={<Icon as={Pencil} />}>Rename</MenuItem><MenuItem key=\"duplicate\">Duplicate</MenuItem></MenuSection><MenuSection aria-label=\"Danger\"><MenuItem key=\"archive\" isDisabled>Archive</MenuItem></MenuSection></Menu>"
46
+ },
47
+ "variants": {
48
+ "selectionMode": {
49
+ "none": {
50
+ "use": "the default — a list of actions. Rows are `menuitem`, and there is no check slot"
51
+ },
52
+ "single": {
53
+ "use": "Figma's Type=Single — one current choice, such as a sort order or the active workspace. Rows are `menuitemradio`"
54
+ },
55
+ "multiple": {
56
+ "use": "Figma's Type=Multi — independent toggles, such as visible columns. Rows are `menuitemcheckbox`"
57
+ }
58
+ }
35
59
  },
36
60
  "slots": {
37
61
  "children": {
38
- "accepts": "MenuItem"
62
+ "accepts": [
63
+ "MenuItem",
64
+ "MenuSection"
65
+ ]
39
66
  }
40
67
  },
41
68
  "a11y": {
42
- "role": "none — it is a plain <ul>",
69
+ "role": "menu",
43
70
  "guarantees": [
44
- "no `role=\"menu\"`, deliberately: a real ARIA menu owes the user roving tabindex, arrow-key navigation and typeahead, and claiming the role without them promises interactions that are not there"
71
+ "one tab stop; arrow keys move between rows, Home and End jump, and typing a letter moves to the next row starting with it",
72
+ "disabled rows are listed and announced as unavailable, and the arrow keys skip them",
73
+ "with a selection mode, rows are `menuitemradio` or `menuitemcheckbox` and announce `aria-checked` — the check glyph is never the only signal",
74
+ "the pointer and the keyboard share one highlighted row: hovering a row focuses it"
75
+ ],
76
+ "requires": [
77
+ "an accessible name — `aria-label` or `aria-labelledby` naming what the actions are for"
45
78
  ],
46
79
  "notes": [
47
- "Each MenuItem is a real <button>, so Tab reaches every row. That is honest, and it is not the same as arrow-key menu navigation.",
48
- "The role arrives when the popover that owns it does."
80
+ "A menu is not a form control. It has no `name` and submits nothing; `selectedKeys` is state you hold.",
81
+ "Titled sections are named groups. An untitled section needs `aria-label`, and is set off by a rule.",
82
+ "Submenus need a MenuTrigger. In a standalone Menu a row with MenuItem children renders as a plain row: there is no root to open a submenu from."
49
83
  ]
50
84
  },
51
85
  "antiPatterns": [
52
86
  {
53
- "dont": "adding `role=\"menu\"` yourself",
54
- "why": "the keyboard behaviour that role promises is not implemented; an honest list is better for a screen-reader user than a lying menu"
87
+ "dont": "wrapping MenuItems in a <div>, a fragment or your own component inside Menu",
88
+ "why": "Menu reads its collection from its direct children; a wrapped row is invisible to it and renders nothing"
55
89
  },
56
90
  {
57
- "dont": "using Menu as a dropdown without positioning it",
58
- "why": "it renders in flow — it has no anchor logic of its own"
91
+ "dont": "an `onClick` on MenuItem",
92
+ "why": "MenuItem renders nothing, so there is no element to click. Pass `onAction` to Menu; it receives the item's key, from the pointer, Enter and Space alike"
93
+ },
94
+ {
95
+ "dont": "using Menu for a form field's value",
96
+ "why": "a screen reader announces a menu as actions, and a form cannot read it. Use Select or Combobox"
97
+ },
98
+ {
99
+ "dont": "putting links to other pages in a Menu",
100
+ "why": "they vanish from the page's link list and landmarks. Navigation belongs in NavItem or Sidebar"
59
101
  }
60
102
  ],
61
103
  "stylesheet": "src/styles/menu.css",
62
104
  "tokens": [
63
105
  "--border-focus",
106
+ "--border-strong",
64
107
  "--border-subtle",
65
108
  "--border-width-default",
66
109
  "--border-width-thick",
@@ -72,10 +115,13 @@
72
115
  "--icon-size-sm",
73
116
  "--ion-duration-base",
74
117
  "--ion-ease-out",
118
+ "--ion-menu-min-width",
119
+ "--ion-shadow-shadow-lg",
75
120
  "--radius-sm",
76
121
  "--radius-xl",
77
122
  "--spacing-12",
78
123
  "--spacing-2",
124
+ "--spacing-4",
79
125
  "--spacing-40",
80
126
  "--spacing-6",
81
127
  "--spacing-8",
@@ -85,20 +131,148 @@
85
131
  "--text-disabled",
86
132
  "--text-primary",
87
133
  "--text-secondary",
134
+ "--text-tertiary",
88
135
  "--type-body",
89
- "--type-body-line-height"
136
+ "--type-body-line-height",
137
+ "--type-caption",
138
+ "--type-caption-line-height"
90
139
  ],
91
140
  "props": {
92
- "children": {
93
- "type": "React.ReactNode",
141
+ "className": {
142
+ "type": "string | undefined",
143
+ "required": false,
144
+ "origin": "own"
145
+ },
146
+ "style": {
147
+ "type": "React.CSSProperties | undefined",
94
148
  "required": false,
95
149
  "origin": "own"
150
+ },
151
+ "escapeKeyBehavior": {
152
+ "type": "\"none\" | \"clearSelection\" | undefined",
153
+ "required": false,
154
+ "origin": "aria",
155
+ "description": "Whether pressing the escape key should clear selection in the menu or not.\n\nMost experiences should not modify this option as it eliminates a keyboard user's ability to\neasily clear selection. Only use if the escape key is being handled externally or should not\ntrigger selection clearing contextually.",
156
+ "values": [
157
+ "none",
158
+ "clearSelection"
159
+ ],
160
+ "tags": {
161
+ "default": "'clearSelection'"
162
+ }
163
+ },
164
+ "autoFocus": {
165
+ "type": "boolean | FocusStrategy | undefined",
166
+ "required": false,
167
+ "origin": "aria",
168
+ "description": "Where the focus should be set."
169
+ },
170
+ "shouldFocusWrap": {
171
+ "type": "boolean | undefined",
172
+ "required": false,
173
+ "origin": "aria",
174
+ "description": "Whether keyboard navigation is circular."
175
+ },
176
+ "onAction": {
177
+ "type": "((key: Key, value: T) => void) | undefined",
178
+ "required": false,
179
+ "origin": "aria",
180
+ "description": "Handler that is called when an item is selected."
181
+ },
182
+ "onClose": {
183
+ "type": "(() => void) | undefined",
184
+ "required": false,
185
+ "origin": "aria",
186
+ "description": "Handler that is called when the menu should close after selecting an item."
187
+ },
188
+ "children": {
189
+ "type": "CollectionChildren<T>",
190
+ "required": true,
191
+ "origin": "aria",
192
+ "description": "The contents of the collection."
193
+ },
194
+ "items": {
195
+ "type": "Iterable<T> | undefined",
196
+ "required": false,
197
+ "origin": "aria",
198
+ "description": "Item objects in the collection."
199
+ },
200
+ "disabledKeys": {
201
+ "type": "Iterable<Key> | undefined",
202
+ "required": false,
203
+ "origin": "aria",
204
+ "description": "The item keys that are disabled. These items cannot be selected, focused, or otherwise\ninteracted with."
205
+ },
206
+ "selectionMode": {
207
+ "type": "SelectionMode | undefined",
208
+ "required": false,
209
+ "origin": "aria",
210
+ "description": "The type of selection that is allowed in the collection.",
211
+ "values": [
212
+ "multiple",
213
+ "none",
214
+ "single"
215
+ ]
216
+ },
217
+ "disallowEmptySelection": {
218
+ "type": "boolean | undefined",
219
+ "required": false,
220
+ "origin": "aria",
221
+ "description": "Whether the collection allows empty selection."
222
+ },
223
+ "selectedKeys": {
224
+ "type": "Iterable<Key> | \"all\" | undefined",
225
+ "required": false,
226
+ "origin": "aria",
227
+ "description": "The currently selected keys in the collection (controlled)."
228
+ },
229
+ "defaultSelectedKeys": {
230
+ "type": "Iterable<Key> | \"all\" | undefined",
231
+ "required": false,
232
+ "origin": "aria",
233
+ "description": "The initial selected keys in the collection (uncontrolled)."
234
+ },
235
+ "onSelectionChange": {
236
+ "type": "((keys: Selection) => void) | undefined",
237
+ "required": false,
238
+ "origin": "aria",
239
+ "description": "Handler that is called when the selection changes."
240
+ },
241
+ "id": {
242
+ "type": "string | undefined",
243
+ "required": false,
244
+ "origin": "aria",
245
+ "description": "The element's unique identifier. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id)."
246
+ },
247
+ "aria-label": {
248
+ "type": "string | undefined",
249
+ "required": false,
250
+ "origin": "aria",
251
+ "description": "Defines a string value that labels the current element."
252
+ },
253
+ "aria-labelledby": {
254
+ "type": "string | undefined",
255
+ "required": false,
256
+ "origin": "aria",
257
+ "description": "Identifies the element (or elements) that labels the current element."
258
+ },
259
+ "aria-describedby": {
260
+ "type": "string | undefined",
261
+ "required": false,
262
+ "origin": "aria",
263
+ "description": "Identifies the element (or elements) that describes the object."
264
+ },
265
+ "aria-details": {
266
+ "type": "string | undefined",
267
+ "required": false,
268
+ "origin": "aria",
269
+ "description": "Identifies the element (or elements) that provide a detailed, extended description for the\nobject."
96
270
  }
97
271
  },
98
272
  "propCounts": {
99
- "own": 1,
100
- "aria": 0,
101
- "dom": 277,
273
+ "own": 2,
274
+ "aria": 18,
275
+ "dom": 0,
102
276
  "other": 0
103
277
  }
104
278
  }