ionbase-ui 0.51.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 (174) hide show
  1. package/dist/components/Accordion.d.ts +54 -0
  2. package/dist/components/Accordion.d.ts.map +1 -0
  3. package/dist/components/Accordion.js +68 -0
  4. package/dist/components/Accordion.js.map +1 -0
  5. package/dist/components/AgentActivity.d.ts +2 -1
  6. package/dist/components/AgentActivity.d.ts.map +1 -1
  7. package/dist/components/AgentActivity.js +4 -17
  8. package/dist/components/AgentActivity.js.map +1 -1
  9. package/dist/components/Breadcrumb.d.ts +45 -0
  10. package/dist/components/Breadcrumb.d.ts.map +1 -0
  11. package/dist/components/Breadcrumb.js +31 -0
  12. package/dist/components/Breadcrumb.js.map +1 -0
  13. package/dist/components/Calendar.d.ts +20 -0
  14. package/dist/components/Calendar.d.ts.map +1 -0
  15. package/dist/components/Calendar.js +151 -0
  16. package/dist/components/Calendar.js.map +1 -0
  17. package/dist/components/CalendarPopover.d.ts +26 -0
  18. package/dist/components/CalendarPopover.d.ts.map +1 -0
  19. package/dist/components/CalendarPopover.js +45 -0
  20. package/dist/components/CalendarPopover.js.map +1 -0
  21. package/dist/components/Checkbox.d.ts +2 -1
  22. package/dist/components/Checkbox.d.ts.map +1 -1
  23. package/dist/components/Checkbox.js +4 -2
  24. package/dist/components/Checkbox.js.map +1 -1
  25. package/dist/components/Combobox.d.ts +86 -0
  26. package/dist/components/Combobox.d.ts.map +1 -0
  27. package/dist/components/Combobox.js +261 -0
  28. package/dist/components/Combobox.js.map +1 -0
  29. package/dist/components/DateField.d.ts +15 -0
  30. package/dist/components/DateField.d.ts.map +1 -0
  31. package/dist/components/DateField.js +74 -0
  32. package/dist/components/DateField.js.map +1 -0
  33. package/dist/components/DatePicker.d.ts +81 -0
  34. package/dist/components/DatePicker.d.ts.map +1 -0
  35. package/dist/components/DatePicker.js +102 -0
  36. package/dist/components/DatePicker.js.map +1 -0
  37. package/dist/components/DateRangePicker.d.ts +117 -0
  38. package/dist/components/DateRangePicker.d.ts.map +1 -0
  39. package/dist/components/DateRangePicker.js +152 -0
  40. package/dist/components/DateRangePicker.js.map +1 -0
  41. package/dist/components/Drawer.d.ts +52 -0
  42. package/dist/components/Drawer.d.ts.map +1 -0
  43. package/dist/components/Drawer.js +66 -0
  44. package/dist/components/Drawer.js.map +1 -0
  45. package/dist/components/FileUpload.d.ts +79 -0
  46. package/dist/components/FileUpload.d.ts.map +1 -0
  47. package/dist/components/FileUpload.js +218 -0
  48. package/dist/components/FileUpload.js.map +1 -0
  49. package/dist/components/Header.d.ts +10 -0
  50. package/dist/components/Header.d.ts.map +1 -1
  51. package/dist/components/Header.js +18 -3
  52. package/dist/components/Header.js.map +1 -1
  53. package/dist/components/NumberInput.d.ts +63 -0
  54. package/dist/components/NumberInput.d.ts.map +1 -0
  55. package/dist/components/NumberInput.js +102 -0
  56. package/dist/components/NumberInput.js.map +1 -0
  57. package/dist/components/ProgressBar.d.ts +46 -0
  58. package/dist/components/ProgressBar.d.ts.map +1 -0
  59. package/dist/components/ProgressBar.js +52 -0
  60. package/dist/components/ProgressBar.js.map +1 -0
  61. package/dist/components/PromptInput.d.ts +89 -0
  62. package/dist/components/PromptInput.d.ts.map +1 -0
  63. package/dist/components/PromptInput.js +130 -0
  64. package/dist/components/PromptInput.js.map +1 -0
  65. package/dist/components/SegmentedControl.d.ts +63 -0
  66. package/dist/components/SegmentedControl.d.ts.map +1 -0
  67. package/dist/components/SegmentedControl.js +79 -0
  68. package/dist/components/SegmentedControl.js.map +1 -0
  69. package/dist/components/Select.d.ts +9 -2
  70. package/dist/components/Select.d.ts.map +1 -1
  71. package/dist/components/Select.js.map +1 -1
  72. package/dist/components/Sidebar.d.ts +109 -0
  73. package/dist/components/Sidebar.d.ts.map +1 -0
  74. package/dist/components/Sidebar.js +106 -0
  75. package/dist/components/Sidebar.js.map +1 -0
  76. package/dist/components/Skeleton.d.ts +39 -0
  77. package/dist/components/Skeleton.d.ts.map +1 -0
  78. package/dist/components/Skeleton.js +50 -0
  79. package/dist/components/Skeleton.js.map +1 -0
  80. package/dist/components/Spinner.d.ts +45 -0
  81. package/dist/components/Spinner.d.ts.map +1 -0
  82. package/dist/components/Spinner.js +44 -0
  83. package/dist/components/Spinner.js.map +1 -0
  84. package/dist/components/Stepper.d.ts +73 -0
  85. package/dist/components/Stepper.d.ts.map +1 -0
  86. package/dist/components/Stepper.js +82 -0
  87. package/dist/components/Stepper.js.map +1 -0
  88. package/dist/components/Toggle.d.ts +2 -1
  89. package/dist/components/Toggle.d.ts.map +1 -1
  90. package/dist/components/Toggle.js +4 -2
  91. package/dist/components/Toggle.js.map +1 -1
  92. package/dist/components/ToolCall.d.ts +66 -0
  93. package/dist/components/ToolCall.d.ts.map +1 -0
  94. package/dist/components/ToolCall.js +78 -0
  95. package/dist/components/ToolCall.js.map +1 -0
  96. package/dist/components/agent-status.d.ts +14 -0
  97. package/dist/components/agent-status.d.ts.map +1 -0
  98. package/dist/components/agent-status.js +16 -0
  99. package/dist/components/agent-status.js.map +1 -0
  100. package/dist/components/index.d.ts +33 -0
  101. package/dist/components/index.d.ts.map +1 -1
  102. package/dist/components/index.js +16 -0
  103. package/dist/components/index.js.map +1 -1
  104. package/dist/components/iso-date.d.ts +53 -0
  105. package/dist/components/iso-date.d.ts.map +1 -0
  106. package/dist/components/iso-date.js +102 -0
  107. package/dist/components/iso-date.js.map +1 -0
  108. package/dist/components/resolve-selection.d.ts +36 -0
  109. package/dist/components/resolve-selection.d.ts.map +1 -0
  110. package/dist/components/resolve-selection.js +14 -0
  111. package/dist/components/resolve-selection.js.map +1 -0
  112. package/dist/figma-descriptions.json +255 -145
  113. package/dist/figma-map.json +3205 -1860
  114. package/dist/meta/Accordion.json +123 -0
  115. package/dist/meta/AccordionItem.json +90 -0
  116. package/dist/meta/AgentActivityStep.json +2 -2
  117. package/dist/meta/Breadcrumb.json +87 -0
  118. package/dist/meta/BreadcrumbItem.json +78 -0
  119. package/dist/meta/Checkbox.json +18 -1
  120. package/dist/meta/Combobox.json +265 -0
  121. package/dist/meta/DatePicker.json +270 -0
  122. package/dist/meta/DateRangePicker.json +284 -0
  123. package/dist/meta/Drawer.json +234 -0
  124. package/dist/meta/EmptyState.json +1 -1
  125. package/dist/meta/FileUpload.json +246 -0
  126. package/dist/meta/Header.json +32 -2
  127. package/dist/meta/NumberInput.json +407 -0
  128. package/dist/meta/PhoneInput.json +39 -39
  129. package/dist/meta/Popover.json +3 -3
  130. package/dist/meta/ProgressBar.json +166 -0
  131. package/dist/meta/PromptInput.json +212 -0
  132. package/dist/meta/SegmentedControl.json +217 -0
  133. package/dist/meta/SegmentedControlItem.json +122 -0
  134. package/dist/meta/Select.json +4 -3
  135. package/dist/meta/Sidebar.json +150 -0
  136. package/dist/meta/SidebarItem.json +184 -0
  137. package/dist/meta/SidebarSection.json +131 -0
  138. package/dist/meta/Skeleton.json +120 -0
  139. package/dist/meta/Spinner.json +123 -0
  140. package/dist/meta/Stepper.json +135 -0
  141. package/dist/meta/StepperStep.json +148 -0
  142. package/dist/meta/Tabs.json +5 -0
  143. package/dist/meta/Toggle.json +18 -1
  144. package/dist/meta/ToolCall.json +192 -0
  145. package/dist/meta/Tooltip.json +3 -3
  146. package/dist/meta/components.json +6759 -2705
  147. package/dist/meta/contrast.json +11075 -3098
  148. package/dist/meta/index.json +266 -8
  149. package/dist/meta/patterns/AgentRun.json +27 -0
  150. package/dist/meta/patterns/Form.json +9 -0
  151. package/dist/meta/patterns/PageShell.json +43 -3
  152. package/dist/meta/patterns/Wizard.json +20 -1
  153. package/dist/meta/patterns/index.json +10 -1
  154. package/dist/styles/accordion.css +102 -0
  155. package/dist/styles/breadcrumb.css +72 -0
  156. package/dist/styles/combobox.css +154 -0
  157. package/dist/styles/date-picker.css +420 -0
  158. package/dist/styles/date-range-picker.css +280 -0
  159. package/dist/styles/drawer.css +181 -0
  160. package/dist/styles/empty-state.css +16 -4
  161. package/dist/styles/file-upload.css +236 -0
  162. package/dist/styles/header.css +26 -0
  163. package/dist/styles/index.css +23 -0
  164. package/dist/styles/number-input.css +83 -0
  165. package/dist/styles/progress-bar.css +107 -0
  166. package/dist/styles/prompt-input.css +127 -0
  167. package/dist/styles/segmented-control.css +182 -0
  168. package/dist/styles/sidebar.css +307 -0
  169. package/dist/styles/skeleton.css +76 -0
  170. package/dist/styles/spinner.css +70 -0
  171. package/dist/styles/stepper.css +250 -0
  172. package/dist/styles/tool-call.css +202 -0
  173. package/llms.txt +2 -2
  174. package/package.json +2 -1
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,307 @@
1
+ /*
2
+ * Sidebar
3
+ *
4
+ * Drawn in Figma as `Sidebar` (1396:502), `Sidebar Section` (1396:501) and
5
+ * `Sidebar Item` (1396:412), from this file. Rows take Menu Item's rhythm — 32px, 8px inset, `md`
6
+ * radius — because a sidebar row and a menu row are the same density of
7
+ * choice. The column is `surface/default` against the page's `surface/page`,
8
+ * the same step a card takes. Hover and current are fills (`surface/hover`,
9
+ * `surface/selected`),
10
+ * which is exactly where this parts company with NavItem: NavItem was measured
11
+ * as a header link that recolours and never fills.
12
+ *
13
+ * Written against the contrast gate's known limits (AGENTS.md, "How a
14
+ * stylesheet goes unmeasured"): state is BEM modifiers, the sidebar's surface
15
+ * is on the block so the translucent hover has a ground in this file, and there
16
+ * are no `:not()` lists.
17
+ */
18
+ .ion-sidebar {
19
+ display: flex;
20
+ flex-direction: column;
21
+ inline-size: 100%;
22
+ block-size: 100%;
23
+ min-block-size: 0;
24
+ border-inline-end: var(--border-width-default) solid var(--border-subtle);
25
+ background-color: var(--surface-default);
26
+ color: var(--text-secondary);
27
+ font-family: var(--font-family-sans);
28
+ font-size: var(--type-body-sm);
29
+ line-height: var(--type-body-sm-line-height);
30
+ }
31
+
32
+ .ion-sidebar__header,
33
+ .ion-sidebar__footer {
34
+ display: flex;
35
+ flex: none;
36
+ flex-direction: column;
37
+ gap: var(--spacing-8);
38
+ padding: var(--spacing-12);
39
+ }
40
+
41
+ .ion-sidebar__header {
42
+ border-block-end: var(--border-width-default) solid var(--border-subtle);
43
+ }
44
+
45
+ .ion-sidebar__footer {
46
+ border-block-start: var(--border-width-default) solid var(--border-subtle);
47
+ }
48
+
49
+ /* Only the middle scrolls, so the switcher and Invite stay put. */
50
+ .ion-sidebar__body {
51
+ display: flex;
52
+ flex: 1 1 auto;
53
+ flex-direction: column;
54
+ gap: var(--spacing-16);
55
+ min-block-size: 0;
56
+ padding: var(--spacing-12) var(--spacing-8);
57
+ overflow-y: auto;
58
+ overscroll-behavior: contain;
59
+ }
60
+
61
+ /* ---------------------------------------------------------------- section */
62
+
63
+ .ion-sidebar__section {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: var(--spacing-2);
67
+ }
68
+
69
+ .ion-sidebar__section-header {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: var(--spacing-4);
73
+ min-block-size: var(--spacing-24);
74
+ padding-inline: var(--spacing-8) var(--spacing-4);
75
+ }
76
+
77
+ .ion-sidebar__section-title {
78
+ display: inline-flex;
79
+ flex: 1 1 auto;
80
+ align-items: center;
81
+ gap: var(--spacing-4);
82
+ margin: 0;
83
+ padding: 0;
84
+ border: 0;
85
+ border-radius: var(--radius-xs);
86
+ background: none;
87
+ color: var(--text-tertiary);
88
+ font: inherit;
89
+ font-size: var(--type-caption);
90
+ line-height: var(--type-caption-line-height);
91
+ font-weight: var(--font-weight-medium);
92
+ text-align: start;
93
+ }
94
+
95
+ button.ion-sidebar__section-title {
96
+ cursor: pointer;
97
+ }
98
+
99
+ .ion-sidebar__section-actions {
100
+ display: inline-flex;
101
+ flex: none;
102
+ align-items: center;
103
+ gap: var(--spacing-2);
104
+ }
105
+
106
+ /* -------------------------------------------------------------------- list */
107
+
108
+ .ion-sidebar__list {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--spacing-2);
112
+ margin: 0;
113
+ padding: 0;
114
+ list-style: none;
115
+ }
116
+
117
+ /*
118
+ * Indentation accumulates by nesting, so depth needs no prop and no inline
119
+ * style: each level is one more padded list inside the last.
120
+ */
121
+ .ion-sidebar__list--nested {
122
+ margin-block-start: var(--spacing-2);
123
+ padding-inline-start: var(--spacing-16);
124
+ }
125
+
126
+ /* A hidden list is `hidden`; the display above must not override it. */
127
+ .ion-sidebar__list[hidden] {
128
+ display: none;
129
+ }
130
+
131
+ /* --------------------------------------------------------------------- row */
132
+
133
+ .ion-sidebar__row {
134
+ position: relative;
135
+ display: flex;
136
+ align-items: center;
137
+ gap: var(--spacing-2);
138
+ min-block-size: var(--spacing-32);
139
+ padding-inline-end: var(--spacing-4);
140
+ border-radius: var(--radius-md);
141
+ color: var(--text-secondary);
142
+ }
143
+
144
+ .ion-sidebar__link {
145
+ display: flex;
146
+ flex: 1 1 auto;
147
+ align-items: center;
148
+ gap: var(--spacing-8);
149
+ min-inline-size: 0;
150
+ min-block-size: var(--spacing-32);
151
+ margin: 0;
152
+ padding: 0 var(--spacing-8);
153
+ border: 0;
154
+ border-radius: var(--radius-md);
155
+ background: none;
156
+ color: inherit;
157
+ font: inherit;
158
+ text-align: start;
159
+ text-decoration: none;
160
+ }
161
+
162
+ a.ion-sidebar__link,
163
+ button.ion-sidebar__link {
164
+ cursor: pointer;
165
+ }
166
+
167
+ .ion-sidebar__icon {
168
+ display: inline-flex;
169
+ flex: none;
170
+ color: var(--icon-secondary);
171
+ }
172
+
173
+ .ion-sidebar__icon svg,
174
+ .ion-sidebar__chevron svg {
175
+ width: var(--icon-size-sm);
176
+ height: var(--icon-size-sm);
177
+ }
178
+
179
+ /* Long names truncate; the full text is still the link's accessible name. */
180
+ .ion-sidebar__label {
181
+ flex: 1 1 auto;
182
+ min-inline-size: 0;
183
+ overflow: hidden;
184
+ text-overflow: ellipsis;
185
+ white-space: nowrap;
186
+ }
187
+
188
+ .ion-sidebar__badge {
189
+ flex: none;
190
+ color: var(--text-tertiary);
191
+ font-size: var(--type-caption);
192
+ line-height: var(--type-caption-line-height);
193
+ font-variant-numeric: tabular-nums;
194
+ }
195
+
196
+ .ion-sidebar__chevron {
197
+ display: inline-flex;
198
+ flex: none;
199
+ color: var(--icon-tertiary);
200
+ transition: transform var(--ion-duration-base) var(--ion-ease-out);
201
+ }
202
+
203
+ .ion-sidebar__chevron--expanded,
204
+ .ion-sidebar__section-title--expanded .ion-sidebar__chevron {
205
+ transform: rotate(90deg);
206
+ }
207
+
208
+ /* The separate expand button of a row that is also a link. */
209
+ .ion-sidebar__toggle {
210
+ display: inline-flex;
211
+ flex: none;
212
+ align-items: center;
213
+ justify-content: center;
214
+ width: var(--spacing-24);
215
+ height: var(--spacing-24);
216
+ margin: 0;
217
+ padding: 0;
218
+ border: 0;
219
+ border-radius: var(--radius-sm);
220
+ background: none;
221
+ color: inherit;
222
+ cursor: pointer;
223
+ }
224
+
225
+ /*
226
+ * Row actions. Hidden until the row is hovered or holds focus — `opacity`,
227
+ * not `display` or `visibility`, so they stay in the tab order and appear the
228
+ * moment focus reaches them. Always shown where there is no hover to reveal
229
+ * them, or a touch user could never find them.
230
+ */
231
+ .ion-sidebar__actions {
232
+ display: inline-flex;
233
+ flex: none;
234
+ align-items: center;
235
+ gap: var(--spacing-2);
236
+ opacity: 0;
237
+ }
238
+
239
+ .ion-sidebar__row:hover .ion-sidebar__actions,
240
+ .ion-sidebar__row:focus-within .ion-sidebar__actions {
241
+ opacity: 1;
242
+ }
243
+
244
+ @media (hover: none) {
245
+ .ion-sidebar__actions {
246
+ opacity: 1;
247
+ }
248
+ }
249
+
250
+ /* ------------------------------------------------------------------ states */
251
+
252
+ @media (hover: hover) {
253
+ .ion-sidebar__row:hover {
254
+ background-color: var(--surface-hover);
255
+ color: var(--text-default);
256
+ }
257
+ }
258
+
259
+ .ion-sidebar__row--current {
260
+ background-color: var(--surface-selected);
261
+ color: var(--text-default);
262
+ font-weight: var(--font-weight-medium);
263
+ }
264
+
265
+ .ion-sidebar__row--current .ion-sidebar__icon {
266
+ color: var(--icon-primary);
267
+ }
268
+
269
+ @media (hover: hover) {
270
+ .ion-sidebar__row--current:hover {
271
+ background-color: var(--surface-selected-hover);
272
+ }
273
+ }
274
+
275
+ /*
276
+ * Two classes, so a disabled row does not react to hover. The sidebar's surface
277
+ * is repeated rather than `transparent`, which the contrast gate would read as an
278
+ * inherited surface and drop — see the note in segmented-control.css.
279
+ */
280
+ .ion-sidebar .ion-sidebar__row--disabled {
281
+ background-color: var(--surface-default);
282
+ color: var(--text-disabled);
283
+ cursor: not-allowed;
284
+ }
285
+
286
+ .ion-sidebar .ion-sidebar__row--disabled .ion-sidebar__icon {
287
+ color: var(--icon-disabled);
288
+ }
289
+
290
+ .ion-sidebar__link:focus-visible,
291
+ .ion-sidebar__toggle:focus-visible,
292
+ .ion-sidebar__section-title:focus-visible {
293
+ outline: var(--border-width-thick) solid var(--border-focus);
294
+ outline-offset: calc(-1 * var(--border-width-thick));
295
+ }
296
+
297
+ @media (forced-colors: active) {
298
+ .ion-sidebar__row--current {
299
+ outline: var(--border-width-default) solid;
300
+ }
301
+ }
302
+
303
+ @media (prefers-reduced-motion: reduce) {
304
+ .ion-sidebar__chevron {
305
+ transition: none;
306
+ }
307
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Skeleton
3
+ *
4
+ * A pulse on one surface token rather than a sweeping gradient. The gradient
5
+ * version is prettier and costs more than it looks: `verify-contrast.mjs`
6
+ * resolves gradient stops as real pairings, so every skeleton would enter the
7
+ * contrast budget as text-shaped colour it is not — and a placeholder has no
8
+ * text to be legible.
9
+ */
10
+ .ion-skeleton {
11
+ --ion-skeleton-width: 100%;
12
+ --ion-skeleton-height: var(--spacing-16);
13
+ --ion-skeleton-radius: var(--radius-sm);
14
+
15
+ width: var(--ion-skeleton-width);
16
+ height: var(--ion-skeleton-height);
17
+ background-color: var(--surface-muted);
18
+ border-radius: var(--ion-skeleton-radius);
19
+ animation: ion-skeleton-pulse var(--ion-duration-slow) var(--ion-ease-in-out)
20
+ infinite;
21
+ }
22
+
23
+ .ion-skeleton-group {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--spacing-8);
27
+ width: var(--ion-skeleton-width, 100%);
28
+ }
29
+
30
+ /* Text sits on the line box so a skeleton paragraph occupies the height the
31
+ * real paragraph will, and the layout does not jump when content lands. */
32
+ .ion-skeleton--text {
33
+ --ion-skeleton-height: var(--type-body-line-height);
34
+ }
35
+
36
+ /* The last line short, because real paragraphs end mid-measure. */
37
+ .ion-skeleton--last {
38
+ --ion-skeleton-width: 60%;
39
+ }
40
+
41
+ .ion-skeleton--circle {
42
+ --ion-skeleton-height: var(--icon-size-lg);
43
+ --ion-skeleton-width: var(--icon-size-lg);
44
+ --ion-skeleton-radius: var(--radius-full);
45
+
46
+ flex: none;
47
+ }
48
+
49
+ .ion-skeleton--rect {
50
+ --ion-skeleton-height: var(--spacing-64);
51
+ --ion-skeleton-radius: var(--radius-md);
52
+ }
53
+
54
+ @keyframes ion-skeleton-pulse {
55
+ 50% {
56
+ opacity: 0.55;
57
+ }
58
+ }
59
+
60
+ /*
61
+ * Held still, unlike Spinner and ProgressBar — and the difference is the point.
62
+ * Those two are the only evidence that a wait is live, so they keep moving
63
+ * slowly. A skeleton says nothing a static block does not, so for a user who
64
+ * asked for less motion it simply stops.
65
+ */
66
+ @media (prefers-reduced-motion: reduce) {
67
+ .ion-skeleton {
68
+ animation: none;
69
+ }
70
+ }
71
+
72
+ @media (forced-colors: active) {
73
+ .ion-skeleton {
74
+ border: var(--border-width-default) solid GrayText;
75
+ }
76
+ }