ionbase-ui 0.84.0 → 0.88.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 (57) hide show
  1. package/dist/components/Checkbox.d.ts +39 -0
  2. package/dist/components/Checkbox.d.ts.map +1 -1
  3. package/dist/components/Checkbox.js +77 -4
  4. package/dist/components/Checkbox.js.map +1 -1
  5. package/dist/components/Fieldset.d.ts +62 -0
  6. package/dist/components/Fieldset.d.ts.map +1 -0
  7. package/dist/components/Fieldset.js +58 -0
  8. package/dist/components/Fieldset.js.map +1 -0
  9. package/dist/components/MultiSelect.d.ts +90 -0
  10. package/dist/components/MultiSelect.d.ts.map +1 -0
  11. package/dist/components/MultiSelect.js +189 -0
  12. package/dist/components/MultiSelect.js.map +1 -0
  13. package/dist/components/Radio.d.ts +14 -0
  14. package/dist/components/Radio.d.ts.map +1 -1
  15. package/dist/components/Radio.js +15 -4
  16. package/dist/components/Radio.js.map +1 -1
  17. package/dist/components/SearchField.d.ts +36 -0
  18. package/dist/components/SearchField.d.ts.map +1 -0
  19. package/dist/components/SearchField.js +76 -0
  20. package/dist/components/SearchField.js.map +1 -0
  21. package/dist/components/Toolbar.d.ts +35 -0
  22. package/dist/components/Toolbar.d.ts.map +1 -0
  23. package/dist/components/Toolbar.js +81 -0
  24. package/dist/components/Toolbar.js.map +1 -0
  25. package/dist/components/index.d.ts +10 -2
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +5 -1
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/figma-descriptions.json +97 -77
  30. package/dist/figma-map.json +228 -4
  31. package/dist/meta/Checkbox.json +8 -6
  32. package/dist/meta/CheckboxGroup.json +268 -0
  33. package/dist/meta/Combobox.json +8 -0
  34. package/dist/meta/Divider.json +2 -2
  35. package/dist/meta/Fieldset.json +162 -0
  36. package/dist/meta/MultiSelect.json +269 -0
  37. package/dist/meta/Radio.json +0 -1
  38. package/dist/meta/RadioGroup.json +59 -7
  39. package/dist/meta/SearchField.json +487 -0
  40. package/dist/meta/Select.json +2 -1
  41. package/dist/meta/Stepper.json +2 -2
  42. package/dist/meta/Tabs.json +3 -3
  43. package/dist/meta/Toolbar.json +130 -0
  44. package/dist/meta/components.json +1616 -238
  45. package/dist/meta/contrast.json +398 -74
  46. package/dist/meta/index.json +84 -8
  47. package/dist/meta/patterns/DataTable.json +33 -6
  48. package/dist/meta/patterns/Form.json +27 -2
  49. package/dist/meta/patterns/index.json +6 -2
  50. package/dist/styles/fieldset.css +65 -0
  51. package/dist/styles/index.css +4 -0
  52. package/dist/styles/multi-select.css +104 -0
  53. package/dist/styles/radio.css +0 -20
  54. package/dist/styles/search-field.css +70 -0
  55. package/dist/styles/toolbar.css +36 -0
  56. package/llms.txt +2 -2
  57. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.84.0",
3
+ "version": "0.88.0",
4
4
  "usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
5
5
  "hooks": [
6
6
  "useAgentRun",
@@ -298,6 +298,28 @@
298
298
  "detail": "dist/meta/Checkbox.json",
299
299
  "hasIntent": true
300
300
  },
301
+ "CheckboxGroup": {
302
+ "summary": "A set of checkboxes answering one question. Owns the selected values, the group's label, help and error, and \"select at least one\".",
303
+ "status": "stable",
304
+ "variants": {
305
+ "size": [
306
+ "sm",
307
+ "md",
308
+ "lg"
309
+ ],
310
+ "intent": [
311
+ "neutral",
312
+ "danger",
313
+ "brand"
314
+ ],
315
+ "orientation": [
316
+ "vertical",
317
+ "horizontal"
318
+ ]
319
+ },
320
+ "detail": "dist/meta/CheckboxGroup.json",
321
+ "hasIntent": true
322
+ },
301
323
  "Citation": {
302
324
  "summary": "The inline source marker. The visible number is for the eye; the accessible name is the sentence.",
303
325
  "status": "stable",
@@ -388,8 +410,8 @@
388
410
  "status": "stable",
389
411
  "variants": {
390
412
  "orientation": [
391
- "horizontal",
392
- "vertical"
413
+ "vertical",
414
+ "horizontal"
393
415
  ]
394
416
  },
395
417
  "detail": "dist/meta/Divider.json",
@@ -433,6 +455,18 @@
433
455
  "detail": "dist/meta/EmptyState.json",
434
456
  "hasIntent": true
435
457
  },
458
+ "Fieldset": {
459
+ "summary": "A <fieldset>/<legend> that groups related fields under one label, one description and one error — an address, a pair of limits, a date typed as two fields.",
460
+ "status": "stable",
461
+ "variants": {
462
+ "orientation": [
463
+ "vertical",
464
+ "horizontal"
465
+ ]
466
+ },
467
+ "detail": "dist/meta/Fieldset.json",
468
+ "hasIntent": true
469
+ },
436
470
  "FileUpload": {
437
471
  "summary": "A drop target wrapped around a real file input, with client-side accept and size checks.",
438
472
  "status": "stable",
@@ -602,6 +636,19 @@
602
636
  "detail": "dist/meta/Modal.json",
603
637
  "hasIntent": true
604
638
  },
639
+ "MultiSelect": {
640
+ "summary": "A text field that filters a list, with any number of values chosen. The choices show as removable tags beneath the field and are read with it.",
641
+ "status": "stable",
642
+ "variants": {
643
+ "size": [
644
+ "sm",
645
+ "md",
646
+ "lg"
647
+ ]
648
+ },
649
+ "detail": "dist/meta/MultiSelect.json",
650
+ "hasIntent": true
651
+ },
605
652
  "NavItem": {
606
653
  "summary": "A primary navigation destination. Renders an <a> when given `href`, a <button> otherwise.",
607
654
  "status": "stable",
@@ -735,9 +782,13 @@
735
782
  "hasIntent": true
736
783
  },
737
784
  "RadioGroup": {
738
- "summary": "A <fieldset>/<legend> group of mutually exclusive options. Owns the shared name and the selected value.",
785
+ "summary": "A <fieldset>/<legend> group of mutually exclusive options. Owns the shared name and the selected value, and the group's label, help and error.",
739
786
  "status": "stable",
740
787
  "variants": {
788
+ "orientation": [
789
+ "vertical",
790
+ "horizontal"
791
+ ],
741
792
  "size": [
742
793
  "sm",
743
794
  "md",
@@ -764,6 +815,19 @@
764
815
  "detail": "dist/meta/ScrollProgress.json",
765
816
  "hasIntent": true
766
817
  },
818
+ "SearchField": {
819
+ "summary": "A text field for a search query: role=\"searchbox\", Enter submits, Escape clears, and a clear button once there is something to clear. Input's box and sizes.",
820
+ "status": "stable",
821
+ "variants": {
822
+ "size": [
823
+ "sm",
824
+ "md",
825
+ "lg"
826
+ ]
827
+ },
828
+ "detail": "dist/meta/SearchField.json",
829
+ "hasIntent": true
830
+ },
767
831
  "SegmentedControl": {
768
832
  "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.",
769
833
  "status": "stable",
@@ -880,8 +944,8 @@
880
944
  "status": "stable",
881
945
  "variants": {
882
946
  "orientation": [
883
- "horizontal",
884
- "vertical"
947
+ "vertical",
948
+ "horizontal"
885
949
  ]
886
950
  },
887
951
  "detail": "dist/meta/Stepper.json",
@@ -990,8 +1054,8 @@
990
1054
  "lg"
991
1055
  ],
992
1056
  "orientation": [
993
- "horizontal",
994
- "vertical"
1057
+ "vertical",
1058
+ "horizontal"
995
1059
  ]
996
1060
  },
997
1061
  "detail": "dist/meta/Tabs.json",
@@ -1103,6 +1167,18 @@
1103
1167
  "detail": "dist/meta/Toggle.json",
1104
1168
  "hasIntent": true
1105
1169
  },
1170
+ "Toolbar": {
1171
+ "summary": "A row of controls that act on one thing, reached as one tab stop: arrow keys between the controls, Tab out in one press, and back to the control used last.",
1172
+ "status": "stable",
1173
+ "variants": {
1174
+ "orientation": [
1175
+ "vertical",
1176
+ "horizontal"
1177
+ ]
1178
+ },
1179
+ "detail": "dist/meta/Toolbar.json",
1180
+ "hasIntent": true
1181
+ },
1106
1182
  "ToolCall": {
1107
1183
  "summary": "The evidence behind one step of an agent run: which tool, with what input, and what came back — collapsed by default, with any failure shown without a click.",
1108
1184
  "status": "stable",
@@ -12,15 +12,17 @@
12
12
  "TableRow",
13
13
  "TableCell",
14
14
  "Checkbox",
15
+ "Toolbar",
15
16
  "Button",
16
17
  "Badge",
17
18
  "Alert",
18
- "Input",
19
+ "SearchField",
20
+ "MultiSelect",
19
21
  "TagGroup"
20
22
  ],
21
23
  "structure": [
22
- "A toolbar above the table: a search Input, filter controls, and the bulk-action Buttons that appear once something is selected.",
23
- "Under the toolbar, once any filter is applied: a TagGroup of the active filters, each removable on its own, beside a Clear all Button. When the last one goes the row goes with it, so move focus to the search Input.",
24
+ "A toolbar above the table: a SearchField (size sm, with an aria-label naming what it searches), filter controls — a MultiSelect with `hideTags` for any filter that can match more than one value — and, once something is selected, the bulk actions in a Toolbar named for what they act on. The search and filters stay ordinary tab stops outside it — see Toolbar's contract.",
25
+ "Under the toolbar, once any filter is applied: a TagGroup of the active filters, each removable on its own — one tag per value of a multi-value filter — beside a Clear all Button. When the last one goes the row goes with it, so move focus to the SearchField.",
24
26
  "Table with a TableHead whose first cell carries the select-all Checkbox, and a TableBody whose rows each carry their own.",
25
27
  "Sortable columns: every header that sorts gets `sortDirection` — the current direction on the sorted column, `none` on the others — so each shows it can be sorted before anyone tries.",
26
28
  "One region below the table for pagination or a load-more Button.",
@@ -71,6 +73,9 @@
71
73
  "isIndeterminate",
72
74
  "isDisabled"
73
75
  ],
76
+ "Toolbar": [
77
+ "aria-label"
78
+ ],
74
79
  "Button": [
75
80
  "variant",
76
81
  "size"
@@ -80,13 +85,26 @@
80
85
  "title",
81
86
  "actions"
82
87
  ],
83
- "Input": [
84
- "leadingIcon"
85
- ],
86
88
  "TagGroup": [
87
89
  "label",
88
90
  "items",
89
91
  "onRemove"
92
+ ],
93
+ "SearchField": [
94
+ "size",
95
+ "value",
96
+ "onChange",
97
+ "placeholder",
98
+ "aria-label"
99
+ ],
100
+ "MultiSelect": [
101
+ "size",
102
+ "aria-label",
103
+ "options",
104
+ "value",
105
+ "onChange",
106
+ "hideTags",
107
+ "placeholder"
90
108
  ]
91
109
  },
92
110
  "variantsUsed": {
@@ -152,6 +170,15 @@
152
170
  {
153
171
  "dont": "sorting only the current page of a paged table",
154
172
  "why": "the reader sees \"A–Z\" and takes it as the whole list; send the sort to the server, which knows every row"
173
+ },
174
+ {
175
+ "dont": "an Input with type=\"search\" and a magnifier icon as the table's search",
176
+ "why": "it has no searchbox role, no Escape to clear and no labelled clear button — SearchField is the same box with that behaviour"
177
+ },
178
+ {
179
+ "dont": "a multi-value filter as a MultiSelect showing its own tags above the active-filters row",
180
+ "do": "`hideTags` on the MultiSelect, and one active-filter tag per value",
181
+ "why": "the same values drawn twice, each removable, in two places that can disagree about focus"
155
182
  }
156
183
  ]
157
184
  }
@@ -9,6 +9,8 @@
9
9
  "Input",
10
10
  "Select",
11
11
  "Checkbox",
12
+ "CheckboxGroup",
13
+ "Fieldset",
12
14
  "Radio",
13
15
  "RadioGroup",
14
16
  "Toggle",
@@ -21,6 +23,7 @@
21
23
  "structure": [
22
24
  "Fields in a single column. Two columns only for genuinely paired values, and they stack below the mobile breakpoint.",
23
25
  "Each field carries its own label and, when invalid, its own errorMessage — the message sits with the control it is about.",
26
+ "Related fields that answer one question — an address, a pair of limits, a set of checkboxes — sit in a Fieldset, CheckboxGroup or RadioGroup, so the question is announced with each field and a group-level error has somewhere to live.",
24
27
  "An error summary Alert above the form after a failed submit, listing each invalid field as a link to it.",
25
28
  "A submit Button and a cancel Link or Button, in that reading order, at the end."
26
29
  ],
@@ -70,7 +73,23 @@
70
73
  "label",
71
74
  "name",
72
75
  "value",
73
- "onChange"
76
+ "onChange",
77
+ "isInvalid",
78
+ "errorMessage",
79
+ "isRequired"
80
+ ],
81
+ "CheckboxGroup": [
82
+ "label",
83
+ "value",
84
+ "onChange",
85
+ "isInvalid",
86
+ "errorMessage",
87
+ "isRequired"
88
+ ],
89
+ "Fieldset": [
90
+ "label",
91
+ "isInvalid",
92
+ "errorMessage"
74
93
  ],
75
94
  "Checkbox": [
76
95
  "isDisabled"
@@ -104,7 +123,8 @@
104
123
  "a visible `label` on every field, or `aria-label` where the label is genuinely elsewhere",
105
124
  "`isInvalid` alongside `errorMessage` — the message alone styles the field without announcing it as invalid",
106
125
  "focus moved to the error summary after a failed submit",
107
- "`isRequired` on required fields rather than an asterisk in the label text"
126
+ "`isRequired` on required fields rather than an asterisk in the label text",
127
+ "a group-level error on the CheckboxGroup, RadioGroup or Fieldset it is about — \"choose at least one\" belongs to the set, not to its first box"
108
128
  ],
109
129
  "notes": [
110
130
  "Validate on blur and on submit, not on every keystroke: an error that appears while the user is still typing the value is wrong more often than right."
@@ -131,6 +151,11 @@
131
151
  "dont": "a Toggle inside a form that is submitted",
132
152
  "do": "Checkbox",
133
153
  "why": "a switch says the change already took effect"
154
+ },
155
+ {
156
+ "dont": "a row of Checkboxes under a heading, with the error in a paragraph beneath",
157
+ "do": "<CheckboxGroup label=\"…\" isRequired isInvalid errorMessage=\"…\">",
158
+ "why": "the heading is not announced with the boxes and the error is attached to none of them"
134
159
  }
135
160
  ]
136
161
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.84.0",
3
+ "version": "0.88.0",
4
4
  "usage": "A pattern is a documented composition of components, not a component. Read one here, then read the contracts of what it composes.",
5
5
  "patterns": {
6
6
  "AgentRun": {
@@ -52,10 +52,12 @@
52
52
  "TableRow",
53
53
  "TableCell",
54
54
  "Checkbox",
55
+ "Toolbar",
55
56
  "Button",
56
57
  "Badge",
57
58
  "Alert",
58
- "Input",
59
+ "SearchField",
60
+ "MultiSelect",
59
61
  "TagGroup"
60
62
  ],
61
63
  "drivenBy": "useTableSort",
@@ -90,6 +92,8 @@
90
92
  "Input",
91
93
  "Select",
92
94
  "Checkbox",
95
+ "CheckboxGroup",
96
+ "Fieldset",
93
97
  "Radio",
94
98
  "RadioGroup",
95
99
  "Toggle",
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Fieldset — and CheckboxGroup and RadioGroup, which render the same shell.
3
+ *
4
+ * The type is Form Field's (`.ion-field`, input.css): the legend is its label,
5
+ * the helper its helper, the error the same recolour. A group of fields reads
6
+ * as one field with several controls, so it must not introduce a second label
7
+ * style for agents and designers to choose between.
8
+ *
9
+ * The fieldset stays `display: block`. A rendered <legend> is laid out in the
10
+ * fieldset's border, not as a flex item, so a flex column here would space
11
+ * everything except the gap under the legend. Spacing is margins on the legend
12
+ * and helper, and a flex wrapper around the fields themselves.
13
+ *
14
+ * Two spacings. A Fieldset of Inputs spaces them as a form does, 16; the
15
+ * choice groups space their options at 8, which is what RadioGroup has always
16
+ * used — options in a list, not fields in a form.
17
+ */
18
+ .ion-fieldset {
19
+ --ion-fieldset-gap: var(--spacing-16);
20
+ --ion-fieldset-inline-gap: var(--spacing-16);
21
+
22
+ min-width: 0; /* a fieldset's default min-content width would stop wrapping */
23
+ margin: 0;
24
+ padding: 0;
25
+ border: none;
26
+ font-family: var(--font-family-sans);
27
+ }
28
+
29
+ .ion-fieldset--choices {
30
+ --ion-fieldset-gap: var(--spacing-8);
31
+ }
32
+
33
+ .ion-fieldset__legend {
34
+ /* Legend has default inline padding; the label beside an Input has none. */
35
+ padding: 0;
36
+ margin-bottom: var(--spacing-6);
37
+ color: var(--text-secondary);
38
+ font-size: var(--type-body-sm);
39
+ line-height: var(--type-body-sm-line-height);
40
+ font-weight: var(--font-weight-medium);
41
+ }
42
+
43
+ .ion-fieldset__content {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: var(--ion-fieldset-gap);
47
+ }
48
+
49
+ .ion-fieldset--horizontal .ion-fieldset__content {
50
+ flex-flow: row wrap;
51
+ column-gap: var(--ion-fieldset-inline-gap);
52
+ }
53
+
54
+ .ion-fieldset__helper {
55
+ display: block;
56
+ margin-top: var(--spacing-6);
57
+ color: var(--text-tertiary);
58
+ font-size: var(--type-body-sm);
59
+ line-height: var(--type-body-sm-line-height);
60
+ font-weight: var(--font-weight-regular);
61
+ }
62
+
63
+ .ion-fieldset--error .ion-fieldset__helper {
64
+ color: var(--text-error);
65
+ }
@@ -37,6 +37,7 @@
37
37
  @import url('./select.css');
38
38
  @import url('./checkbox.css');
39
39
  @import url('./radio.css');
40
+ @import url('./fieldset.css');
40
41
  @import url('./toggle.css');
41
42
  @import url('./menu.css');
42
43
  @import url('./avatar.css');
@@ -46,6 +47,7 @@
46
47
  @import url('./table.css');
47
48
  @import url('./pagination.css');
48
49
  @import url('./divider.css');
50
+ @import url('./toolbar.css');
49
51
  @import url('./link.css');
50
52
  @import url('./modal.css');
51
53
  @import url('./popover.css');
@@ -77,10 +79,12 @@
77
79
  @import url('./prompt-input.css');
78
80
  @import url('./tool-call.css');
79
81
  @import url('./number-input.css');
82
+ @import url('./search-field.css');
80
83
  @import url('./segmented-control.css');
81
84
  @import url('./sidebar.css');
82
85
  @import url('./file-upload.css');
83
86
  @import url('./combobox.css');
87
+ @import url('./multi-select.css');
84
88
 
85
89
  /*
86
90
  * date-range-picker AFTER date-picker, and the order is load-bearing rather
@@ -0,0 +1,104 @@
1
+ /*
2
+ * MultiSelect
3
+ *
4
+ * The field is Combobox's, which is Input's box: `.ion-input .ion-combobox`
5
+ * with `.ion-multi-select` on top, so the three never drift apart. The menu is
6
+ * Combobox's menu, block and all, which is also what keeps the contrast gate
7
+ * measuring these rows against `surface/raised` — see combobox.css.
8
+ *
9
+ * What is new is the row's check box, and the tags row under the field.
10
+ *
11
+ * THE CHECK IS CHECKBOX'S BOX, DRAWN
12
+ *
13
+ * 16px at radius/xs; empty it is 2px `border/stronger`, filled it drops to
14
+ * 1px of `border/primary-strong` over `surface/primary` with the flush bevel —
15
+ * the Small checkbox exactly. A multi-select row reads as "tick as many as you
16
+ * like" only if its mark is recognisably a checkbox; Combobox's single-select
17
+ * tint says "the one", which is the opposite.
18
+ *
19
+ * NO SELECTED TINT
20
+ *
21
+ * Combobox tints its one selected row. Here any number can be selected, and a
22
+ * list of tinted rows loses the one thing the tint is for — so the check says
23
+ * selected, and the background is left to say focused.
24
+ */
25
+ .ion-multi-select-menu__option {
26
+ flex-direction: row;
27
+ align-items: flex-start;
28
+ gap: var(--spacing-8);
29
+ }
30
+
31
+ .ion-multi-select-menu__text {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--spacing-2);
35
+ min-width: 0;
36
+ }
37
+
38
+ .ion-multi-select-menu__check {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ flex-shrink: 0;
43
+ width: var(--spacing-16);
44
+ height: var(--spacing-16);
45
+
46
+ /* Centred on the label's first line, as Checkbox centres its box. */
47
+ margin-top: calc((var(--type-body-line-height) - var(--spacing-16)) / 2);
48
+ background-color: var(--surface-default);
49
+ border: var(--border-width-thick) solid var(--border-stronger);
50
+ border-radius: var(--radius-xs);
51
+ }
52
+
53
+ .ion-multi-select-menu__check svg {
54
+ display: block;
55
+
56
+ /* Checkbox's derived mark at 16: box / 2 + 4. */
57
+ width: calc(var(--spacing-16) / 2 + var(--spacing-4));
58
+ height: calc(var(--spacing-16) / 2 + var(--spacing-4));
59
+ opacity: 0;
60
+ }
61
+
62
+ .ion-combobox-menu__option.ion-multi-select-menu__option[data-selected='true'] {
63
+ background-color: transparent;
64
+ }
65
+
66
+ .ion-combobox-menu__option.ion-multi-select-menu__option[data-focused='true'] {
67
+ background-color: var(--surface-hover);
68
+ }
69
+
70
+ .ion-multi-select-menu__option[data-selected='true'] .ion-combobox-menu__label {
71
+ font-weight: var(--font-weight-regular);
72
+ }
73
+
74
+ .ion-multi-select-menu__option[data-selected='true']
75
+ .ion-multi-select-menu__check {
76
+ /* Coloured only once filled: empty, the glyph is hidden and has no ground. */
77
+ color: var(--icon-on-color);
78
+ background-color: var(--surface-primary);
79
+ border-width: var(--border-width-default);
80
+ border-color: var(--border-primary-strong);
81
+ box-shadow: var(--ion-shadow-raised-flush-xs);
82
+ }
83
+
84
+ .ion-multi-select-menu__option[data-selected='true']
85
+ .ion-multi-select-menu__check
86
+ svg {
87
+ opacity: 1;
88
+ }
89
+
90
+ .ion-multi-select-menu__option[data-disabled='true']
91
+ .ion-multi-select-menu__check {
92
+ background-color: var(--surface-disabled);
93
+ border-color: var(--border-disabled);
94
+ box-shadow: none;
95
+ }
96
+
97
+ @media (forced-colors: active) {
98
+ .ion-multi-select-menu__option[data-selected='true']
99
+ .ion-multi-select-menu__check {
100
+ background-color: Highlight;
101
+ border-color: Highlight;
102
+ color: HighlightText;
103
+ }
104
+ }
@@ -178,23 +178,3 @@
178
178
  .ion-radio[data-disabled='true'] .ion-radio__label {
179
179
  color: var(--text-disabled);
180
180
  }
181
-
182
- /* The group stacks its radios; Figma has no group component, so this is the
183
- * minimum a fieldset needs rather than an invented layout. */
184
- .ion-radio-group {
185
- display: flex;
186
- flex-direction: column;
187
- gap: var(--spacing-8);
188
- border: none;
189
- margin: 0;
190
- padding: 0;
191
- }
192
-
193
- .ion-radio-group__label {
194
- color: var(--text-secondary);
195
- font-family: var(--font-family-sans);
196
- font-size: var(--type-body-sm);
197
- line-height: var(--type-body-sm-line-height);
198
- font-weight: var(--font-weight-medium);
199
- padding: 0;
200
- }
@@ -0,0 +1,70 @@
1
+ /*
2
+ * SearchField
3
+ *
4
+ * The box is `.ion-input` itself — the component renders Input's classes, so
5
+ * every size and state comes from input.css and cannot drift from it. This
6
+ * file owns only what Input does not have: the clear button, and silencing the
7
+ * browser's own.
8
+ *
9
+ * The clear button is (field height − 8px) square — 24 / 32 / 40 — like
10
+ * NumberInput's steps, so a Small field's target still meets WCAG 2.5.8.
11
+ */
12
+
13
+ /* Restates Input's surface at zero specificity, for the contrast gate — the
14
+ * same reason, and the same :where(), as number-input.css. */
15
+ :where(.ion-search-field) {
16
+ background-color: var(--surface-default);
17
+ }
18
+
19
+ .ion-search-field--clearable {
20
+ /* The button carries its own inset; full padding would double it. */
21
+ padding-inline-end: var(--spacing-4);
22
+ }
23
+
24
+ /*
25
+ * `type="search"` gets a native cancel button in Chromium and Safari, drawn in
26
+ * the platform's style and sitting beside ours. Two clear buttons, one of them
27
+ * unlabelled and unstyled, is the bug this removes.
28
+ */
29
+ .ion-search-field__field::-webkit-search-cancel-button,
30
+ .ion-search-field__field::-webkit-search-decoration {
31
+ display: none;
32
+ }
33
+
34
+ .ion-search-field__clear {
35
+ display: inline-flex;
36
+ flex: none;
37
+ align-items: center;
38
+ justify-content: center;
39
+ width: calc(var(--ion-input-height) - var(--spacing-8));
40
+ height: calc(var(--ion-input-height) - var(--spacing-8));
41
+ margin: 0;
42
+ padding: 0;
43
+ border: 0;
44
+ border-radius: var(--radius-sm);
45
+ background-color: transparent;
46
+ color: var(--icon-secondary);
47
+ cursor: pointer;
48
+ }
49
+
50
+ .ion-search-field__clear svg {
51
+ width: var(--ion-input-icon-size);
52
+ height: var(--ion-input-icon-size);
53
+ }
54
+
55
+ @media (hover: hover) {
56
+ .ion-search-field__clear:hover {
57
+ background-color: var(--surface-hover);
58
+ color: var(--icon-default);
59
+ }
60
+ }
61
+
62
+ .ion-search-field__clear:active {
63
+ background-color: var(--surface-pressed);
64
+ color: var(--icon-default);
65
+ }
66
+
67
+ .ion-search-field__clear:focus-visible {
68
+ outline: var(--border-width-thick) solid var(--border-focus);
69
+ outline-offset: calc(-1 * var(--border-width-thick));
70
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Toolbar
3
+ *
4
+ * Drawn in Figma as `Toolbar` on the Toolbar page. A row of controls 8 apart —
5
+ * the gap PageHeader's actions and the demo's bulk bar already used, so a
6
+ * toolbar dropped into either changes nothing — centred on one line, wrapping
7
+ * rather than overflowing on a narrow screen.
8
+ *
9
+ * Wrapping keeps DOM order, and DOM order is arrow-key order, so a wrapped
10
+ * toolbar still walks left to right, top to bottom.
11
+ *
12
+ * The separator is Divider, vertical: an <hr> with `aria-orientation`, which a
13
+ * screen reader announces between groups and focus skips. Inset 4 top and
14
+ * bottom so it reads as a break between groups rather than a border drawn
15
+ * around the row.
16
+ */
17
+ .ion-toolbar {
18
+ display: flex;
19
+ flex-wrap: wrap;
20
+ align-items: center;
21
+ gap: var(--spacing-8);
22
+ min-width: 0;
23
+ }
24
+
25
+ .ion-toolbar--vertical {
26
+ flex-direction: column;
27
+ align-items: stretch;
28
+ }
29
+
30
+ .ion-toolbar > .ion-divider--vertical {
31
+ margin-block: var(--spacing-4);
32
+ }
33
+
34
+ .ion-toolbar--vertical > .ion-divider--horizontal {
35
+ margin-inline: var(--spacing-4);
36
+ }
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.84.0. 84 components.
5
+ Version 0.88.0. 89 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`, `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`
16
+ `Accordion`, `AccordionItem`, `AgentActivity`, `AgentActivityStep`, `AgentStop`, `Alert`, `ApprovalGate`, `Avatar`, `AvatarGradient`, `AvatarGroup`, `Badge`, `Breadcrumb`, `BreadcrumbItem`, `Button`, `Card`, `ChartLegend`, `ChartTooltip`, `Checkbox`, `CheckboxGroup`, `Citation`, `CitationList`, `CitationListItem`, `Combobox`, `CommandPalette`, `ConfidenceIndicator`, `DatePicker`, `DateRangePicker`, `Divider`, `Drawer`, `EmptyState`, `Fieldset`, `FileUpload`, `FullCard`, `Header`, `Icon`, `Input`, `Kbd`, `Link`, `Logo`, `LogoMark`, `Menu`, `MenuItem`, `MenuSection`, `MenuTrigger`, `Modal`, `MultiSelect`, `NavItem`, `NumberInput`, `PageHeader`, `Pagination`, `PhoneInput`, `Popover`, `ProgressBar`, `PromptInput`, `Radio`, `RadioGroup`, `ScrollProgress`, `SearchField`, `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`, `Toolbar`, `ToolCall`, `Tooltip`
17
17
 
18
18
  ## Patterns
19
19