ionbase-ui 0.59.0 → 0.70.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 (114) hide show
  1. package/dist/components/AgentActivity.d.ts +2 -1
  2. package/dist/components/AgentActivity.d.ts.map +1 -1
  3. package/dist/components/AgentActivity.js +4 -17
  4. package/dist/components/AgentActivity.js.map +1 -1
  5. package/dist/components/Calendar.d.ts +20 -0
  6. package/dist/components/Calendar.d.ts.map +1 -0
  7. package/dist/components/Calendar.js +151 -0
  8. package/dist/components/Calendar.js.map +1 -0
  9. package/dist/components/CalendarPopover.d.ts +26 -0
  10. package/dist/components/CalendarPopover.d.ts.map +1 -0
  11. package/dist/components/CalendarPopover.js +45 -0
  12. package/dist/components/CalendarPopover.js.map +1 -0
  13. package/dist/components/DateField.d.ts +15 -0
  14. package/dist/components/DateField.d.ts.map +1 -0
  15. package/dist/components/DateField.js +74 -0
  16. package/dist/components/DateField.js.map +1 -0
  17. package/dist/components/DatePicker.d.ts +81 -0
  18. package/dist/components/DatePicker.d.ts.map +1 -0
  19. package/dist/components/DatePicker.js +102 -0
  20. package/dist/components/DatePicker.js.map +1 -0
  21. package/dist/components/DateRangePicker.d.ts +117 -0
  22. package/dist/components/DateRangePicker.d.ts.map +1 -0
  23. package/dist/components/DateRangePicker.js +152 -0
  24. package/dist/components/DateRangePicker.js.map +1 -0
  25. package/dist/components/Header.d.ts +10 -0
  26. package/dist/components/Header.d.ts.map +1 -1
  27. package/dist/components/Header.js +18 -3
  28. package/dist/components/Header.js.map +1 -1
  29. package/dist/components/NumberInput.d.ts +63 -0
  30. package/dist/components/NumberInput.d.ts.map +1 -0
  31. package/dist/components/NumberInput.js +102 -0
  32. package/dist/components/NumberInput.js.map +1 -0
  33. package/dist/components/PromptInput.d.ts +89 -0
  34. package/dist/components/PromptInput.d.ts.map +1 -0
  35. package/dist/components/PromptInput.js +130 -0
  36. package/dist/components/PromptInput.js.map +1 -0
  37. package/dist/components/ScrollProgress.d.ts +6 -0
  38. package/dist/components/ScrollProgress.d.ts.map +1 -1
  39. package/dist/components/ScrollProgress.js +2 -2
  40. package/dist/components/ScrollProgress.js.map +1 -1
  41. package/dist/components/SegmentedControl.d.ts +63 -0
  42. package/dist/components/SegmentedControl.d.ts.map +1 -0
  43. package/dist/components/SegmentedControl.js +79 -0
  44. package/dist/components/SegmentedControl.js.map +1 -0
  45. package/dist/components/Sidebar.d.ts +109 -0
  46. package/dist/components/Sidebar.d.ts.map +1 -0
  47. package/dist/components/Sidebar.js +106 -0
  48. package/dist/components/Sidebar.js.map +1 -0
  49. package/dist/components/Stepper.d.ts +73 -0
  50. package/dist/components/Stepper.d.ts.map +1 -0
  51. package/dist/components/Stepper.js +82 -0
  52. package/dist/components/Stepper.js.map +1 -0
  53. package/dist/components/Toast.d.ts +8 -1
  54. package/dist/components/Toast.d.ts.map +1 -1
  55. package/dist/components/Toast.js +2 -2
  56. package/dist/components/Toast.js.map +1 -1
  57. package/dist/components/ToolCall.d.ts +66 -0
  58. package/dist/components/ToolCall.d.ts.map +1 -0
  59. package/dist/components/ToolCall.js +78 -0
  60. package/dist/components/ToolCall.js.map +1 -0
  61. package/dist/components/agent-status.d.ts +14 -0
  62. package/dist/components/agent-status.d.ts.map +1 -0
  63. package/dist/components/agent-status.js +16 -0
  64. package/dist/components/agent-status.js.map +1 -0
  65. package/dist/components/index.d.ts +18 -1
  66. package/dist/components/index.d.ts.map +1 -1
  67. package/dist/components/index.js +8 -0
  68. package/dist/components/index.js.map +1 -1
  69. package/dist/components/iso-date.d.ts +53 -0
  70. package/dist/components/iso-date.d.ts.map +1 -0
  71. package/dist/components/iso-date.js +102 -0
  72. package/dist/components/iso-date.js.map +1 -0
  73. package/dist/figma-descriptions.json +107 -52
  74. package/dist/figma-map.json +735 -2
  75. package/dist/meta/AgentActivityStep.json +2 -2
  76. package/dist/meta/DatePicker.json +270 -0
  77. package/dist/meta/DateRangePicker.json +284 -0
  78. package/dist/meta/Header.json +32 -2
  79. package/dist/meta/NumberInput.json +407 -0
  80. package/dist/meta/PromptInput.json +212 -0
  81. package/dist/meta/ScrollProgress.json +26 -1
  82. package/dist/meta/SegmentedControl.json +217 -0
  83. package/dist/meta/SegmentedControlItem.json +122 -0
  84. package/dist/meta/Sidebar.json +150 -0
  85. package/dist/meta/SidebarItem.json +184 -0
  86. package/dist/meta/SidebarSection.json +131 -0
  87. package/dist/meta/Stepper.json +135 -0
  88. package/dist/meta/StepperStep.json +148 -0
  89. package/dist/meta/Tabs.json +5 -0
  90. package/dist/meta/ToastProvider.json +9 -2
  91. package/dist/meta/ToolCall.json +192 -0
  92. package/dist/meta/components.json +5812 -3293
  93. package/dist/meta/contrast.json +7776 -2478
  94. package/dist/meta/index.json +145 -3
  95. package/dist/meta/patterns/AgentRun.json +27 -0
  96. package/dist/meta/patterns/Form.json +9 -0
  97. package/dist/meta/patterns/PageShell.json +43 -3
  98. package/dist/meta/patterns/Wizard.json +20 -1
  99. package/dist/meta/patterns/index.json +10 -1
  100. package/dist/styles/date-picker.css +420 -0
  101. package/dist/styles/date-range-picker.css +280 -0
  102. package/dist/styles/header.css +26 -0
  103. package/dist/styles/index.css +15 -0
  104. package/dist/styles/number-input.css +83 -0
  105. package/dist/styles/prompt-input.css +127 -0
  106. package/dist/styles/scroll-progress.css +42 -4
  107. package/dist/styles/segmented-control.css +182 -0
  108. package/dist/styles/sidebar.css +307 -0
  109. package/dist/styles/stepper.css +250 -0
  110. package/dist/styles/table.css +9 -0
  111. package/dist/styles/tool-call.css +202 -0
  112. package/llms.txt +2 -2
  113. package/package.json +20 -19
  114. package/LICENSE +0 -21
@@ -0,0 +1,217 @@
1
+ {
2
+ "name": "SegmentedControl",
3
+ "source": "src/components/SegmentedControl.tsx",
4
+ "propsType": "SegmentedControlProps",
5
+ "description": "SegmentedControl — pick one of a few options, and see all of them.\n\nNOT TABS, ALTHOUGH IT LOOKS LIKE THE PILL TYPE, and the difference is the\ncontract. Tabs switch between panels: `tablist`, `tab`, `tabpanel`, and each\ntab controls a region. This sets a VALUE — a view mode, a time range, a\nunit — and controls no panel. It is a `radiogroup`, so it has one tab stop,\narrow keys move and select, and it submits with a form like any radio.\n\nBuilt from real radio inputs, visually hidden, not from buttons with\n`aria-pressed`. A row of toggle buttons is a tab stop per option and no\nmutual exclusion that assistive tech can announce; radios give \"2 of 3,\nselected\" for free.\n\nThe look is Tabs' pill track, token for token, so the two cannot drift\napart visually — which is exactly why the intent file spends its words on\nwhen to use which.",
6
+ "import": "import { SegmentedControl } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "Pick one of two to five options, all visible at once. A radio group drawn as a segmented track — it sets a value and controls no panel.",
9
+ "useWhen": [
10
+ "switching how the same content is shown — List / Board / Calendar",
11
+ "a short, fixed set of mutually exclusive settings — Day / Week / Month, °C / °F",
12
+ "a filter with a few exclusive choices where seeing every option matters"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "each option shows a different panel of content",
17
+ "use": "Tabs",
18
+ "why": "Tabs wires each tab to its panel with tablist/tabpanel semantics. A SegmentedControl controls no region, so a screen-reader user is never told which content changed"
19
+ },
20
+ {
21
+ "when": "there are more than five options, or the labels are long",
22
+ "use": "Select",
23
+ "why": "segments do not wrap; a track that overflows hides the options it exists to show"
24
+ },
25
+ {
26
+ "when": "the choice sits in a form with a visible question and descriptions per option",
27
+ "use": "RadioGroup",
28
+ "why": "same semantics, but room for a label and helper text on each option"
29
+ },
30
+ {
31
+ "when": "the setting is a single on/off",
32
+ "use": "Toggle"
33
+ },
34
+ {
35
+ "when": "the options trigger actions rather than set a value — Bold, Italic",
36
+ "use": "Button",
37
+ "why": "an action is not a selection; a radio group announces a state that does not exist"
38
+ }
39
+ ],
40
+ "composition": {
41
+ "order": [
42
+ "SegmentedControl",
43
+ "SegmentedControlItem"
44
+ ],
45
+ "example": "<SegmentedControl label=\"View\" defaultValue=\"list\"><SegmentedControlItem value=\"list\">List</SegmentedControlItem><SegmentedControlItem value=\"board\">Board</SegmentedControlItem></SegmentedControl>"
46
+ },
47
+ "variants": {
48
+ "size": {
49
+ "sm": {
50
+ "use": "toolbars and dense panel headers"
51
+ },
52
+ "md": {
53
+ "use": "the default"
54
+ },
55
+ "lg": {
56
+ "use": "a primary view switch at the top of a page"
57
+ }
58
+ }
59
+ },
60
+ "slots": {
61
+ "children": {
62
+ "accepts": "SegmentedControlItem",
63
+ "note": "two to five. One item is not a choice; six is a Select"
64
+ }
65
+ },
66
+ "a11y": {
67
+ "role": "radiogroup / radio",
68
+ "requires": [
69
+ "`label` — the group's accessible name. Hidden by default; `showLabel` renders it above the track"
70
+ ],
71
+ "guarantees": [
72
+ "one tab stop for the whole control; arrow keys move and select, skipping disabled segments",
73
+ "built from real radio inputs, so it announces \"2 of 3, selected\" and submits with a form under `name`",
74
+ "a disabled selected segment keeps its selected surface — a disabled control still has a value"
75
+ ],
76
+ "notes": [
77
+ "it looks exactly like Tabs' pill type on purpose, and behaves differently on purpose. Choose by what changes, not by how it looks"
78
+ ]
79
+ },
80
+ "antiPatterns": [
81
+ {
82
+ "dont": "a SegmentedControl that swaps the panel below it",
83
+ "do": "<Tabs type=\"pill\">",
84
+ "why": "the panel relationship is exactly what tabpanel semantics announce; without it the content changes silently"
85
+ },
86
+ {
87
+ "dont": "no defaultValue or value",
88
+ "why": "a segmented track with nothing selected reads as broken, not as a choice still to make"
89
+ },
90
+ {
91
+ "dont": "icon-only segments with no aria-label",
92
+ "do": "<SegmentedControlItem value=\"grid\" icon={<Grid />} aria-label=\"Grid view\" />",
93
+ "why": "each segment is a radio, and an unnamed radio is announced as just \"radio button\""
94
+ }
95
+ ],
96
+ "stylesheet": "src/styles/segmented-control.css",
97
+ "tokens": [
98
+ "--border-width-thick",
99
+ "--font-family-sans",
100
+ "--font-weight-medium",
101
+ "--icon-size-md",
102
+ "--icon-size-sm",
103
+ "--ion-duration-base",
104
+ "--ion-ease-out",
105
+ "--ion-segmented-font-size",
106
+ "--ion-segmented-icon-size",
107
+ "--ion-segmented-item-gap",
108
+ "--ion-segmented-item-height",
109
+ "--ion-segmented-item-padding-x",
110
+ "--ion-segmented-line-height",
111
+ "--radius-lg",
112
+ "--radius-md",
113
+ "--ring-focus",
114
+ "--spacing-12",
115
+ "--spacing-16",
116
+ "--spacing-2",
117
+ "--spacing-20",
118
+ "--spacing-24",
119
+ "--spacing-32",
120
+ "--spacing-4",
121
+ "--spacing-40",
122
+ "--spacing-6",
123
+ "--spacing-8",
124
+ "--surface-default",
125
+ "--surface-hover",
126
+ "--surface-muted",
127
+ "--surface-page",
128
+ "--text-default",
129
+ "--text-disabled",
130
+ "--text-tertiary",
131
+ "--type-body",
132
+ "--type-body-lg",
133
+ "--type-body-lg-line-height",
134
+ "--type-body-line-height",
135
+ "--type-body-sm",
136
+ "--type-body-sm-line-height"
137
+ ],
138
+ "props": {
139
+ "label": {
140
+ "type": "string",
141
+ "required": true,
142
+ "origin": "own",
143
+ "description": "The accessible name of the group. Required: \"List, Board, Calendar\" means\nnothing until something says they are views."
144
+ },
145
+ "showLabel": {
146
+ "type": "boolean | undefined",
147
+ "required": false,
148
+ "origin": "own",
149
+ "description": "Show `label` above the control, as a form field does. Hidden by default.",
150
+ "default": false
151
+ },
152
+ "value": {
153
+ "type": "string | undefined",
154
+ "required": false,
155
+ "origin": "own",
156
+ "description": "Controlled selected value. Pass `onChange` with it."
157
+ },
158
+ "defaultValue": {
159
+ "type": "string | undefined",
160
+ "required": false,
161
+ "origin": "own",
162
+ "description": "Uncontrolled starting value. One option should always be selected — a\nsegmented control with nothing chosen reads as broken, not as \"no choice\"."
163
+ },
164
+ "onChange": {
165
+ "type": "((value: string) => void) | undefined",
166
+ "required": false,
167
+ "origin": "own"
168
+ },
169
+ "size": {
170
+ "type": "SegmentedControlSize | undefined",
171
+ "required": false,
172
+ "origin": "own",
173
+ "values": [
174
+ "sm",
175
+ "md",
176
+ "lg"
177
+ ],
178
+ "default": "md"
179
+ },
180
+ "isFullWidth": {
181
+ "type": "boolean | undefined",
182
+ "required": false,
183
+ "origin": "own",
184
+ "description": "Stretch to the container, with equal-width segments.",
185
+ "default": false
186
+ },
187
+ "isDisabled": {
188
+ "type": "boolean | undefined",
189
+ "required": false,
190
+ "origin": "own",
191
+ "default": false
192
+ },
193
+ "name": {
194
+ "type": "string | undefined",
195
+ "required": false,
196
+ "origin": "own",
197
+ "description": "Submitted with a form under this name."
198
+ },
199
+ "children": {
200
+ "type": "React.ReactNode",
201
+ "required": false,
202
+ "origin": "own",
203
+ "description": "`SegmentedControlItem` elements. Two to five of them."
204
+ },
205
+ "className": {
206
+ "type": "string | undefined",
207
+ "required": false,
208
+ "origin": "own"
209
+ }
210
+ },
211
+ "propCounts": {
212
+ "own": 11,
213
+ "aria": 0,
214
+ "dom": 0,
215
+ "other": 0
216
+ }
217
+ }
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "SegmentedControlItem",
3
+ "source": "src/components/SegmentedControl.tsx",
4
+ "propsType": "SegmentedControlItemProps",
5
+ "import": "import { SegmentedControlItem } from 'ionbase-ui';",
6
+ "status": "stable",
7
+ "summary": "One segment: a radio drawn as part of the track, with an optional icon.",
8
+ "useWhen": [
9
+ "inside a SegmentedControl, once per option"
10
+ ],
11
+ "useInstead": [
12
+ {
13
+ "when": "outside a segmented track",
14
+ "use": "Radio"
15
+ }
16
+ ],
17
+ "slots": {
18
+ "children": {
19
+ "accepts": "text",
20
+ "note": "one or two words. Segments do not wrap, and a long label truncates"
21
+ },
22
+ "icon": {
23
+ "accepts": "Icon",
24
+ "note": "decorative beside a label; with no label, pass aria-label"
25
+ }
26
+ },
27
+ "a11y": {
28
+ "requires": [
29
+ "`aria-label` when the segment has an icon and no text — the accessible name otherwise comes from its children"
30
+ ]
31
+ },
32
+ "antiPatterns": [
33
+ {
34
+ "dont": "using it outside SegmentedControl",
35
+ "why": "it reads its selection state from the group, and throws without one"
36
+ }
37
+ ],
38
+ "stylesheet": "src/styles/segmented-control.css",
39
+ "tokens": [
40
+ "--border-width-thick",
41
+ "--font-family-sans",
42
+ "--font-weight-medium",
43
+ "--icon-size-md",
44
+ "--icon-size-sm",
45
+ "--ion-duration-base",
46
+ "--ion-ease-out",
47
+ "--ion-segmented-font-size",
48
+ "--ion-segmented-icon-size",
49
+ "--ion-segmented-item-gap",
50
+ "--ion-segmented-item-height",
51
+ "--ion-segmented-item-padding-x",
52
+ "--ion-segmented-line-height",
53
+ "--radius-lg",
54
+ "--radius-md",
55
+ "--ring-focus",
56
+ "--spacing-12",
57
+ "--spacing-16",
58
+ "--spacing-2",
59
+ "--spacing-20",
60
+ "--spacing-24",
61
+ "--spacing-32",
62
+ "--spacing-4",
63
+ "--spacing-40",
64
+ "--spacing-6",
65
+ "--spacing-8",
66
+ "--surface-default",
67
+ "--surface-hover",
68
+ "--surface-muted",
69
+ "--surface-page",
70
+ "--text-default",
71
+ "--text-disabled",
72
+ "--text-tertiary",
73
+ "--type-body",
74
+ "--type-body-lg",
75
+ "--type-body-lg-line-height",
76
+ "--type-body-line-height",
77
+ "--type-body-sm",
78
+ "--type-body-sm-line-height"
79
+ ],
80
+ "props": {
81
+ "value": {
82
+ "type": "string",
83
+ "required": true,
84
+ "origin": "own",
85
+ "description": "The value this segment selects."
86
+ },
87
+ "icon": {
88
+ "type": "React.ReactNode",
89
+ "required": false,
90
+ "origin": "own",
91
+ "description": "Icon before the label. `aria-hidden` is applied for you."
92
+ },
93
+ "aria-label": {
94
+ "type": "string | undefined",
95
+ "required": false,
96
+ "origin": "own",
97
+ "description": "Required when the segment has no visible text — an icon-only segment is\notherwise announced as an unnamed radio button."
98
+ },
99
+ "isDisabled": {
100
+ "type": "boolean | undefined",
101
+ "required": false,
102
+ "origin": "own",
103
+ "default": false
104
+ },
105
+ "children": {
106
+ "type": "React.ReactNode",
107
+ "required": false,
108
+ "origin": "own"
109
+ },
110
+ "className": {
111
+ "type": "string | undefined",
112
+ "required": false,
113
+ "origin": "own"
114
+ }
115
+ },
116
+ "propCounts": {
117
+ "own": 6,
118
+ "aria": 0,
119
+ "dom": 0,
120
+ "other": 0
121
+ }
122
+ }
@@ -0,0 +1,150 @@
1
+ {
2
+ "name": "Sidebar",
3
+ "source": "src/components/Sidebar.tsx",
4
+ "propsType": "SidebarProps",
5
+ "description": "Sidebar — an application's navigation, as a column.\n\nDISCLOSURE NAVIGATION, NOT `role=\"tree\"`\n\nA file-tree look invites the tree role, and the tree role is wrong here.\nA treeview takes the arrow keys, allows one tab stop for the whole widget,\nand does not permit interactive content inside an item — so the `…` and `+`\nbuttons on a row, which are half the point of a workspace sidebar, could not\nexist in it. This is the WAI-ARIA disclosure navigation pattern instead:\nnested lists of links, each expandable level behind a real button with\n`aria-expanded`, and everything reachable with Tab.\n\nAN ITEM THAT IS A LINK AND HAS CHILDREN GETS TWO CONTROLS\n\nA button cannot sit inside a link. So a row that navigates AND expands\nrenders the link and a separate chevron button beside it; a row with no\n`href` is one button that expands. The actions slot is a sibling of both,\nnever nested inside either.\n\nTHE CURRENT PAGE IS NEVER FOLDED AWAY\n\nAn item whose subtree contains the current page starts open. A sidebar that\nloads with the user's location collapsed inside a closed group tells a\nsighted user nothing and a screen-reader user less.\n\nExpansion is per item, controlled or not. Remembering it across visits is the\ncaller's job — this component cannot know where that state should live.",
6
+ "import": "import { Sidebar } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "An application's navigation as a column: pinned header and footer that take any component, a scrolling body of titled, collapsible sections, and nested items that expand.",
9
+ "useWhen": [
10
+ "an app has more destinations than fit in a header — workspaces, projects, saved views",
11
+ "the navigation is hierarchical and users need to see where they are inside it",
12
+ "rows need their own actions — create inside this space, rename, favourite"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "there are five or fewer top-level destinations and no hierarchy",
17
+ "use": "Header",
18
+ "why": "a sidebar spends a column of width on what a header row carries in its height"
19
+ },
20
+ {
21
+ "when": "the hierarchy describes the current page's position rather than offering destinations",
22
+ "use": "Breadcrumb"
23
+ },
24
+ {
25
+ "when": "sections of content collapse, not destinations",
26
+ "use": "Accordion",
27
+ "why": "Accordion's headings belong to the document outline; a sidebar is a navigation landmark"
28
+ }
29
+ ],
30
+ "composition": {
31
+ "order": [
32
+ "Sidebar",
33
+ "SidebarSection",
34
+ "SidebarItem"
35
+ ],
36
+ "note": "Items always go inside a section, because a section renders the list. Anything that is not an item — a Divider, a promotional Alert — goes directly in Sidebar between sections, or in `header` / `footer`. On narrow screens, put the Sidebar inside a Drawer; it does not collapse itself.",
37
+ "example": "<Sidebar label=\"Workspace\" header={<WorkspaceMenu />} footer={<Button>Invite</Button>}><SidebarSection><SidebarItem label=\"Home\" href=\"/\" isCurrent /></SidebarSection><Divider /><SidebarSection title=\"Spaces\" isCollapsible><SidebarItem label=\"Team Space\" href=\"/team\"><SidebarItem label=\"Projects\" href=\"/team/projects\" /></SidebarItem></SidebarSection></Sidebar>"
38
+ },
39
+ "slots": {
40
+ "header": {
41
+ "accepts": "Menu",
42
+ "note": "pinned above the scroll — a workspace switcher, search, icon Buttons"
43
+ },
44
+ "footer": {
45
+ "accepts": "Button",
46
+ "note": "pinned below the scroll — Invite, Help, a trial notice in an Alert"
47
+ },
48
+ "children": {
49
+ "accepts": "SidebarSection",
50
+ "note": "and Divider or other non-item content between sections"
51
+ }
52
+ },
53
+ "a11y": {
54
+ "role": "navigation",
55
+ "requires": [
56
+ "`label` — the accessible name of the navigation landmark. Pages with a sidebar usually have a second nav, and two unnamed ones cannot be told apart"
57
+ ],
58
+ "guarantees": [
59
+ "only the body scrolls; the header and footer stay reachable at any scroll position"
60
+ ],
61
+ "notes": [
62
+ "it is disclosure navigation, not a treeview: everything is reachable with Tab, and rows can hold buttons. The tree role forbids interactive content inside items and would have ruled out row actions"
63
+ ]
64
+ },
65
+ "antiPatterns": [
66
+ {
67
+ "dont": "role=\"tree\" on a sidebar with row actions",
68
+ "why": "a treeview item cannot contain buttons, and its arrow-key model hides every action from Tab"
69
+ },
70
+ {
71
+ "dont": "a SidebarItem placed directly in Sidebar, outside a section",
72
+ "do": "wrap it in an untitled <SidebarSection>",
73
+ "why": "the item renders an <li>, which is only valid inside the list a section provides"
74
+ },
75
+ {
76
+ "dont": "a sidebar that is only ever a hamburger menu on desktop",
77
+ "why": "hierarchical navigation that is hidden by default is navigation nobody uses; hide it only where the width forces it"
78
+ }
79
+ ],
80
+ "stylesheet": "src/styles/sidebar.css",
81
+ "tokens": [
82
+ "--border-focus",
83
+ "--border-subtle",
84
+ "--border-width-default",
85
+ "--border-width-thick",
86
+ "--font-family-sans",
87
+ "--font-weight-medium",
88
+ "--icon-disabled",
89
+ "--icon-primary",
90
+ "--icon-secondary",
91
+ "--icon-size-sm",
92
+ "--icon-tertiary",
93
+ "--ion-duration-base",
94
+ "--ion-ease-out",
95
+ "--radius-md",
96
+ "--radius-sm",
97
+ "--radius-xs",
98
+ "--spacing-12",
99
+ "--spacing-16",
100
+ "--spacing-2",
101
+ "--spacing-24",
102
+ "--spacing-32",
103
+ "--spacing-4",
104
+ "--spacing-8",
105
+ "--surface-default",
106
+ "--surface-hover",
107
+ "--surface-selected",
108
+ "--surface-selected-hover",
109
+ "--text-default",
110
+ "--text-disabled",
111
+ "--text-secondary",
112
+ "--text-tertiary",
113
+ "--type-body-sm",
114
+ "--type-body-sm-line-height",
115
+ "--type-caption",
116
+ "--type-caption-line-height"
117
+ ],
118
+ "props": {
119
+ "label": {
120
+ "type": "string",
121
+ "required": true,
122
+ "origin": "own",
123
+ "description": "Names the navigation landmark. Required: a page with a sidebar usually has\na second `<nav>` too, and \"navigation, navigation\" tells nobody which is\nwhich."
124
+ },
125
+ "header": {
126
+ "type": "React.ReactNode",
127
+ "required": false,
128
+ "origin": "own",
129
+ "description": "Pinned above the scrolling body — a workspace switcher, a search trigger,\nicon buttons. Any component."
130
+ },
131
+ "footer": {
132
+ "type": "React.ReactNode",
133
+ "required": false,
134
+ "origin": "own",
135
+ "description": "Pinned below the scrolling body — an Invite button, help, a trial notice.\nAny component."
136
+ },
137
+ "children": {
138
+ "type": "React.ReactNode",
139
+ "required": false,
140
+ "origin": "own",
141
+ "description": "`SidebarSection`s, and anything that sits between them — a `Divider`, a\npromotional card. Items themselves always go inside a section."
142
+ }
143
+ },
144
+ "propCounts": {
145
+ "own": 4,
146
+ "aria": 0,
147
+ "dom": 277,
148
+ "other": 0
149
+ }
150
+ }
@@ -0,0 +1,184 @@
1
+ {
2
+ "name": "SidebarItem",
3
+ "source": "src/components/Sidebar.tsx",
4
+ "propsType": "SidebarItemProps",
5
+ "import": "import { SidebarItem } from 'ionbase-ui';",
6
+ "status": "stable",
7
+ "summary": "One sidebar row: a link or a button, with an icon, a count, actions revealed on hover or focus, and nested items that expand.",
8
+ "useWhen": [
9
+ "inside a SidebarSection, or nested inside another SidebarItem"
10
+ ],
11
+ "useInstead": [
12
+ {
13
+ "when": "it is a link in a header bar",
14
+ "use": "NavItem",
15
+ "why": "NavItem recolours on hover without a fill, as measured for the header; sidebar rows fill"
16
+ },
17
+ {
18
+ "when": "it is a row in a dropdown",
19
+ "use": "MenuItem"
20
+ }
21
+ ],
22
+ "slots": {
23
+ "icon": {
24
+ "accepts": "Icon",
25
+ "note": "decorative; label names the row"
26
+ },
27
+ "badge": {
28
+ "accepts": "text",
29
+ "note": "a count or short status, trailing"
30
+ },
31
+ "actions": {
32
+ "accepts": "Button",
33
+ "note": "icon-only Buttons with aria-label, or a Menu trigger for more. Keep to two"
34
+ },
35
+ "children": {
36
+ "accepts": "SidebarItem",
37
+ "note": "nested items; their presence makes the row expandable"
38
+ }
39
+ },
40
+ "a11y": {
41
+ "requires": [
42
+ "`label` — the row's name, and the name of its expand button when it has children"
43
+ ],
44
+ "guarantees": [
45
+ "`isCurrent` sets aria-current=\"page\" on the link",
46
+ "a row with `href` and children renders the link and a separate expand button beside it — a button cannot be nested in a link",
47
+ "a row with children and no `href` is a single button with aria-expanded",
48
+ "a row whose subtree holds the current page starts expanded",
49
+ "actions stay in the tab order and become visible when focus reaches them; they are always visible on touch screens"
50
+ ]
51
+ },
52
+ "antiPatterns": [
53
+ {
54
+ "dont": "onClick on a row to navigate",
55
+ "do": "href",
56
+ "why": "a real link opens in a new tab, shows its address and is announced as a link"
57
+ },
58
+ {
59
+ "dont": "actions nested inside the label or icon",
60
+ "do": "the actions prop",
61
+ "why": "interactive content inside a link is invalid and unreachable by keyboard in most browsers"
62
+ },
63
+ {
64
+ "dont": "marking a parent isCurrent because a child page is open",
65
+ "why": "only the page the user is on is current; its ancestors are opened for them instead"
66
+ }
67
+ ],
68
+ "stylesheet": "src/styles/sidebar.css",
69
+ "tokens": [
70
+ "--border-focus",
71
+ "--border-subtle",
72
+ "--border-width-default",
73
+ "--border-width-thick",
74
+ "--font-family-sans",
75
+ "--font-weight-medium",
76
+ "--icon-disabled",
77
+ "--icon-primary",
78
+ "--icon-secondary",
79
+ "--icon-size-sm",
80
+ "--icon-tertiary",
81
+ "--ion-duration-base",
82
+ "--ion-ease-out",
83
+ "--radius-md",
84
+ "--radius-sm",
85
+ "--radius-xs",
86
+ "--spacing-12",
87
+ "--spacing-16",
88
+ "--spacing-2",
89
+ "--spacing-24",
90
+ "--spacing-32",
91
+ "--spacing-4",
92
+ "--spacing-8",
93
+ "--surface-default",
94
+ "--surface-hover",
95
+ "--surface-selected",
96
+ "--surface-selected-hover",
97
+ "--text-default",
98
+ "--text-disabled",
99
+ "--text-secondary",
100
+ "--text-tertiary",
101
+ "--type-body-sm",
102
+ "--type-body-sm-line-height",
103
+ "--type-caption",
104
+ "--type-caption-line-height"
105
+ ],
106
+ "props": {
107
+ "label": {
108
+ "type": "string",
109
+ "required": true,
110
+ "origin": "own",
111
+ "description": "The item's text. A string, because it also names the expand button of an\nitem that has children."
112
+ },
113
+ "icon": {
114
+ "type": "React.ReactNode",
115
+ "required": false,
116
+ "origin": "own",
117
+ "description": "Leading icon. Decorative — `label` names the item."
118
+ },
119
+ "href": {
120
+ "type": "string | undefined",
121
+ "required": false,
122
+ "origin": "own",
123
+ "description": "Where it goes. Omit for an item that only groups its children."
124
+ },
125
+ "onPress": {
126
+ "type": "(() => void) | undefined",
127
+ "required": false,
128
+ "origin": "own",
129
+ "description": "For an item that acts rather than navigates. Ignored with `href`."
130
+ },
131
+ "isCurrent": {
132
+ "type": "boolean | undefined",
133
+ "required": false,
134
+ "origin": "own",
135
+ "description": "The page the user is on. Marked `aria-current=\"page\"`."
136
+ },
137
+ "isDisabled": {
138
+ "type": "boolean | undefined",
139
+ "required": false,
140
+ "origin": "own"
141
+ },
142
+ "badge": {
143
+ "type": "React.ReactNode",
144
+ "required": false,
145
+ "origin": "own",
146
+ "description": "Trailing text — a count, a shortcut, a status."
147
+ },
148
+ "actions": {
149
+ "type": "React.ReactNode",
150
+ "required": false,
151
+ "origin": "own",
152
+ "description": "Controls for this row — `…` and `+`. Revealed on hover and whenever focus\nis inside the row; always shown on touch screens. Icon-only buttons need\ntheir own `aria-label`."
153
+ },
154
+ "defaultExpanded": {
155
+ "type": "boolean | undefined",
156
+ "required": false,
157
+ "origin": "own",
158
+ "description": "Uncontrolled: whether the children start open. Defaults to open when the\ncurrent page is somewhere inside."
159
+ },
160
+ "isExpanded": {
161
+ "type": "boolean | undefined",
162
+ "required": false,
163
+ "origin": "own",
164
+ "description": "Controlled: whether the children are open."
165
+ },
166
+ "onExpandedChange": {
167
+ "type": "((isExpanded: boolean) => void) | undefined",
168
+ "required": false,
169
+ "origin": "own"
170
+ },
171
+ "children": {
172
+ "type": "React.ReactNode",
173
+ "required": false,
174
+ "origin": "own",
175
+ "description": "Nested `SidebarItem`s. Their presence is what makes the item expandable."
176
+ }
177
+ },
178
+ "propCounts": {
179
+ "own": 12,
180
+ "aria": 0,
181
+ "dom": 277,
182
+ "other": 0
183
+ }
184
+ }