@ropav/styles 0.3.0 → 0.5.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 (163) hide show
  1. package/README.md +160 -19
  2. package/dist/components/accordion.css +12 -5
  3. package/dist/components/alert-dialog.css +1 -1
  4. package/dist/components/alert.css +9 -1
  5. package/dist/components/autocomplete/autocomplete.styles.d.ts +33 -0
  6. package/dist/components/autocomplete/autocomplete.styles.d.ts.map +1 -1
  7. package/dist/components/autocomplete/autocomplete.styles.js +12 -0
  8. package/dist/components/autocomplete.css +39 -3
  9. package/dist/components/avatar.css +9 -0
  10. package/dist/components/badge.css +7 -0
  11. package/dist/components/button-group.css +6 -6
  12. package/dist/components/button.css +34 -11
  13. package/dist/components/calendar-year-picker.css +2 -1
  14. package/dist/components/calendar.css +8 -0
  15. package/dist/components/card.css +22 -1
  16. package/dist/components/chip.css +21 -0
  17. package/dist/components/close-button.css +8 -0
  18. package/dist/components/color-input-group/color-input-group.styles.d.ts +27 -0
  19. package/dist/components/color-input-group/color-input-group.styles.d.ts.map +1 -1
  20. package/dist/components/color-input-group/color-input-group.styles.js +6 -0
  21. package/dist/components/color-input-group.css +30 -1
  22. package/dist/components/color-swatch-picker.css +11 -0
  23. package/dist/components/combo-box/combo-box.styles.d.ts +27 -0
  24. package/dist/components/combo-box/combo-box.styles.d.ts.map +1 -1
  25. package/dist/components/combo-box/combo-box.styles.js +17 -7
  26. package/dist/components/combo-box.css +21 -1
  27. package/dist/components/date-input-group/date-input-group.styles.d.ts +27 -0
  28. package/dist/components/date-input-group/date-input-group.styles.d.ts.map +1 -1
  29. package/dist/components/date-input-group/date-input-group.styles.js +6 -0
  30. package/dist/components/date-input-group.css +30 -1
  31. package/dist/components/dropdown/dropdown.styles.d.ts.map +1 -1
  32. package/dist/components/dropdown/dropdown.styles.js +1 -1
  33. package/dist/components/dropdown.css +17 -12
  34. package/dist/components/index.css +4 -0
  35. package/dist/components/index.d.ts +4 -0
  36. package/dist/components/index.d.ts.map +1 -1
  37. package/dist/components/input/input.styles.d.ts +15 -0
  38. package/dist/components/input/input.styles.d.ts.map +1 -1
  39. package/dist/components/input/input.styles.js +6 -0
  40. package/dist/components/input-group/input-group.styles.d.ts +27 -0
  41. package/dist/components/input-group/input-group.styles.d.ts.map +1 -1
  42. package/dist/components/input-group/input-group.styles.js +6 -0
  43. package/dist/components/input-group.css +32 -1
  44. package/dist/components/input.css +23 -1
  45. package/dist/components/list-box/list-box.styles.d.ts +15 -0
  46. package/dist/components/list-box/list-box.styles.d.ts.map +1 -1
  47. package/dist/components/list-box/list-box.styles.js +15 -5
  48. package/dist/components/list-box-item.css +4 -2
  49. package/dist/components/list-box.css +19 -0
  50. package/dist/components/menu/menu.styles.d.ts +19 -1
  51. package/dist/components/menu/menu.styles.d.ts.map +1 -1
  52. package/dist/components/menu/menu.styles.js +9 -1
  53. package/dist/components/menu.css +25 -0
  54. package/dist/components/meter.css +12 -0
  55. package/dist/components/modal.css +1 -1
  56. package/dist/components/number-field/number-field.styles.d.ts +27 -0
  57. package/dist/components/number-field/number-field.styles.d.ts.map +1 -1
  58. package/dist/components/number-field/number-field.styles.js +6 -0
  59. package/dist/components/number-field.css +68 -1
  60. package/dist/components/pagination.css +10 -6
  61. package/dist/components/popover.css +15 -1
  62. package/dist/components/progress-bar.css +12 -0
  63. package/dist/components/progress-circle.css +12 -0
  64. package/dist/components/radio.css +7 -0
  65. package/dist/components/range-calendar.css +28 -0
  66. package/dist/components/search-field/search-field.styles.d.ts +27 -0
  67. package/dist/components/search-field/search-field.styles.d.ts.map +1 -1
  68. package/dist/components/search-field/search-field.styles.js +6 -0
  69. package/dist/components/search-field.css +33 -1
  70. package/dist/components/segmented-control/index.d.ts +2 -0
  71. package/dist/components/segmented-control/index.d.ts.map +1 -0
  72. package/dist/components/segmented-control/index.js +2 -0
  73. package/dist/components/segmented-control/segmented-control.styles.d.ts +73 -0
  74. package/dist/components/segmented-control/segmented-control.styles.d.ts.map +1 -0
  75. package/dist/components/segmented-control/segmented-control.styles.js +27 -0
  76. package/dist/components/segmented-control.css +193 -0
  77. package/dist/components/select/select.styles.d.ts +33 -0
  78. package/dist/components/select/select.styles.d.ts.map +1 -1
  79. package/dist/components/select/select.styles.js +12 -0
  80. package/dist/components/select.css +40 -3
  81. package/dist/components/separator.css +18 -1
  82. package/dist/components/sidebar/index.d.ts +2 -0
  83. package/dist/components/sidebar/index.d.ts.map +1 -0
  84. package/dist/components/sidebar/index.js +2 -0
  85. package/dist/components/sidebar/sidebar.styles.d.ts +190 -0
  86. package/dist/components/sidebar/sidebar.styles.d.ts.map +1 -0
  87. package/dist/components/sidebar/sidebar.styles.js +52 -0
  88. package/dist/components/sidebar.css +510 -0
  89. package/dist/components/skeleton.css +9 -0
  90. package/dist/components/slider.css +61 -0
  91. package/dist/components/splitter/index.d.ts +2 -0
  92. package/dist/components/splitter/index.d.ts.map +1 -0
  93. package/dist/components/splitter/index.js +2 -0
  94. package/dist/components/splitter/splitter.styles.d.ts +61 -0
  95. package/dist/components/splitter/splitter.styles.d.ts.map +1 -0
  96. package/dist/components/splitter/splitter.styles.js +26 -0
  97. package/dist/components/splitter.css +241 -0
  98. package/dist/components/surface.css +24 -0
  99. package/dist/components/switch.css +30 -0
  100. package/dist/components/table.css +11 -3
  101. package/dist/components/tabs.css +30 -2
  102. package/dist/components/tag.css +10 -0
  103. package/dist/components/textarea/textarea.styles.d.ts +15 -0
  104. package/dist/components/textarea/textarea.styles.d.ts.map +1 -1
  105. package/dist/components/textarea/textarea.styles.js +6 -0
  106. package/dist/components/textarea.css +23 -1
  107. package/dist/components/toast.css +1 -1
  108. package/dist/components/toggle-button-group.css +7 -7
  109. package/dist/components/toggle-button.css +17 -11
  110. package/dist/components/toolbar.css +8 -1
  111. package/dist/components/tooltip.css +15 -2
  112. package/dist/components/typography.css +21 -0
  113. package/dist/components/virtualizer/index.d.ts +2 -0
  114. package/dist/components/virtualizer/index.d.ts.map +1 -0
  115. package/dist/components/virtualizer/index.js +2 -0
  116. package/dist/components/virtualizer/virtualizer.styles.d.ts +44 -0
  117. package/dist/components/virtualizer/virtualizer.styles.d.ts.map +1 -0
  118. package/dist/components/virtualizer/virtualizer.styles.js +15 -0
  119. package/dist/components/virtualizer.css +64 -0
  120. package/dist/index.css +7 -2
  121. package/dist/index.js +5 -1
  122. package/dist/ropav.min.css +2 -2
  123. package/dist/themes/airbnb.css +231 -0
  124. package/dist/themes/all.css +17 -0
  125. package/dist/themes/coinbase.css +231 -0
  126. package/dist/themes/{default/variables.css → default.css} +28 -9
  127. package/dist/themes/discord.css +231 -0
  128. package/dist/themes/hero.css +262 -0
  129. package/dist/themes/lavender.css +231 -0
  130. package/dist/themes/mint.css +231 -0
  131. package/dist/themes/netflix.css +231 -0
  132. package/dist/themes/rabbit.css +231 -0
  133. package/dist/themes/shared/theme.css +19 -2
  134. package/dist/themes/sky.css +231 -0
  135. package/dist/themes/spotify.css +231 -0
  136. package/dist/themes/uber.css +231 -0
  137. package/dist/utilities/index.css +97 -0
  138. package/dist/variants/index.css +5 -15
  139. package/package.json +2 -3
  140. package/src/components/autocomplete/autocomplete.styles.ts +12 -0
  141. package/src/components/color-input-group/color-input-group.styles.ts +10 -0
  142. package/src/components/combo-box/combo-box.styles.ts +12 -0
  143. package/src/components/date-input-group/date-input-group.styles.ts +10 -0
  144. package/src/components/dropdown/dropdown.styles.ts +3 -1
  145. package/src/components/index.ts +4 -0
  146. package/src/components/input/input.styles.ts +6 -0
  147. package/src/components/input-group/input-group.styles.ts +10 -0
  148. package/src/components/list-box/list-box.styles.ts +6 -0
  149. package/src/components/menu/menu.styles.ts +10 -0
  150. package/src/components/number-field/number-field.styles.ts +10 -0
  151. package/src/components/search-field/search-field.styles.ts +10 -0
  152. package/src/components/segmented-control/index.ts +1 -0
  153. package/src/components/segmented-control/segmented-control.styles.ts +37 -0
  154. package/src/components/select/select.styles.ts +12 -0
  155. package/src/components/sidebar/index.ts +1 -0
  156. package/src/components/sidebar/sidebar.styles.ts +94 -0
  157. package/src/components/splitter/index.ts +1 -0
  158. package/src/components/splitter/splitter.styles.ts +44 -0
  159. package/src/components/textarea/textarea.styles.ts +6 -0
  160. package/src/components/virtualizer/index.ts +1 -0
  161. package/src/components/virtualizer/virtualizer.styles.ts +15 -0
  162. package/dist/themes/default/components/index.css +0 -6
  163. package/dist/themes/default/index.css +0 -8
package/README.md CHANGED
@@ -35,7 +35,7 @@ That single line pulls in, in layer order (`theme, base, components, utilities`)
35
35
  - Tailwind CSS v4 and `tw-animate-css`
36
36
  - base styles and the scrollbar system
37
37
  - the component layer — 85 files, one per component
38
- - the default theme: variables for light and dark, plus per-component theme overrides
38
+ - the default theme: tokens for light and dark
39
39
  - utilities and custom variants
40
40
 
41
41
  ### Importing only what you need
@@ -45,11 +45,12 @@ That single line pulls in, in layer order (`theme, base, components, utilities`)
45
45
 
46
46
  @import "@ropav/styles/components/button.css" layer(components);
47
47
  @import "@ropav/styles/components/chip.css" layer(components);
48
- @import "@ropav/styles/themes/default" layer(theme);
48
+ @import "@ropav/styles/themes/shared/theme.css";
49
+ @import "@ropav/styles/themes/default";
49
50
  ```
50
51
 
51
- > The granular subpaths — `./components/*.css`, `./base`, `./base/*.css`, `./themes/*`, `./utilities`,
52
- > `./variants` — exist **only in the published tarball**; `clean-package.config.json` writes them into
52
+ > The granular subpaths — `./components/*.css`, `./base`, `./base/*.css`, `./themes/*`, `./themes/*.css`,
53
+ > `./utilities`, `./variants` — exist **only in the published tarball**; `clean-package.config.json` writes them into
53
54
  > `exports` at `prepack` time. Inside the workspace, import the files from `packages/styles/` by relative path
54
55
  > instead.
55
56
 
@@ -74,14 +75,14 @@ packages/styles/
74
75
  │ └── scrollbar.css # Scrollbar system
75
76
  ├── components/ # 85 CSS files, one per component
76
77
  ├── themes/
77
- │ ├── default/
78
- ├── variables.css # Light and dark token sets
79
- ├── index.css # Theme entry point
80
- │ │ └── components/ # Per-component theme overrides
78
+ │ ├── default.css # Default theme — hand-written, light and dark token sets
79
+ │ ├── sky.css … rabbit.css # Ten more themes generated, do not edit
80
+ │ ├── all.css # Every bundled theme, for docs and playgrounds
81
81
  │ └── shared/
82
82
  │ └── theme.css # @theme block — derived values, radius scale, easing curves
83
83
  ├── utilities/index.css # Tailwind v4 @utility definitions
84
84
  ├── variants/index.css # Tailwind v4 @custom-variant definitions
85
+ ├── scripts/themes/ # Build-time theme generator — not published
85
86
  └── src/ # TypeScript: tv() variants + shared utility class strings
86
87
  ```
87
88
 
@@ -113,31 +114,171 @@ component that publishes it as an attribute.
113
114
 
114
115
  ## Theming
115
116
 
116
- Light is the default on `:root`; dark turns on with a `.dark` class or `[data-theme="dark"]`:
117
+ Two independent axes. `data-theme` picks the palette, a `light`/`dark` class picks the appearance:
117
118
 
118
119
  ```html
119
- <html data-theme="dark"></html>
120
+ <html data-theme="netflix" class="dark"></html>
120
121
  ```
121
122
 
122
- Every token is a CSS custom property, so overriding them in your own stylesheet is enough to retheme — no
123
- rebuild of this package:
123
+ Either can be left out. With no `data-theme` you get the default theme; with no class you get light.
124
+ The default theme also answers to `[data-theme="light"]` and `[data-theme="dark"]`, which is what it
125
+ did before the other themes existed.
126
+
127
+ ### Bundled themes
128
+
129
+ The default one, plus a theme per brand ported from HeroUI's gallery — `hero` among them, being
130
+ HeroUI's own palette. The default is already in `@ropav/styles`; the others are opt-in, one file
131
+ each:
132
+
133
+ ```css
134
+ @import "@ropav/styles";
135
+ @import "@ropav/styles/themes/netflix";
136
+ ```
137
+
138
+ | `data-theme` | | `data-theme` | | `data-theme` |
139
+ | --- | --- | --- | --- | --- |
140
+ | `default` | | `mint` | | `coinbase` |
141
+ | `hero` | | `netflix` | | `airbnb` |
142
+ | `sky` | | `uber` | | `discord` |
143
+ | `lavender` | | `spotify` | | `rabbit` |
144
+
145
+ Each carries both a light and a dark palette, so a theme is a brand rather than an appearance. There is
146
+ no `netflix-dark` — it is `data-theme="netflix"` plus `class="dark"`.
147
+
148
+ `themes/all.css` pulls in all of them at once. That is for docs and playgrounds; an app should import
149
+ only the themes it actually offers, since each is around 10 kB before compression.
150
+
151
+ Every theme but `default` and `hero` is **generated** — edit `scripts/themes/presets.ts` and run
152
+ `pnpm generate:themes`, never the CSS. A preset is four numbers (accent lightness, chroma and hue, plus
153
+ the chroma of the neutral ramp), a radius pair, and any exact brand colours that should beat the
154
+ calculated ones.
155
+
156
+ Those two stay hand-written, each file's header saying what the generator cannot reproduce about it.
157
+ Their presets carry the label and the presentation order; the CSS is the source of truth for their
158
+ tokens.
159
+
160
+ ### Retheming
161
+
162
+ Every token is a CSS custom property, so overriding them in your own stylesheet is enough — no rebuild
163
+ of this package, and no need to go through a theme file at all:
124
164
 
125
165
  ```css
126
166
  :root {
127
167
  --accent: oklch(0.62 0.19 253.83);
128
168
  --radius: 0.5rem;
129
- --field-radius: calc(var(--radius) * 1.5);
130
169
  }
131
170
  ```
132
171
 
133
- Tokens cover base colors (`--background`, `--surface`, `--overlay`, `--muted`), interactive and status colors
134
- (`--accent`, `--success`, `--warning`, `--danger`, each with a `-foreground` and a derived `-hover`), form
135
- fields (`--field-background`, `--field-border`, `--field-radius`, …), layout knobs (`--spacing`,
136
- `--border-width`, `--radius`, `--ring-offset-width`, `--cursor-interactive`), the scrollbar set, and shadows.
172
+ Author CSS outside a cascade layer outranks everything here, so these win wherever you put them.
173
+
174
+ To add a theme rather than change the default one, write the same token block under your own attribute.
175
+ It has to redeclare the *derived* tokens too, not just the authored ones: a custom property substitutes
176
+ `var()` where it is declared, so an `--accent-hover` inherited from `:root` would still be mixed from
177
+ the root's `--accent`. Copy a generated theme and edit it, or generate one.
178
+
179
+ ```css
180
+ @layer theme {
181
+ [data-theme="ocean"] {
182
+ color-scheme: light;
183
+ --accent: oklch(0.62 0.14 220);
184
+ --accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%);
185
+ /* … */
186
+ }
187
+ }
188
+ ```
189
+
190
+ A theme only needs to carry colours. Everything keyed on neither the palette nor the appearance —
191
+ `--spacing`, `--cursor-*`, the primitives, the shadows, `--backdrop` — stays on `:root` and `.dark` in
192
+ `themes/default.css`, both of which keep matching an element that carries a `data-theme`.
193
+
194
+ ### Tokens
137
195
 
138
- **`themes/default/variables.css` is the source of truth read it rather than a list in a README**, which goes
196
+ Base colors (`--background`, `--surface`, `--overlay`, `--muted`), interactive and status colors
197
+ (`--accent`, `--success`, `--warning`, `--danger`, each with a `-foreground` and a derived `-hover` and
198
+ `-soft`), form fields (`--field-background`, `--field-border`, `--field-radius`, …), layout knobs
199
+ (`--spacing`, `--border-width`, `--radius`, `--ring-offset-width`, `--cursor-interactive`), the
200
+ scrollbar set, and shadows.
201
+
202
+ **`themes/default.css` is the source of truth — read it rather than a list in a README**, which goes
139
203
  stale the moment a token moves. `themes/shared/theme.css` holds what is derived from those tokens: the
140
- `--radius-xs` … `--radius-4xl` scale, `color-mix()` hover and soft variants, and the easing curves.
204
+ `--radius-xs` … `--radius-4xl` scale and the easing curves.
205
+
206
+ ### Reduced motion
207
+
208
+ `data-reduce-motion="true"` on any ancestor forces animations off, `"false"` forces them on regardless
209
+ of the OS setting, and with neither the `prefers-reduced-motion` media query decides. Defined as the
210
+ `motion-reduce` / `motion-safe` variants in `variants/index.css`.
211
+
212
+ ### Forced colors
213
+
214
+ Forced Colors Mode - Windows High Contrast - replaces author colours with the user's own palette and
215
+ strips `box-shadow` outright. That second part is what makes it more than a colour question here:
216
+ every ring in this library *is* a `box-shadow`, because that is what `ring-*` compiles to, and it sits
217
+ on top of `outline-none`. Left alone, a focused control would have no indicator at all.
218
+
219
+ Nothing to opt into. The three focus utilities - `focus-ring`, `focus-field-ring`,
220
+ `invalid-field-ring` - draw an outline back in `Highlight` under `forced-colors: active`, so any
221
+ component that goes through `status-focused`, `status-focused-field` or `status-invalid-field` is
222
+ covered without a line of its own. `status-disabled` picks up `GrayText` the same way.
223
+
224
+ The other half is state carried only by `background-color`, which the override flattens into its
225
+ surroundings. Where selection is *just* a background - a tag, a calendar day, a table row - apply
226
+ `forced-selected`, **as an `@apply` statement of its own**:
227
+
228
+ ```css
229
+ .thing[data-selected="true"] {
230
+ @apply bg-accent text-accent-foreground;
231
+ @apply forced-selected;
232
+ }
233
+ ```
234
+
235
+ Folded into the line above it, Tailwind sorts the list and hoists the nested media query over the
236
+ plain declarations, and the `background-color` it exists to override wins instead. Where selection
237
+ also moves a thumb or shows a glyph, the component writes its own `forced-colors` block, because
238
+ those parts need colours of their own - see `switch.css`, `radio.css`, `tabs.css`, `range-calendar.css`,
239
+ `slider.css` and `skeleton.css`.
240
+
241
+ Use the system colour keywords, not tokens: `Highlight` / `HighlightText` for a selected control,
242
+ `CanvasText` on `Canvas` for ordinary content, `ButtonBorder` for a control's edge, `GrayText` for
243
+ disabled. They are the only colours exempt from the override.
244
+
245
+ Watch for `transparent` used as a spacer. Forced colors preserves a transparent *background*, but a
246
+ transparent `border-color` is turned **opaque** - so a border held open purely to reserve layout space
247
+ (`slider.css` does this at both ends of the track) stops being invisible and renders as a solid block.
248
+ Restate those as `Canvas` inside the media query.
249
+
250
+ One trap, and `getComputedStyle` cannot see it. Chromium paints a `Canvas`-coloured **backplate**
251
+ behind the text of any element that has text, so that text over an image stays legible. It lands on
252
+ top of that element's own background, so a `Highlight` fill carrying `HighlightText` renders as a
253
+ solid plate with the label invisible inside it - the colours are all correct and the component is
254
+ unreadable. `forced-color-adjust: none` is what suppresses the backplate. `forced-selected` already
255
+ carries it; anything hand-rolling the same pairing needs it too, including the case where the fill
256
+ sits on a different element than the text (`tabs.css`, `range-calendar.css`). Reach for it only
257
+ alongside explicit system colours - on its own it just hands the author's palette back, which is the
258
+ opposite of the point.
259
+
260
+ Structure has to survive the mode as well as state. A component that is only a tinted fill and a
261
+ shadow - a card, an alert, a text field, a chip - renders as loose text once both are taken, which
262
+ reads as no component at all. So every container carries an inset `CanvasText` outline under the
263
+ mode and every control a `ButtonBorder` one, on the variants that actually paint; a transparent
264
+ surface is left alone, since framing it would invent a box that was never there. A separator is the
265
+ exception that proves it: the whole thing *is* its background, so it takes a colour rather than an
266
+ edge.
267
+
268
+ Two things guard it. `forced-colors.browser.test.ts` in the `ropav` package asserts the stylesheet
269
+ directly, and the Storybook package runs **every story** through an audit that renders it twice - the
270
+ mode off, then on over CDP - and fails on anything that painted something and stops painting it. The
271
+ second one exists because the first cannot see this class of bug on its own: the colour override runs
272
+ after the cascade and the backplate is painted later still, so a component reports every declared
273
+ colour correctly while rendering as a blank block. Three bugs shipped that way before the audit
274
+ existed.
275
+
276
+ To look at it by hand, use Chromium's rendering panel - DevTools, `Cmd+Shift+P`, "Show Rendering",
277
+ then *Emulate CSS media feature forced-colors* - and note that **macOS has no Forced Colors Mode at
278
+ all**: "Increase contrast" maps to `prefers-contrast: more`, so switching it on tests nothing. There
279
+ is deliberately no toolbar control in Storybook. `data-reduce-motion` is an attribute a decorator can
280
+ set, but this is a browser-level media feature an iframe cannot turn on for itself, and faking it with
281
+ a stylesheet of system colours would miss the `box-shadow` removal - the part that actually breaks.
141
282
 
142
283
  ## Build
143
284
 
@@ -116,7 +116,14 @@
116
116
  /* Surface variant */
117
117
  .accordion--surface {
118
118
  @apply bg-surface;
119
- border-radius: min(32px, var(--radius-3xl));
119
+ border-radius: min(32px, var(--component-radius));
120
+
121
+ /* Forced Colors Mode flattens the fill onto the page and strips the shadow, so the container
122
+ * stops having any shape and its contents sit loose on the page. */
123
+ @media (forced-colors: active) {
124
+ outline: 1px solid CanvasText;
125
+ outline-offset: -1px;
126
+ }
120
127
 
121
128
  .accordion__trigger {
122
129
  @media (hover: hover) {
@@ -140,13 +147,13 @@
140
147
 
141
148
  /* Special rounding for first and last items in surface variant */
142
149
  &:first-child [data-slot="accordion-trigger"] {
143
- border-start-start-radius: min(32px, var(--radius-3xl));
144
- border-start-end-radius: min(32px, var(--radius-3xl));
150
+ border-start-start-radius: min(32px, var(--component-radius));
151
+ border-start-end-radius: min(32px, var(--component-radius));
145
152
  }
146
153
 
147
154
  &:last-child:not(:has([data-slot="accordion-trigger"][aria-expanded="true"]))
148
155
  [data-slot="accordion-trigger"] {
149
- border-end-start-radius: min(32px, var(--radius-3xl));
150
- border-end-end-radius: min(32px, var(--radius-3xl));
156
+ border-end-start-radius: min(32px, var(--component-radius));
157
+ border-end-end-radius: min(32px, var(--component-radius));
151
158
  }
152
159
  }
@@ -145,7 +145,7 @@
145
145
  @apply flex min-h-0 w-full flex-col;
146
146
  @apply max-h-full;
147
147
  @apply bg-overlay shadow-overlay outline-none;
148
- border-radius: min(32px, var(--radius-3xl));
148
+ border-radius: min(32px, var(--component-radius));
149
149
  @apply p-6;
150
150
  @apply overflow-clip;
151
151
  @apply pointer-events-auto;
@@ -3,7 +3,15 @@
3
3
  /* Base alert styles */
4
4
  .alert {
5
5
  @apply flex w-full flex-row items-start justify-start gap-4 bg-surface px-4 py-3 shadow-surface;
6
- border-radius: min(32px, var(--radius-3xl));
6
+
7
+ /* Forced Colors Mode flattens the fill onto the page and strips the shadow, so the container
8
+ * stops having any shape - its contents just sit loose on the page. An outline rather than a
9
+ * border so nothing shifts, and it follows the radius the component sets. */
10
+ @media (forced-colors: active) {
11
+ outline: 1px solid CanvasText;
12
+ outline-offset: -1px;
13
+ }
14
+ border-radius: min(32px, var(--component-radius));
7
15
  }
8
16
 
9
17
  /* Alert elements */
@@ -7,6 +7,17 @@ export declare const autocompleteVariants: import("tailwind-variants").TVReturnT
7
7
  trigger: string;
8
8
  };
9
9
  };
10
+ size: {
11
+ lg: {
12
+ base: string;
13
+ popover: string;
14
+ };
15
+ md: {};
16
+ sm: {
17
+ base: string;
18
+ popover: string;
19
+ };
20
+ };
10
21
  variant: {
11
22
  primary: {
12
23
  base: string;
@@ -31,6 +42,17 @@ export declare const autocompleteVariants: import("tailwind-variants").TVReturnT
31
42
  trigger: string;
32
43
  };
33
44
  };
45
+ size: {
46
+ lg: {
47
+ base: string;
48
+ popover: string;
49
+ };
50
+ md: {};
51
+ sm: {
52
+ base: string;
53
+ popover: string;
54
+ };
55
+ };
34
56
  variant: {
35
57
  primary: {
36
58
  base: string;
@@ -55,6 +77,17 @@ export declare const autocompleteVariants: import("tailwind-variants").TVReturnT
55
77
  trigger: string;
56
78
  };
57
79
  };
80
+ size: {
81
+ lg: {
82
+ base: string;
83
+ popover: string;
84
+ };
85
+ md: {};
86
+ sm: {
87
+ base: string;
88
+ popover: string;
89
+ };
90
+ };
58
91
  variant: {
59
92
  primary: {
60
93
  base: string;
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.styles.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,oBAAoB;;QAgB3B,KAAK;QACL,IAAI;YACF,IAAI;YACJ,OAAO;;;;QAIT,OAAO;YACL,IAAI;;QAEN,SAAS;YACP,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIV,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"autocomplete.styles.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,oBAAoB;;QAiB3B,KAAK;QACL,IAAI;YACF,IAAI;YACJ,OAAO;;;;QAIT,EAAE;YACA,IAAI;YACJ,OAAO;;QAET,EAAE;QACF,EAAE;YACA,IAAI;YACJ,OAAO;;;;QAIT,OAAO;YACL,IAAI;;QAEN,SAAS;YACP,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIV,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -3,6 +3,7 @@ import { tv } from "tailwind-variants";
3
3
  const autocompleteVariants = tv({
4
4
  defaultVariants: {
5
5
  fullWidth: false,
6
+ size: "md",
6
7
  variant: "primary"
7
8
  },
8
9
  slots: {
@@ -22,6 +23,17 @@ const autocompleteVariants = tv({
22
23
  trigger: "autocomplete__trigger--full-width"
23
24
  }
24
25
  },
26
+ size: {
27
+ lg: {
28
+ base: "autocomplete--lg",
29
+ popover: "autocomplete__popover--lg"
30
+ },
31
+ md: {},
32
+ sm: {
33
+ base: "autocomplete--sm",
34
+ popover: "autocomplete__popover--sm"
35
+ }
36
+ },
25
37
  variant: {
26
38
  primary: { base: "autocomplete--primary" },
27
39
  secondary: { base: "autocomplete--secondary" }
@@ -4,7 +4,15 @@
4
4
  }
5
5
 
6
6
  .autocomplete__trigger {
7
- @apply relative isolate inline-flex min-h-9 rounded-field border bg-field px-3 py-2 text-sm text-field-foreground shadow-field outline-none select-none no-highlight;
7
+ @apply relative isolate inline-flex min-h-9 rounded-field border bg-field px-3 field-line-md text-field-foreground shadow-field outline-none select-none no-highlight;
8
+
9
+ /* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a control
10
+ * with nothing to say it is one. `ButtonBorder` is the keyword the platform uses for a
11
+ * control's edge. */
12
+ @media (forced-colors: active) {
13
+ outline: 1px solid ButtonBorder;
14
+ outline-offset: -1px;
15
+ }
8
16
 
9
17
  /**
10
18
  * Transitions
@@ -101,7 +109,7 @@
101
109
  }
102
110
 
103
111
  .autocomplete__value {
104
- @apply flex-1 text-start text-base wrap-break-word text-current sm:text-sm;
112
+ @apply flex-1 text-start wrap-break-word text-current;
105
113
 
106
114
  &[data-placeholder="true"] {
107
115
  @apply text-field-placeholder;
@@ -131,7 +139,7 @@
131
139
  /* Similar to popover content styles */
132
140
  .autocomplete__popover {
133
141
  @apply flex w-(--trigger-width) max-w-(--trigger-width) origin-(--trigger-anchor-point) scroll-py-1 flex-col overflow-hidden overscroll-contain bg-overlay p-0 pt-2 text-sm outline-none;
134
- border-radius: min(32px, var(--radius-3xl));
142
+ border-radius: min(32px, var(--component-radius));
135
143
  box-shadow: var(--shadow-overlay);
136
144
 
137
145
  &:focus,
@@ -281,3 +289,31 @@
281
289
  transform: scale(0.93);
282
290
  }
283
291
  }
292
+
293
+ /* ==========================================================================
294
+ Size variants
295
+ ========================================================================== */
296
+
297
+ /* The trigger's own metrics, and the option rows in a popover that is portalled out of this tree -
298
+ * which is why the popover carries its own size class rather than being reached from the base. */
299
+ .autocomplete--sm .autocomplete__trigger {
300
+ @apply min-h-8 field-line-sm;
301
+ }
302
+
303
+ .autocomplete--lg .autocomplete__trigger {
304
+ @apply min-h-10 field-line-lg;
305
+ }
306
+
307
+ .autocomplete__popover--sm {
308
+ [data-slot="list-box-item"] {
309
+ @apply min-h-8 py-1;
310
+ }
311
+ }
312
+
313
+ .autocomplete__popover--lg {
314
+ @apply text-base;
315
+
316
+ [data-slot="list-box-item"] {
317
+ @apply min-h-10 py-2;
318
+ }
319
+ }
@@ -1,6 +1,15 @@
1
1
  /* Base avatar styles */
2
2
  .avatar {
3
3
  @apply relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-3xl bg-default;
4
+
5
+ /* Forced Colors Mode flattens the fill onto the page, so a fallback avatar is initials sitting
6
+ * loose on the page with no shape around them. The edge goes here rather than on the fallback:
7
+ * this is the element carrying the radius, and the `overflow-hidden` above would clip a
8
+ * square outline drawn inside it down to four arcs. */
9
+ @media (forced-colors: active) {
10
+ outline: 1px solid CanvasText;
11
+ outline-offset: -1px;
12
+ }
4
13
  }
5
14
 
6
15
  /* Avatar fallback element */
@@ -18,6 +18,13 @@
18
18
  color: var(--badge-fg);
19
19
  border: 1px solid var(--badge-border);
20
20
  background-clip: padding-box;
21
+
22
+ /* That border is the page colour, there to cut the badge out of whatever it overlaps, and
23
+ * forced colors turns it into a visible ring - which is the only reason a badge still reads
24
+ * as one. Stated here so it stays true if `--badge-border` is ever repointed. */
25
+ @media (forced-colors: active) {
26
+ border-color: CanvasText;
27
+ }
21
28
  }
22
29
 
23
30
  .badge__label {
@@ -26,32 +26,32 @@
26
26
 
27
27
  /* Apply border radius to first button (start edge) */
28
28
  .button-group--horizontal .button:first-child {
29
- @apply rounded-s-3xl;
29
+ @apply rounded-s-component;
30
30
  }
31
31
 
32
32
  /* Apply border radius to last button (end edge) */
33
33
  .button-group--horizontal .button:last-child {
34
- @apply rounded-e-3xl;
34
+ @apply rounded-e-component;
35
35
  }
36
36
 
37
37
  /* If only one button, apply both rounded edges */
38
38
  .button-group--horizontal .button:first-child:last-child {
39
- @apply rounded-3xl;
39
+ @apply rounded-component;
40
40
  }
41
41
 
42
42
  /* Apply border radius to first button (top edge) in vertical */
43
43
  .button-group--vertical .button:first-child {
44
- @apply rounded-t-3xl;
44
+ @apply rounded-t-component;
45
45
  }
46
46
 
47
47
  /* Apply border radius to last button (bottom edge) in vertical */
48
48
  .button-group--vertical .button:last-child {
49
- @apply rounded-b-3xl;
49
+ @apply rounded-b-component;
50
50
  }
51
51
 
52
52
  /* If only one button in vertical, apply all rounded edges */
53
53
  .button-group--vertical .button:first-child:last-child {
54
- @apply rounded-3xl;
54
+ @apply rounded-component;
55
55
  }
56
56
 
57
57
  /* Remove scale transform on active/pressed state for buttons in group */
@@ -2,7 +2,7 @@
2
2
 
3
3
  /* Base button styles */
4
4
  .button {
5
- @apply relative isolate inline-flex h-10 w-fit origin-center items-center justify-center gap-2 rounded-3xl px-4 text-sm font-medium whitespace-nowrap outline-none select-none no-highlight md:h-9;
5
+ @apply relative isolate inline-flex h-9 w-fit origin-center items-center justify-center gap-2 rounded-component px-4 text-sm font-medium whitespace-nowrap outline-none select-none no-highlight;
6
6
 
7
7
  /**
8
8
  * Transitions
@@ -26,6 +26,15 @@
26
26
  background-color: var(--button-bg);
27
27
  color: var(--button-fg);
28
28
 
29
+ /* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a control
30
+ * that reads as bare text. `ButtonBorder` is the keyword the platform uses for a control's
31
+ * edge; an outline rather than a border so nothing shifts, and the focus ring replaces it
32
+ * while focused, which is the right order of importance. */
33
+ @media (forced-colors: active) {
34
+ outline: 1px solid ButtonBorder;
35
+ outline-offset: -1px;
36
+ }
37
+
29
38
  /* Focus state */
30
39
  &:focus-visible:not(:focus),
31
40
  &[data-focus-visible="true"] {
@@ -60,17 +69,13 @@
60
69
 
61
70
  /* SVG icon styling - exclude spinner and link-icon svgs */
62
71
  & svg:not([data-slot="spinner"] svg, [data-slot="link-icon"] svg) {
63
- @apply pointer-events-none -mx-0.5 my-0.5 size-5 shrink-0 self-center sm:my-1 sm:size-4;
72
+ @apply pointer-events-none -mx-0.5 my-1 size-4 shrink-0 self-center;
64
73
  }
65
74
  }
66
75
 
67
76
  /* Size variants */
68
77
  .button--sm {
69
- @apply h-9 px-3 md:h-8;
70
-
71
- & svg:not([data-slot="spinner"] svg, [data-slot="link-icon"] svg) {
72
- @apply size-4;
73
- }
78
+ @apply h-8 px-3;
74
79
 
75
80
  &:active,
76
81
  &[data-pressed="true"] {
@@ -83,7 +88,7 @@
83
88
  }
84
89
 
85
90
  .button--lg {
86
- @apply h-11 text-base md:h-10;
91
+ @apply h-10 text-base;
87
92
 
88
93
  &:active,
89
94
  &[data-pressed="true"] {
@@ -97,6 +102,16 @@
97
102
  --button-bg-hover: var(--accent-hover);
98
103
  --button-bg-pressed: var(--accent-hover);
99
104
  --button-fg: var(--accent-foreground);
105
+
106
+ /* Solid against outlined is the one distinction that survives - forced colors has no keyword
107
+ * for "this is the important one", so the emphasis has to come from the fill. `Highlight` is
108
+ * spoken for by selection, which a button is not in. `forced-color-adjust` keeps the backplate
109
+ * off the label. */
110
+ @media (forced-colors: active) {
111
+ forced-color-adjust: none;
112
+ background-color: CanvasText;
113
+ color: Canvas;
114
+ }
100
115
  }
101
116
 
102
117
  .button--secondary {
@@ -130,6 +145,14 @@
130
145
  --button-bg-hover: var(--danger-hover);
131
146
  --button-bg-pressed: var(--danger-hover);
132
147
  --button-fg: var(--danger-foreground);
148
+
149
+ /* Solid like the primary one. The danger colour itself cannot survive - there is no keyword
150
+ * for it - so the label is what says this is the destructive action. */
151
+ @media (forced-colors: active) {
152
+ forced-color-adjust: none;
153
+ background-color: CanvasText;
154
+ color: Canvas;
155
+ }
133
156
  }
134
157
 
135
158
  .button--danger-soft {
@@ -141,15 +164,15 @@
141
164
 
142
165
  /* Icon-only modifier */
143
166
  .button--icon-only {
144
- @apply w-10 p-0 md:w-9;
167
+ @apply w-9 p-0;
145
168
  }
146
169
 
147
170
  .button--icon-only.button--sm {
148
- @apply w-9 md:w-8;
171
+ @apply w-8;
149
172
  }
150
173
 
151
174
  .button--icon-only.button--lg {
152
- @apply w-11 md:w-10;
175
+ @apply w-10;
153
176
  }
154
177
 
155
178
  /* Full width modifier */
@@ -116,7 +116,7 @@
116
116
  * Year Cell
117
117
  * -------------------------------------------------------------------------- */
118
118
  .calendar-year-picker__year-cell {
119
- @apply relative inline-flex h-8 items-center justify-center rounded-3xl px-2.5 text-sm font-medium outline-none select-none no-highlight;
119
+ @apply relative inline-flex h-8 items-center justify-center rounded-component px-2.5 text-sm font-medium outline-none select-none no-highlight;
120
120
  touch-action: manipulation;
121
121
 
122
122
  /**
@@ -144,6 +144,7 @@
144
144
  &[data-selected="true"],
145
145
  &[aria-selected="true"] {
146
146
  @apply bg-accent text-accent-foreground;
147
+ @apply forced-selected;
147
148
 
148
149
  @media (hover: hover) and (pointer: fine) {
149
150
  &:is(:hover, [data-hovered="true"]) {
@@ -207,6 +207,13 @@
207
207
  &[data-today="true"] {
208
208
  @apply bg-accent-soft text-accent-soft-foreground;
209
209
 
210
+ /* A tinted background is all that marks today, and forced colors flattens it onto the page.
211
+ * An edge rather than a fill, so today still reads as itself once it is also selected. */
212
+ @media (forced-colors: active) {
213
+ outline: 1px solid CanvasText;
214
+ outline-offset: -1px;
215
+ }
216
+
210
217
  @media (hover: hover) {
211
218
  &:hover:not([data-selected="true"]),
212
219
  &[data-hovered="true"]:not([data-selected="true"]) {
@@ -218,6 +225,7 @@
218
225
  /* Selected state */
219
226
  &[data-selected="true"] {
220
227
  @apply bg-accent text-accent-foreground;
228
+ @apply forced-selected;
221
229
  }
222
230
 
223
231
  /* Pressed state */