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
@@ -187,6 +187,32 @@
187
187
  }
188
188
  }
189
189
 
190
+ /*
191
+ * menuType="dialog": the toggle opens the caller's Drawer, so there is no
192
+ * panel. The slots keep their bar layout below the breakpoint, and the end
193
+ * slot is pushed to the far edge because the toggle now leads.
194
+ */
195
+ @media (width < 896px) {
196
+ .ion-header.ion-header--dialog-menu .ion-header__menu {
197
+ display: contents;
198
+ }
199
+
200
+ .ion-header--dialog-menu .ion-header__center {
201
+ flex-direction: row;
202
+ align-items: center;
203
+ width: auto;
204
+ }
205
+
206
+ .ion-header--dialog-menu .ion-header__end {
207
+ width: auto;
208
+ margin-inline-start: auto;
209
+ }
210
+
211
+ .ion-header--dialog-menu .ion-header__end > * {
212
+ flex: none;
213
+ }
214
+ }
215
+
190
216
  /*
191
217
  * Forced-colors: box-shadow is dropped by the OS, so the toggle would lose its
192
218
  * only edge. A border in ButtonText keeps it visible.
@@ -73,9 +73,24 @@
73
73
  @import url('./breadcrumb.css');
74
74
  @import url('./accordion.css');
75
75
  @import url('./drawer.css');
76
+ @import url('./stepper.css');
77
+ @import url('./prompt-input.css');
78
+ @import url('./tool-call.css');
79
+ @import url('./number-input.css');
80
+ @import url('./segmented-control.css');
81
+ @import url('./sidebar.css');
76
82
  @import url('./file-upload.css');
77
83
  @import url('./combobox.css');
78
84
 
85
+ /*
86
+ * date-range-picker AFTER date-picker, and the order is load-bearing rather
87
+ * than alphabetical. The range band overrides `.ion-calendar__day`'s
88
+ * border-radius at equal specificity — square sides so consecutive days join
89
+ * into one bar — and at equal specificity the later rule is the one that wins.
90
+ */
91
+ @import url('./date-picker.css');
92
+ @import url('./date-range-picker.css');
93
+
79
94
  /*
80
95
  * Visually hidden, but not hidden from assistive tech.
81
96
  *
@@ -0,0 +1,83 @@
1
+ /*
2
+ * NumberInput
3
+ *
4
+ * Drawn in Figma as `Number Input` (1389:473), from this file — Input's 21
5
+ * variants cloned, so the box keeps Input's bindings. The box is `.ion-input` itself — the component renders
6
+ * Input's classes, so every size and state comes from input.css and cannot
7
+ * drift from it. This file owns only what Input does not have: the − and +
8
+ * buttons, and tabular figures.
9
+ *
10
+ * − and + sit side by side at (field height − 8px): 24 / 32 / 40. Stacked
11
+ * chevrons would halve that, and a Small field's 16px targets would fall under
12
+ * WCAG 2.5.8's 24px.
13
+ */
14
+
15
+ /*
16
+ * Restates Input's default surface, at zero specificity, for the contrast gate.
17
+ *
18
+ * The gate resolves a translucent `surface/hover` against the block's own
19
+ * background — looked up in THIS stylesheet, and the box's background is
20
+ * declared in input.css. Without this, the step buttons' hover and pressed
21
+ * pairings came back skipped in both modes: unmeasured, not passing.
22
+ *
23
+ * `:where()` is what makes it safe. It matches what the box already paints and
24
+ * loses to every Input state, so disabled and read-only still repaint the box.
25
+ */
26
+ :where(.ion-number-input) {
27
+ background-color: var(--surface-default);
28
+ }
29
+
30
+ .ion-number-input--stepper {
31
+ /* The buttons carry their own inset; full padding would double it. */
32
+ padding-inline-end: var(--spacing-4);
33
+ }
34
+
35
+ /* Figures line up down a column of fields, which is where numbers usually are. */
36
+ .ion-number-input__field {
37
+ font-variant-numeric: tabular-nums;
38
+ }
39
+
40
+ .ion-number-input__steps {
41
+ display: inline-flex;
42
+ flex: none;
43
+ gap: var(--spacing-2);
44
+ }
45
+
46
+ .ion-number-input__step {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ width: calc(var(--ion-input-height) - var(--spacing-8));
51
+ height: calc(var(--ion-input-height) - var(--spacing-8));
52
+ margin: 0;
53
+ padding: 0;
54
+ border: 0;
55
+ border-radius: var(--radius-sm);
56
+ background-color: transparent;
57
+ color: var(--icon-secondary);
58
+ cursor: pointer;
59
+ }
60
+
61
+ .ion-number-input__step svg {
62
+ width: var(--ion-input-icon-size);
63
+ height: var(--ion-input-icon-size);
64
+ }
65
+
66
+ @media (hover: hover) {
67
+ .ion-number-input__step:hover {
68
+ background-color: var(--surface-hover);
69
+ color: var(--icon-default);
70
+ }
71
+ }
72
+
73
+ .ion-number-input__step:active {
74
+ background-color: var(--surface-pressed);
75
+ color: var(--icon-default);
76
+ }
77
+
78
+ /* At the bound, or with the whole field disabled or read-only. */
79
+ .ion-number-input__step:disabled {
80
+ background-color: transparent;
81
+ color: var(--icon-disabled);
82
+ cursor: not-allowed;
83
+ }
@@ -0,0 +1,127 @@
1
+ /*
2
+ * PromptInput
3
+ *
4
+ * Drawn in Figma as `Prompt Input` (1393:387), from this file, composing Icon
5
+ * Button and Agent Stop instances rather than redrawing them. The box is Textarea's — surface, border, hover and focus
6
+ * roles unchanged — one radius rung larger, because it holds a toolbar and
7
+ * reads as a surface rather than a field. The send control is Button `sm`
8
+ * made square; the stop control is AgentStop `sm`, unmodified.
9
+ *
10
+ * The border and focus live on the BOX, not the textarea. The box is what
11
+ * looks like the field, so it is what shows focus; a ring around only the text
12
+ * area inside it would draw a field within a field.
13
+ *
14
+ * States are BEM modifiers and `:focus-within`, never `[data-*]`, so the
15
+ * contrast gate measures them — see stepper.css for what happens otherwise.
16
+ */
17
+ .ion-prompt-input {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--spacing-8);
21
+ padding: var(--spacing-8);
22
+ border: var(--border-width-default) solid var(--border-default);
23
+ border-radius: var(--radius-lg);
24
+ background-color: var(--surface-default);
25
+ color: var(--text-default);
26
+ font-family: var(--font-family-sans);
27
+ cursor: text;
28
+ transition: border-color var(--ion-duration-base) var(--ion-ease-out);
29
+ }
30
+
31
+ @media (hover: hover) {
32
+ .ion-prompt-input:hover:not(.ion-prompt-input--disabled) {
33
+ border-color: var(--border-strong);
34
+ }
35
+ }
36
+
37
+ /*
38
+ * Same specificity as the hover rule above and declared after it, so focus
39
+ * wins while the pointer is still over the box — which it always is right
40
+ * after a click into the field.
41
+ */
42
+ .ion-prompt-input:focus-within:not(.ion-prompt-input--disabled) {
43
+ border-color: var(--border-focus);
44
+ }
45
+
46
+ .ion-prompt-input__attachments {
47
+ display: flex;
48
+ flex-wrap: wrap;
49
+ gap: var(--spacing-8);
50
+ padding-inline: var(--spacing-4);
51
+ }
52
+
53
+ .ion-prompt-input__field {
54
+ display: block;
55
+ width: 100%;
56
+ margin: 0;
57
+
58
+ /* Lines the text up with the actions' left edge below it. */
59
+ padding: var(--spacing-4);
60
+ border: 0;
61
+ outline: none;
62
+ background-color: transparent;
63
+ color: var(--text-default);
64
+ font: inherit;
65
+ font-size: var(--type-body);
66
+ line-height: var(--type-body-line-height);
67
+
68
+ /* Height is set from the content, up to maxRows; a grip would fight it. */
69
+ resize: none;
70
+ }
71
+
72
+ /* text/tertiary, as Textarea — text/placeholder was retired for 2.38:1. */
73
+ .ion-prompt-input__field::placeholder {
74
+ color: var(--text-tertiary);
75
+ opacity: 1;
76
+ }
77
+
78
+ .ion-prompt-input__toolbar {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: space-between;
82
+ gap: var(--spacing-8);
83
+ cursor: default;
84
+ }
85
+
86
+ .ion-prompt-input__actions {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: var(--spacing-4);
90
+ min-width: 0;
91
+ }
92
+
93
+ /*
94
+ * Square, at Button's own `sm` height. Two classes, so it outranks
95
+ * `.ion-button--sm` without depending on which stylesheet loads first.
96
+ */
97
+ .ion-prompt-input .ion-prompt-input__send {
98
+ --ion-button-padding-x: 0;
99
+
100
+ flex: none;
101
+ width: var(--spacing-32);
102
+ }
103
+
104
+ .ion-prompt-input__stop {
105
+ flex: none;
106
+ }
107
+
108
+ .ion-prompt-input--disabled {
109
+ border-color: var(--border-disabled);
110
+ background-color: var(--surface-disabled);
111
+ cursor: not-allowed;
112
+ }
113
+
114
+ .ion-prompt-input--disabled .ion-prompt-input__field {
115
+ color: var(--text-disabled);
116
+ cursor: not-allowed;
117
+ }
118
+
119
+ .ion-prompt-input--disabled .ion-prompt-input__field::placeholder {
120
+ color: var(--text-disabled);
121
+ }
122
+
123
+ @media (prefers-reduced-motion: reduce) {
124
+ .ion-prompt-input {
125
+ transition: none;
126
+ }
127
+ }
@@ -160,13 +160,24 @@
160
160
  * very percentage the reader opened the panel to act on. Keyboard users are
161
161
  * unaffected either way: the rows are real buttons in tab order, so Tab
162
162
  * scrolls each one into view without touching the wheel.
163
+ *
164
+ * CLOSED IS `display: none`, NOT ONLY INVISIBLE
165
+ *
166
+ * `visibility: hidden` removes the panel from sight and from tab order, but a
167
+ * hidden box still counts toward its scroll container's overflow. A rail near
168
+ * the right edge of a page therefore widened the page by however far the
169
+ * closed panel reached past it — 152px in the demo app — and a phone scrolled
170
+ * sideways into empty space. `display: none` takes it out of layout entirely.
171
+ * The reveal survives: `transition-behavior: allow-discrete` holds `display`
172
+ * until the fade-out finishes, and `@starting-style` gives the fade-in a
173
+ * starting point. A browser without either simply shows and hides it without
174
+ * the fade. `visibility` stays in the transition for the same browsers.
163
175
  */
164
176
  .ion-scroll-progress__panel {
165
177
  position: absolute;
166
- left: 0;
167
178
  top: 50%;
168
179
  z-index: 10;
169
- display: flex;
180
+ display: none;
170
181
  flex-direction: column;
171
182
  gap: var(--spacing-4);
172
183
  width: 220px;
@@ -181,20 +192,47 @@
181
192
  opacity: 0;
182
193
  visibility: hidden;
183
194
  pointer-events: none;
184
- transform: translateY(-50%) translateX(-4px);
195
+ transform: translateY(-50%) translateX(var(--ion-scroll-progress-slide));
185
196
  transition:
186
197
  opacity var(--ion-duration-base) var(--ion-ease-out),
187
198
  transform var(--ion-duration-base) var(--ion-ease-out),
188
- visibility var(--ion-duration-base);
199
+ visibility var(--ion-duration-base),
200
+ display var(--ion-duration-base) allow-discrete;
201
+ }
202
+
203
+ /*
204
+ * Placement picks the edge the panel is anchored to and the direction it
205
+ * slides in from — always from the rail outward. `right` is the original
206
+ * behaviour; `left` is for a rail against the right edge of the page, where a
207
+ * rightward panel would leave the screen.
208
+ */
209
+ .ion-scroll-progress__panel--right {
210
+ --ion-scroll-progress-slide: -4px;
211
+
212
+ left: 0;
213
+ }
214
+
215
+ .ion-scroll-progress__panel--left {
216
+ --ion-scroll-progress-slide: 4px;
217
+
218
+ right: 0;
189
219
  }
190
220
 
191
221
  .ion-scroll-progress__panel[data-open='true'] {
222
+ display: flex;
192
223
  opacity: 1;
193
224
  visibility: visible;
194
225
  pointer-events: auto;
195
226
  transform: translateY(-50%) translateX(0);
196
227
  }
197
228
 
229
+ @starting-style {
230
+ .ion-scroll-progress__panel[data-open='true'] {
231
+ opacity: 0;
232
+ transform: translateY(-50%) translateX(var(--ion-scroll-progress-slide));
233
+ }
234
+ }
235
+
198
236
  /*
199
237
  * `display: block`, not `flex`: `text-overflow: ellipsis` does not apply to an
200
238
  * anonymous flex item, so a flex row here would clip the label with no ellipsis
@@ -0,0 +1,182 @@
1
+ /*
2
+ * SegmentedControl
3
+ *
4
+ * Drawn in Figma as `Segmented Control` (1390:435) and `Segmented Control
5
+ * Item` (1390:332), from this file. It is Tabs' pill track, token for token: the muted
6
+ * track, the 2px gap, the default-surface selected segment, the page-surface
7
+ * selected hover, and the overall height ladder (32/40/48, 12/16/20 padding). Two
8
+ * components that look identical must not drift into looking almost
9
+ * identical, so if tabs.css changes its pill, this changes with it.
10
+ *
11
+ * Two deliberate differences from the pill track. It is `inline-flex` in every
12
+ * orientation, because a radiogroup has none. And the track radius is `lg`
13
+ * rather than `full`, so `--full-width` does not stretch a stadium shape across
14
+ * a card.
15
+ *
16
+ * States are BEM modifiers (`--selected`, `--disabled`, `--focus-visible`),
17
+ * never `[data-*]`, and the track surface is on the block, so the contrast gate
18
+ * measures the translucent hover against it — see stepper.css and
19
+ * number-input.css for the two ways that goes wrong.
20
+ */
21
+ .ion-segmented-control {
22
+ --ion-segmented-item-height: var(--spacing-32);
23
+ --ion-segmented-item-padding-x: var(--spacing-16);
24
+ --ion-segmented-item-gap: var(--spacing-8);
25
+ --ion-segmented-icon-size: var(--icon-size-md);
26
+ --ion-segmented-font-size: var(--type-body);
27
+ --ion-segmented-line-height: var(--type-body-line-height);
28
+
29
+ display: inline-flex;
30
+ gap: var(--spacing-2);
31
+ padding: var(--spacing-4);
32
+ border-radius: var(--radius-lg);
33
+ background-color: var(--surface-muted);
34
+ color: var(--text-tertiary);
35
+ font-family: var(--font-family-sans);
36
+ }
37
+
38
+ /* Item heights are the pill's 40/32/48 less the track's 4px inset on each side. */
39
+ .ion-segmented-control--sm {
40
+ --ion-segmented-item-height: var(--spacing-24);
41
+ --ion-segmented-item-padding-x: var(--spacing-12);
42
+ --ion-segmented-item-gap: var(--spacing-6);
43
+ --ion-segmented-icon-size: var(--icon-size-sm);
44
+ --ion-segmented-font-size: var(--type-body-sm);
45
+ --ion-segmented-line-height: var(--type-body-sm-line-height);
46
+ }
47
+
48
+ .ion-segmented-control--lg {
49
+ --ion-segmented-item-height: var(--spacing-40);
50
+ --ion-segmented-item-padding-x: var(--spacing-20);
51
+ --ion-segmented-font-size: var(--type-body-lg);
52
+ --ion-segmented-line-height: var(--type-body-lg-line-height);
53
+ }
54
+
55
+ .ion-segmented-control--full-width {
56
+ display: flex;
57
+ }
58
+
59
+ .ion-segmented-control__item {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ gap: var(--ion-segmented-item-gap);
64
+ min-height: var(--ion-segmented-item-height);
65
+ padding-inline: var(--ion-segmented-item-padding-x);
66
+ border-radius: var(--radius-md);
67
+ color: var(--text-tertiary);
68
+ font-size: var(--ion-segmented-font-size);
69
+ line-height: var(--ion-segmented-line-height);
70
+ font-weight: var(--font-weight-medium);
71
+ white-space: nowrap;
72
+ cursor: pointer;
73
+ user-select: none;
74
+ transition:
75
+ color var(--ion-duration-base) var(--ion-ease-out),
76
+ background-color var(--ion-duration-base) var(--ion-ease-out);
77
+ }
78
+
79
+ .ion-segmented-control--full-width .ion-segmented-control__item {
80
+ flex: 1 1 0;
81
+ min-width: 0;
82
+ }
83
+
84
+ .ion-segmented-control__icon {
85
+ display: inline-flex;
86
+ flex: none;
87
+ }
88
+
89
+ .ion-segmented-control__icon svg {
90
+ width: var(--ion-segmented-icon-size);
91
+ height: var(--ion-segmented-icon-size);
92
+ }
93
+
94
+ .ion-segmented-control__label {
95
+ overflow: hidden;
96
+ text-overflow: ellipsis;
97
+ }
98
+
99
+ /* `.ion-field` is a column that stretches its children; a labelled control
100
+ * keeps its own width unless it was asked to fill. */
101
+ .ion-segmented-control-field {
102
+ align-items: flex-start;
103
+ }
104
+
105
+ .ion-segmented-control-field--full-width {
106
+ align-items: stretch;
107
+ }
108
+
109
+ /*
110
+ * State order is specificity, not `:not()` lists. The contrast gate splits a
111
+ * selector at every comma and reads `--disabled` inside `:not(...)` as the
112
+ * disabled state, so a hover rule guarded with `:not(--selected, --disabled)`
113
+ * was measured as disabled text on a hover surface — two failures for a
114
+ * combination that never renders. Plain rules, ordered, say the same thing
115
+ * and are read correctly:
116
+ *
117
+ * hover (0,2,0) -> selected (0,1,0) -> selected hover (0,2,0)
118
+ * -> disabled (0,2,0) -> disabled selected (0,3,0)
119
+ */
120
+ @media (hover: hover) {
121
+ .ion-segmented-control__item:hover {
122
+ background-color: var(--surface-hover);
123
+ color: var(--text-default);
124
+ }
125
+ }
126
+
127
+ .ion-segmented-control__item--selected {
128
+ background-color: var(--surface-default);
129
+ color: var(--text-default);
130
+ }
131
+
132
+ @media (hover: hover) {
133
+ .ion-segmented-control__item--selected:hover {
134
+ background-color: var(--surface-page);
135
+ }
136
+ }
137
+
138
+ /*
139
+ * Focus lands on the hidden radio, so the ring is drawn on the segment around
140
+ * it. Tabs' ring: 2px `ring/focus`, offset by its own width.
141
+ */
142
+ .ion-segmented-control__item--focus-visible {
143
+ outline: var(--border-width-thick) solid var(--ring-focus);
144
+ outline-offset: var(--border-width-thick);
145
+ }
146
+
147
+ /*
148
+ * Two classes, so it outranks hover: a disabled segment does not react.
149
+ *
150
+ * The track's own surface rather than `transparent` — identical on screen,
151
+ * because the segment sits on the track. It is explicit for the contrast gate:
152
+ * with `transparent` the surface is inherited from the block, and the gate
153
+ * treats a single modifier that takes its surface from the block, while a
154
+ * compound of it (`--selected` + `--disabled`) supplies its own, as a state
155
+ * that cannot render — and drops it without counting a skip.
156
+ */
157
+ .ion-segmented-control .ion-segmented-control__item--disabled {
158
+ background-color: var(--surface-muted);
159
+ color: var(--text-disabled);
160
+ cursor: not-allowed;
161
+ }
162
+
163
+ /*
164
+ * A disabled SELECTED segment must still show which one is selected — a
165
+ * disabled control still has a value. Only the text dims.
166
+ */
167
+ .ion-segmented-control
168
+ .ion-segmented-control__item--selected.ion-segmented-control__item--disabled {
169
+ background-color: var(--surface-default);
170
+ }
171
+
172
+ @media (forced-colors: active) {
173
+ .ion-segmented-control__item--selected {
174
+ outline: var(--border-width-thick) solid;
175
+ }
176
+ }
177
+
178
+ @media (prefers-reduced-motion: reduce) {
179
+ .ion-segmented-control__item {
180
+ transition: none;
181
+ }
182
+ }