ionbase-ui 0.59.0 → 0.69.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 (102) 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/SegmentedControl.d.ts +63 -0
  38. package/dist/components/SegmentedControl.d.ts.map +1 -0
  39. package/dist/components/SegmentedControl.js +79 -0
  40. package/dist/components/SegmentedControl.js.map +1 -0
  41. package/dist/components/Sidebar.d.ts +109 -0
  42. package/dist/components/Sidebar.d.ts.map +1 -0
  43. package/dist/components/Sidebar.js +106 -0
  44. package/dist/components/Sidebar.js.map +1 -0
  45. package/dist/components/Stepper.d.ts +73 -0
  46. package/dist/components/Stepper.d.ts.map +1 -0
  47. package/dist/components/Stepper.js +82 -0
  48. package/dist/components/Stepper.js.map +1 -0
  49. package/dist/components/ToolCall.d.ts +66 -0
  50. package/dist/components/ToolCall.d.ts.map +1 -0
  51. package/dist/components/ToolCall.js +78 -0
  52. package/dist/components/ToolCall.js.map +1 -0
  53. package/dist/components/agent-status.d.ts +14 -0
  54. package/dist/components/agent-status.d.ts.map +1 -0
  55. package/dist/components/agent-status.js +16 -0
  56. package/dist/components/agent-status.js.map +1 -0
  57. package/dist/components/index.d.ts +17 -0
  58. package/dist/components/index.d.ts.map +1 -1
  59. package/dist/components/index.js +8 -0
  60. package/dist/components/index.js.map +1 -1
  61. package/dist/components/iso-date.d.ts +53 -0
  62. package/dist/components/iso-date.d.ts.map +1 -0
  63. package/dist/components/iso-date.js +102 -0
  64. package/dist/components/iso-date.js.map +1 -0
  65. package/dist/figma-descriptions.json +107 -52
  66. package/dist/figma-map.json +735 -2
  67. package/dist/meta/AgentActivityStep.json +2 -2
  68. package/dist/meta/DatePicker.json +270 -0
  69. package/dist/meta/DateRangePicker.json +284 -0
  70. package/dist/meta/Header.json +32 -2
  71. package/dist/meta/NumberInput.json +407 -0
  72. package/dist/meta/PromptInput.json +212 -0
  73. package/dist/meta/SegmentedControl.json +217 -0
  74. package/dist/meta/SegmentedControlItem.json +122 -0
  75. package/dist/meta/Sidebar.json +150 -0
  76. package/dist/meta/SidebarItem.json +184 -0
  77. package/dist/meta/SidebarSection.json +131 -0
  78. package/dist/meta/Stepper.json +135 -0
  79. package/dist/meta/StepperStep.json +148 -0
  80. package/dist/meta/Tabs.json +5 -0
  81. package/dist/meta/ToolCall.json +192 -0
  82. package/dist/meta/components.json +5784 -3297
  83. package/dist/meta/contrast.json +7776 -2478
  84. package/dist/meta/index.json +139 -2
  85. package/dist/meta/patterns/AgentRun.json +27 -0
  86. package/dist/meta/patterns/Form.json +9 -0
  87. package/dist/meta/patterns/PageShell.json +43 -3
  88. package/dist/meta/patterns/Wizard.json +20 -1
  89. package/dist/meta/patterns/index.json +10 -1
  90. package/dist/styles/date-picker.css +420 -0
  91. package/dist/styles/date-range-picker.css +280 -0
  92. package/dist/styles/header.css +26 -0
  93. package/dist/styles/index.css +15 -0
  94. package/dist/styles/number-input.css +83 -0
  95. package/dist/styles/prompt-input.css +127 -0
  96. package/dist/styles/segmented-control.css +182 -0
  97. package/dist/styles/sidebar.css +307 -0
  98. package/dist/styles/stepper.css +250 -0
  99. package/dist/styles/tool-call.css +202 -0
  100. package/llms.txt +2 -2
  101. package/package.json +20 -19
  102. package/LICENSE +0 -21
@@ -0,0 +1,420 @@
1
+ /*
2
+ * DatePicker — the segmented field, the calendar grid and the overlay that
3
+ * holds it. `date-range-picker.css` carries only what the range adds.
4
+ *
5
+ * Drawn in Figma as `Date Picker` (1394:536, the field — Type=Range is
6
+ * DateRangePicker's), `Calendar` (1395:1102) and `Calendar Day` (1395:318), from
7
+ * this file. Measurements were borrowed rather than invented, the same way
8
+ * AgentStop borrowed Button's and ApprovalGate borrowed Alert's:
9
+ *
10
+ * the field Input's box, unchanged — `.ion-date-picker` rides on
11
+ * `.ion-input` rather than restating a border, a radius and
12
+ * seven states that would then drift from the real thing
13
+ * the overlay Combobox Menu's surface, border, radius and shadow
14
+ * a day cell Menu's row metrics, squared off to the icon ladder
15
+ *
16
+ * The bet held here too: the drawing changed no structure. From now on the
17
+ * numbers move in Figma and come back through the token pipeline.
18
+ *
19
+ * WHY THE SPLIT ACROSS TWO FILES IS NOT ARBITRARY
20
+ *
21
+ * `build-meta.mjs` resolves a component's token list from `src/styles/<kebab
22
+ * name>.css`, falling back to the longest filename that is a PREFIX of it.
23
+ * `date-picker` is not a prefix of `date-range-picker`, so one shared file
24
+ * would leave DateRangePicker with `stylesheet: null` and an empty `tokens`
25
+ * array — and `verify-meta`'s "every custom property is defined by a token
26
+ * layer" check would then pass by having nothing to check. A gate that passes
27
+ * because it was handed nothing is the failure mode that file warns about.
28
+ */
29
+
30
+ /* ------------------------------------------------------------------ field */
31
+
32
+ /*
33
+ * The group is `.ion-input` plus this. Only the parts Input has no opinion
34
+ * about are new: how the segments sit, and the trailing calendar button.
35
+ */
36
+ .ion-date-picker {
37
+ cursor: text;
38
+ }
39
+
40
+ .ion-date-field {
41
+ display: flex;
42
+ flex: 1 1 auto;
43
+ align-items: center;
44
+ min-width: 0;
45
+ color: var(--text-secondary);
46
+ font-family: inherit;
47
+ font-size: inherit;
48
+ line-height: inherit;
49
+ font-weight: var(--font-weight-regular);
50
+ }
51
+
52
+ .ion-date-field:focus {
53
+ outline: none;
54
+ }
55
+
56
+ /*
57
+ * A segment is a spinbutton, not an input, so every affordance a text field
58
+ * gets for free has to be declared: the caret is suppressed, selection is off,
59
+ * and the tap target is the padded box rather than the glyph.
60
+ */
61
+ .ion-date-field__segment {
62
+ padding-inline: var(--spacing-2);
63
+ border-radius: var(--radius-2xs);
64
+ text-align: center;
65
+ font-variant-numeric: tabular-nums;
66
+ caret-color: transparent;
67
+ outline: none;
68
+ }
69
+
70
+ /*
71
+ * Focus is a filled box, NOT the focus ring the rest of the system uses.
72
+ *
73
+ * A ring is drawn outside the element it belongs to, and these elements sit
74
+ * shoulder to shoulder inside a bordered field with two or three pixels between
75
+ * them — an outline on the month segment overlaps the day segment beside it and
76
+ * clips against the field's own border. A filled box is contained, reads at a
77
+ * glance, and is what every native date input does for the same reason.
78
+ *
79
+ * `:focus-visible` would be wrong here too. The segment takes focus from a
80
+ * click as well as from the keyboard, and in both cases the user needs to know
81
+ * which of three near-identical numbers the next keystroke will change.
82
+ */
83
+ .ion-date-field__segment:focus {
84
+ background-color: var(--surface-primary);
85
+ color: var(--text-on-color);
86
+ }
87
+
88
+ /*
89
+ * An unfilled segment shows its placeholder — "mm", "dd", "yyyy" in the
90
+ * reader's own language, from Intl. Held at the placeholder token so an empty
91
+ * field reads as empty rather than as a date somebody chose.
92
+ */
93
+ .ion-date-field__segment--placeholder {
94
+ color: var(--text-tertiary);
95
+ }
96
+
97
+ .ion-date-field__segment:focus.ion-date-field__segment--placeholder {
98
+ color: var(--text-on-color);
99
+ }
100
+
101
+ /*
102
+ * The separators. Not focusable, no padding, no reserved width — they are the
103
+ * locale's own "/" or "." and must not be mistaken for something editable.
104
+ */
105
+ .ion-date-field__segment--literal {
106
+ padding-inline: 0;
107
+ color: var(--text-tertiary);
108
+ }
109
+
110
+ .ion-input[data-disabled='true'] .ion-date-field,
111
+ .ion-input[data-disabled='true'] .ion-date-field__segment {
112
+ color: var(--text-disabled);
113
+ cursor: not-allowed;
114
+ }
115
+
116
+ .ion-date-picker__button {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ flex-shrink: 0;
121
+ padding: 0;
122
+ background: none;
123
+ border: none;
124
+ border-radius: var(--radius-2xs);
125
+ color: var(--icon-interactive);
126
+ cursor: pointer;
127
+ }
128
+
129
+ .ion-date-picker__button svg {
130
+ width: var(--ion-input-icon-size);
131
+ height: var(--ion-input-icon-size);
132
+ }
133
+
134
+ .ion-input[data-disabled='true'] .ion-date-picker__button {
135
+ color: var(--icon-disabled);
136
+ cursor: not-allowed;
137
+ }
138
+
139
+ /* ---------------------------------------------------------------- overlay */
140
+
141
+ /*
142
+ * Transparent. It exists to catch the outside click and nothing else — the page
143
+ * behind stays legible because the field being edited is part of what the user
144
+ * is reading. Modal's scrim is visible because a modal replaces the page.
145
+ */
146
+ .ion-calendar-popover__underlay {
147
+ position: fixed;
148
+ inset: 0;
149
+ }
150
+
151
+ .ion-calendar-popover {
152
+ z-index: 1000;
153
+ padding: var(--spacing-12);
154
+ background-color: var(--surface-raised);
155
+ border: var(--border-width-default) solid var(--border-subtle);
156
+ border-radius: var(--radius-md);
157
+ box-shadow: var(--ion-shadow-shadow-lg);
158
+ }
159
+
160
+ /* --------------------------------------------------------------- calendar */
161
+
162
+ /*
163
+ * THE BACKGROUND IS NOT DECORATION — IT IS WHAT MAKES THE HOVERS MEASURABLE.
164
+ *
165
+ * `--surface-hover` is `rgba(0, 0, 0, 0.05)`: a tint with no colour of its own.
166
+ * The contrast gate resolves a translucent ground by compositing it over the
167
+ * BLOCK's default background, and a block that declares none leaves it with
168
+ * nothing to composite against — so every hover pairing inside came back
169
+ * *skipped*: not failing, not passing, unmeasured. `combobox.css` records the
170
+ * same trap from the other direction, and Drawer shipped it in 0.57.0 with only
171
+ * a count in a summary line to catch it.
172
+ *
173
+ * The calendar is always inside `.ion-calendar-popover`, which is already
174
+ * `surface/raised`, so this restates what is true rather than changing what is
175
+ * painted. What it buys is a gate that can answer the question.
176
+ */
177
+ .ion-calendar {
178
+ display: flex;
179
+ align-items: stretch;
180
+ gap: var(--spacing-12);
181
+ background-color: var(--surface-raised);
182
+ font-family: var(--font-family-sans);
183
+ }
184
+
185
+ .ion-calendar__body {
186
+ display: flex;
187
+ flex-direction: column;
188
+ gap: var(--spacing-8);
189
+ }
190
+
191
+ .ion-calendar__header {
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: space-between;
195
+ gap: var(--spacing-8);
196
+ }
197
+
198
+ .ion-calendar__title {
199
+ flex: 1 1 auto;
200
+ color: var(--text-default);
201
+ font-size: var(--type-body-sm);
202
+ line-height: var(--type-body-sm-line-height);
203
+ font-weight: var(--font-weight-semibold);
204
+ text-align: center;
205
+ text-transform: uppercase;
206
+ letter-spacing: 0.04em;
207
+ }
208
+
209
+ /*
210
+ * The per-grid month label. Present only when more than one month is shown —
211
+ * with a single grid the header title above already names the month, and two
212
+ * copies of "April 2026" stacked on top of each other reads as a rendering bug.
213
+ */
214
+ .ion-calendar__month-label {
215
+ display: none;
216
+ padding-block-end: var(--spacing-6);
217
+ color: var(--text-default);
218
+ font-size: var(--type-body-sm);
219
+ line-height: var(--type-body-sm-line-height);
220
+ font-weight: var(--font-weight-semibold);
221
+ text-align: center;
222
+ }
223
+
224
+ .ion-calendar:not([data-months='1']) .ion-calendar__month-label {
225
+ display: block;
226
+ }
227
+
228
+ /*
229
+ * ...and with more than one month the HEADER title is the one that goes, for
230
+ * the mirror-image reason: it names the whole visible range
231
+ * ("November – December 2024") while each grid already names itself. The
232
+ * visually-hidden <h2> beside it stays either way, so the calendar keeps its
233
+ * accessible name with no visible title at all.
234
+ */
235
+ .ion-calendar:not([data-months='1']) .ion-calendar__title {
236
+ visibility: hidden;
237
+ }
238
+
239
+ .ion-calendar__nav-button {
240
+ display: inline-flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ flex-shrink: 0;
244
+ width: var(--spacing-32);
245
+ height: var(--spacing-32);
246
+ padding: 0;
247
+ background-color: transparent;
248
+ border: none;
249
+ border-radius: var(--radius-sm);
250
+ color: var(--icon-secondary);
251
+ cursor: pointer;
252
+ }
253
+
254
+ .ion-calendar__nav-button svg {
255
+ width: var(--icon-size-sm);
256
+ height: var(--icon-size-sm);
257
+ }
258
+
259
+ @media (hover: hover) {
260
+ .ion-calendar__nav-button:hover {
261
+ background-color: var(--surface-hover);
262
+ }
263
+ }
264
+
265
+ /*
266
+ * The boundary case: a `minValue` inside the visible month leaves no previous
267
+ * month to step to. react-aria disables the arrow rather than removing it, so
268
+ * the header does not reflow — and the cursor says why.
269
+ */
270
+ .ion-calendar__nav-button:disabled {
271
+ color: var(--icon-disabled);
272
+ cursor: not-allowed;
273
+ }
274
+
275
+ .ion-calendar__months {
276
+ display: flex;
277
+ gap: var(--spacing-24);
278
+ }
279
+
280
+ .ion-calendar__grid {
281
+ border-collapse: collapse;
282
+
283
+ /*
284
+ * Fixed, so every column is the same width regardless of what is in it. Left
285
+ * automatic, a month whose widest cell is "30" and one whose widest is "9"
286
+ * lay out differently, and the two grids of a range calendar sitting side by
287
+ * side would not line up.
288
+ */
289
+ table-layout: fixed;
290
+ }
291
+
292
+ .ion-calendar__weekday {
293
+ width: var(--spacing-32);
294
+ padding-block-end: var(--spacing-4);
295
+ color: var(--text-tertiary);
296
+ font-size: var(--type-caption);
297
+ line-height: var(--type-caption-line-height);
298
+ font-weight: var(--font-weight-medium);
299
+ text-align: center;
300
+ }
301
+
302
+ .ion-calendar__cell {
303
+ padding: 0;
304
+ text-align: center;
305
+ }
306
+
307
+ /*
308
+ * A day.
309
+ *
310
+ * `--ion-calendar-day-size` is a slot rather than a literal because three rules
311
+ * need the same number — the cell, the range band's bleed and the today dot's
312
+ * offset — and a band that is a pixel off the cell it fills is visible.
313
+ */
314
+ .ion-calendar__day {
315
+ --ion-calendar-day-size: var(--spacing-32);
316
+
317
+ position: relative;
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ width: var(--ion-calendar-day-size);
322
+ height: var(--ion-calendar-day-size);
323
+ border-radius: var(--radius-full);
324
+ color: var(--text-default);
325
+ font-size: var(--type-body-sm);
326
+ line-height: var(--type-body-sm-line-height);
327
+ font-variant-numeric: tabular-nums;
328
+ cursor: pointer;
329
+ outline: none;
330
+ }
331
+
332
+ @media (hover: hover) {
333
+ .ion-calendar__day:not([data-disabled], [data-unavailable]):hover {
334
+ background-color: var(--surface-hover);
335
+ }
336
+ }
337
+
338
+ /*
339
+ * Keyboard focus is a ring, and unlike the segments above it is safe here:
340
+ * a day cell has a full cell's worth of space around it, and the ring sits in
341
+ * the gap rather than over the neighbour.
342
+ */
343
+ .ion-calendar__day:focus-visible {
344
+ outline: var(--border-width-thick) solid var(--border-focus);
345
+ outline-offset: calc(var(--border-width-default) * 2);
346
+ }
347
+
348
+ .ion-calendar__day[data-selected] {
349
+ background-color: var(--surface-primary);
350
+ color: var(--text-on-color);
351
+ font-weight: var(--font-weight-medium);
352
+ }
353
+
354
+ /*
355
+ * Today, marked whether or not it is selected.
356
+ *
357
+ * A dot under the numeral rather than a border around it: a ring would be one
358
+ * more circle in a grid of circles and would read as a second selected day. The
359
+ * dot moves to the on-colour token when the cell is selected so the mark
360
+ * survives the fill — a "today" cue that disappears the moment today is picked
361
+ * vanishes exactly when the user is checking that they picked it.
362
+ */
363
+ .ion-calendar__day[data-today]::after {
364
+ position: absolute;
365
+ bottom: var(--spacing-4);
366
+ left: 50%;
367
+ width: var(--spacing-4);
368
+ height: var(--spacing-4);
369
+ background-color: var(--icon-primary);
370
+ border-radius: var(--radius-full);
371
+ transform: translateX(-50%);
372
+ content: '';
373
+ }
374
+
375
+ .ion-calendar__day[data-today][data-selected]::after {
376
+ background-color: var(--icon-on-color);
377
+ }
378
+
379
+ /*
380
+ * Disabled and unavailable are DIFFERENT and are drawn differently on purpose.
381
+ *
382
+ * Disabled is outside the allowed bounds, or the whole control is off: the day
383
+ * is not part of the question. Unavailable is inside the bounds and taken — a
384
+ * booked night, a closed holiday. A user who sees both drawn the same way reads
385
+ * a fully-booked week as a broken calendar.
386
+ */
387
+ .ion-calendar__day[data-disabled] {
388
+ color: var(--text-disabled);
389
+ cursor: not-allowed;
390
+ }
391
+
392
+ .ion-calendar__day[data-unavailable] {
393
+ color: var(--text-disabled);
394
+ cursor: not-allowed;
395
+ text-decoration: line-through;
396
+ }
397
+
398
+ /*
399
+ * Forced colours: every fill above is a background-color, and Windows High
400
+ * Contrast discards those. Without this the selected day loses its fill and the
401
+ * grid becomes a wall of identical numbers with nothing marking the answer.
402
+ */
403
+ @media (forced-colors: active) {
404
+ .ion-calendar__day[data-selected] {
405
+ background-color: Highlight;
406
+ color: HighlightText;
407
+ forced-color-adjust: none;
408
+ }
409
+
410
+ .ion-calendar__day[data-disabled],
411
+ .ion-calendar__day[data-unavailable] {
412
+ color: GrayText;
413
+ }
414
+
415
+ .ion-date-field__segment:focus {
416
+ background-color: Highlight;
417
+ color: HighlightText;
418
+ forced-color-adjust: none;
419
+ }
420
+ }
@@ -0,0 +1,280 @@
1
+ /*
2
+ * DateRangePicker — only what the range adds.
3
+ *
4
+ * The field, the calendar grid, the day cell and the overlay all come from
5
+ * `date-picker.css` unchanged. What is here is the three things a range has and
6
+ * a single date does not: two fields with a dash between them, a band across
7
+ * the days in between, and the preset rail beside the grids.
8
+ *
9
+ * This is a separate file rather than a section of that one because
10
+ * `build-meta.mjs` resolves a component's token list from its own kebab-cased
11
+ * filename — see the note at the top of `date-picker.css` for what sharing one
12
+ * file would silently do to `verify-meta`'s token check.
13
+ */
14
+
15
+ /* ------------------------------------------------------------------ field */
16
+
17
+ .ion-date-range-picker {
18
+ cursor: text;
19
+ }
20
+
21
+ /*
22
+ * The dash between the two fields. `aria-hidden` in the markup, so this is
23
+ * purely a visual separator — held at the tertiary token so it reads as
24
+ * punctuation rather than as one of the two values it sits between.
25
+ */
26
+ .ion-date-range-picker__dash {
27
+ flex-shrink: 0;
28
+ padding-inline: var(--spacing-2);
29
+ color: var(--text-tertiary);
30
+ }
31
+
32
+ .ion-input[data-disabled='true'] .ion-date-range-picker__dash {
33
+ color: var(--text-disabled);
34
+ }
35
+
36
+ .ion-date-range-picker__button {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ flex-shrink: 0;
41
+ padding: 0;
42
+ background: none;
43
+ border: none;
44
+ border-radius: var(--radius-2xs);
45
+ color: var(--icon-interactive);
46
+ cursor: pointer;
47
+ }
48
+
49
+ .ion-date-range-picker__button svg {
50
+ width: var(--ion-input-icon-size);
51
+ height: var(--ion-input-icon-size);
52
+ }
53
+
54
+ .ion-input[data-disabled='true'] .ion-date-range-picker__button {
55
+ color: var(--icon-disabled);
56
+ cursor: not-allowed;
57
+ }
58
+
59
+ /* ------------------------------------------------------------- the band */
60
+
61
+ /*
62
+ * A day inside the range but not at either end.
63
+ *
64
+ * SQUARE, and bled sideways by half the gap between cells, so consecutive days
65
+ * join into one continuous bar instead of a row of separate tinted pills. The
66
+ * bleed is `--ion-calendar-day-size` derived rather than a literal because the
67
+ * cell size is a slot in `date-picker.css`: a band measured independently is a
68
+ * band that is one pixel off the first time that slot changes.
69
+ *
70
+ * The rule overrides `border-radius` from `.ion-calendar__day`, which is why it
71
+ * has to load after it — `styles/index.css` imports date-picker.css first.
72
+ *
73
+ * Range states are selected by the BEM modifiers Calendar already emits, not
74
+ * by `[data-in-range]` and friends. Same DOM, same specificity (0,2,0) — but
75
+ * the contrast gate keys contexts on `--modifier` classes and folds every
76
+ * `[data-*]` into `default`, so it merged the endpoint's white text with the
77
+ * in-range hover surface and reported 1.15:1 for a state that cannot render.
78
+ */
79
+ .ion-calendar__day.ion-calendar__day--in-range {
80
+ width: 100%;
81
+ background-color: var(--surface-primary-subtle);
82
+ border-radius: var(--radius-none);
83
+ color: var(--text-default);
84
+ font-weight: var(--font-weight-regular);
85
+ }
86
+
87
+ /*
88
+ * The two endpoints. Solid fill, and rounded only on the OUTER side so each one
89
+ * caps the band rather than floating beside it.
90
+ *
91
+ * Logical properties, not `border-top-left-radius`: in an RTL locale the start
92
+ * of the range is on the right, and a physically-named radius would cap the
93
+ * wrong end of every range an Arabic or Hebrew reader selects.
94
+ */
95
+ .ion-calendar__day.ion-calendar__day--range-start,
96
+ .ion-calendar__day.ion-calendar__day--range-end {
97
+ width: 100%;
98
+ background-color: var(--surface-primary);
99
+ color: var(--text-on-color);
100
+ }
101
+
102
+ .ion-calendar__day.ion-calendar__day--range-start {
103
+ border-start-start-radius: var(--radius-full);
104
+ border-start-end-radius: var(--radius-none);
105
+ border-end-start-radius: var(--radius-full);
106
+ border-end-end-radius: var(--radius-none);
107
+ }
108
+
109
+ .ion-calendar__day.ion-calendar__day--range-end {
110
+ border-start-start-radius: var(--radius-none);
111
+ border-start-end-radius: var(--radius-full);
112
+ border-end-start-radius: var(--radius-none);
113
+ border-end-end-radius: var(--radius-full);
114
+ }
115
+
116
+ /*
117
+ * A one-day range is both ends at once, and the two rules above would leave it
118
+ * square on both inner sides — a lozenge with no round edge anywhere. Fully
119
+ * round is what a single selected day looks like everywhere else in the grid.
120
+ */
121
+ .ion-calendar__day.ion-calendar__day--range-start.ion-calendar__day--range-end {
122
+ border-radius: var(--radius-full);
123
+ }
124
+
125
+ @media (hover: hover) {
126
+ .ion-calendar__day.ion-calendar__day--in-range:hover {
127
+ background-color: var(--surface-primary-subtle-hover);
128
+ }
129
+ }
130
+
131
+ /* --------------------------------------------------------------- presets */
132
+
133
+ /*
134
+ * The rail. A column of shortcuts at the start edge of the calendar, matching
135
+ * the reference this was drawn from.
136
+ *
137
+ * `role="group"` with a label rather than a `<nav>` or a list: these are
138
+ * buttons that set the value beside them, and a reader arriving at an unnamed
139
+ * cluster of eight buttons inside a date dialog has no way to tell what they
140
+ * are for.
141
+ *
142
+ * ITS OWN BLOCK, NOT `.ion-date-range-picker__presets`, and the reason is the
143
+ * same one `combobox.css` gives for `.ion-combobox-menu`. The rail's hover is
144
+ * `--surface-hover`, a five-percent black tint. The contrast gate composites a
145
+ * translucent ground over the BLOCK's own background — and
146
+ * `.ion-date-range-picker` has none of its own, because it borrows `.ion-input`'s
147
+ * from another stylesheet, which this file's parse cannot see. Named as an
148
+ * element of the field, every hover pairing in the rail came back *skipped*:
149
+ * unmeasured, while the gate reported green.
150
+ *
151
+ * Naming it for where it actually lives — on the popover's raised surface —
152
+ * gives the tint something to sit on, and the declaration below is true rather
153
+ * than defensive.
154
+ */
155
+ .ion-range-presets {
156
+ display: flex;
157
+ flex-direction: column;
158
+ flex-shrink: 0;
159
+ background-color: var(--surface-raised);
160
+ gap: var(--spacing-2);
161
+ padding-inline-end: var(--spacing-12);
162
+ border-inline-end: var(--border-width-default) solid var(--border-subtle);
163
+ }
164
+
165
+ .ion-range-presets__item {
166
+ padding: var(--spacing-6) var(--spacing-8);
167
+ background-color: transparent;
168
+ border: none;
169
+ border-radius: var(--radius-sm);
170
+ color: var(--text-default);
171
+ font-family: var(--font-family-sans);
172
+ font-size: var(--type-body-sm);
173
+ line-height: var(--type-body-sm-line-height);
174
+ text-align: start;
175
+ white-space: nowrap;
176
+ cursor: pointer;
177
+ }
178
+
179
+ @media (hover: hover) {
180
+ .ion-range-presets__item:hover {
181
+ background-color: var(--surface-hover);
182
+ }
183
+ }
184
+
185
+ .ion-range-presets__item:focus-visible {
186
+ outline: var(--border-width-thick) solid var(--border-focus);
187
+ outline-offset: calc(var(--border-width-default) * -1);
188
+ }
189
+
190
+ /*
191
+ * A preset whose range is the one currently selected.
192
+ *
193
+ * Tinted AND weighted, not tinted alone — `aria-pressed` carries it for a
194
+ * screen reader, and a background tint on its own is the colour-only
195
+ * distinction WCAG 1.4.1 is about for everyone else.
196
+ */
197
+ .ion-range-presets__item[data-selected] {
198
+ background-color: var(--surface-primary-subtle);
199
+ color: var(--text-primary);
200
+ font-weight: var(--font-weight-medium);
201
+ }
202
+
203
+ /* ---------------------------------------------------------------- footer */
204
+
205
+ /* Its own block too, and for the same reason as the rail above. */
206
+ .ion-range-footer {
207
+ display: flex;
208
+ justify-content: flex-end;
209
+ background-color: var(--surface-raised);
210
+ padding-block-start: var(--spacing-8);
211
+ border-block-start: var(--border-width-default) solid var(--border-subtle);
212
+ }
213
+
214
+ /*
215
+ * Clear, and there is no Apply beside it — the value commits when the second
216
+ * date lands. See the component's own doc comment for why a draft model was
217
+ * considered and rejected.
218
+ */
219
+ .ion-range-footer__clear {
220
+ padding: var(--spacing-4) var(--spacing-8);
221
+ background-color: transparent;
222
+ border: none;
223
+ border-radius: var(--radius-sm);
224
+ color: var(--text-interactive);
225
+ font-family: var(--font-family-sans);
226
+ font-size: var(--type-body-sm);
227
+ line-height: var(--type-body-sm-line-height);
228
+ font-weight: var(--font-weight-medium);
229
+ cursor: pointer;
230
+ }
231
+
232
+ @media (hover: hover) {
233
+ .ion-range-footer__clear:hover {
234
+ background-color: var(--surface-hover);
235
+ color: var(--text-interactive-hover);
236
+ }
237
+ }
238
+
239
+ .ion-range-footer__clear:focus-visible {
240
+ outline: var(--border-width-thick) solid var(--border-focus);
241
+ outline-offset: calc(var(--border-width-default) * -1);
242
+ }
243
+
244
+ /*
245
+ * The rail stacks above the grids on a narrow viewport rather than being
246
+ * dropped. A two-month calendar is already near the width of a small phone, and
247
+ * the shortcuts are the fastest path to a range on exactly the device where
248
+ * dragging across two grids is worst.
249
+ */
250
+ @media (width <= 32rem) {
251
+ .ion-calendar {
252
+ flex-direction: column;
253
+ }
254
+
255
+ .ion-range-presets {
256
+ flex-flow: row wrap;
257
+ padding-inline-end: 0;
258
+ padding-block-end: var(--spacing-8);
259
+ border-inline-end: none;
260
+ border-block-end: var(--border-width-default) solid var(--border-subtle);
261
+ }
262
+
263
+ .ion-calendar__months {
264
+ flex-direction: column;
265
+ }
266
+ }
267
+
268
+ @media (forced-colors: active) {
269
+ .ion-calendar__day[data-in-range] {
270
+ background-color: Highlight;
271
+ color: HighlightText;
272
+ forced-color-adjust: none;
273
+ }
274
+
275
+ .ion-range-presets__item[data-selected] {
276
+ background-color: Highlight;
277
+ color: HighlightText;
278
+ forced-color-adjust: none;
279
+ }
280
+ }