@ropav/styles 0.3.0 → 0.4.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.
- package/README.md +155 -19
- package/dist/components/accordion.css +7 -0
- package/dist/components/alert.css +8 -0
- package/dist/components/autocomplete.css +8 -0
- package/dist/components/avatar.css +9 -0
- package/dist/components/badge.css +7 -0
- package/dist/components/button.css +27 -0
- package/dist/components/calendar-year-picker.css +1 -0
- package/dist/components/calendar.css +8 -0
- package/dist/components/card.css +21 -0
- package/dist/components/chip.css +21 -0
- package/dist/components/close-button.css +8 -0
- package/dist/components/color-input-group.css +9 -0
- package/dist/components/color-swatch-picker.css +11 -0
- package/dist/components/date-input-group.css +9 -0
- package/dist/components/dropdown/dropdown.styles.d.ts.map +1 -1
- package/dist/components/dropdown/dropdown.styles.js +1 -1
- package/dist/components/dropdown.css +16 -11
- package/dist/components/input-group.css +9 -0
- package/dist/components/input.css +10 -0
- package/dist/components/menu.css +6 -0
- package/dist/components/meter.css +12 -0
- package/dist/components/number-field.css +8 -0
- package/dist/components/pagination.css +4 -0
- package/dist/components/progress-bar.css +12 -0
- package/dist/components/progress-circle.css +12 -0
- package/dist/components/radio.css +7 -0
- package/dist/components/range-calendar.css +28 -0
- package/dist/components/search-field.css +8 -0
- package/dist/components/select.css +9 -0
- package/dist/components/separator.css +18 -1
- package/dist/components/skeleton.css +9 -0
- package/dist/components/slider.css +61 -0
- package/dist/components/surface.css +24 -0
- package/dist/components/switch.css +30 -0
- package/dist/components/table.css +8 -0
- package/dist/components/tabs.css +28 -0
- package/dist/components/tag.css +10 -0
- package/dist/components/textarea.css +10 -0
- package/dist/components/toggle-button.css +10 -0
- package/dist/components/toolbar.css +7 -0
- package/dist/components/typography.css +21 -0
- package/dist/index.css +7 -2
- package/dist/ropav.min.css +1 -1
- package/dist/themes/airbnb.css +226 -0
- package/dist/themes/all.css +16 -0
- package/dist/themes/coinbase.css +226 -0
- package/dist/themes/{default/variables.css → default.css} +12 -5
- package/dist/themes/discord.css +226 -0
- package/dist/themes/lavender.css +226 -0
- package/dist/themes/mint.css +226 -0
- package/dist/themes/netflix.css +226 -0
- package/dist/themes/rabbit.css +226 -0
- package/dist/themes/sky.css +226 -0
- package/dist/themes/spotify.css +226 -0
- package/dist/themes/uber.css +226 -0
- package/dist/utilities/index.css +70 -0
- package/dist/variants/index.css +5 -15
- package/package.json +2 -3
- package/src/components/dropdown/dropdown.styles.ts +3 -1
- package/dist/themes/default/components/index.css +0 -6
- 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:
|
|
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/
|
|
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/*`, `./
|
|
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
|
-
│
|
|
79
|
-
│
|
|
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,166 @@ component that publishes it as an attribute.
|
|
|
113
114
|
|
|
114
115
|
## Theming
|
|
115
116
|
|
|
116
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
+
Eleven, ported from HeroUI's theme gallery. The default one is already in `@ropav/styles`; the rest are
|
|
130
|
+
opt-in, one file each:
|
|
131
|
+
|
|
132
|
+
```css
|
|
133
|
+
@import "@ropav/styles";
|
|
134
|
+
@import "@ropav/styles/themes/netflix";
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| `data-theme` | | `data-theme` | | `data-theme` |
|
|
138
|
+
| --- | --- | --- | --- | --- |
|
|
139
|
+
| `default` | | `netflix` | | `airbnb` |
|
|
140
|
+
| `sky` | | `uber` | | `discord` |
|
|
141
|
+
| `lavender` | | `spotify` | | `rabbit` |
|
|
142
|
+
| `mint` | | `coinbase` | | |
|
|
143
|
+
|
|
144
|
+
Each carries both a light and a dark palette, so a theme is a brand rather than an appearance. There is
|
|
145
|
+
no `netflix-dark` — it is `data-theme="netflix"` plus `class="dark"`.
|
|
146
|
+
|
|
147
|
+
`themes/all.css` pulls in all of them at once. That is for docs and playgrounds; an app should import
|
|
148
|
+
only the themes it actually offers, since each is around 10 kB before compression.
|
|
149
|
+
|
|
150
|
+
The ten non-default themes are **generated** — edit `scripts/themes/presets.ts` and run
|
|
151
|
+
`pnpm generate:themes`, never the CSS. A preset is four numbers (accent lightness, chroma and hue, plus
|
|
152
|
+
the chroma of the neutral ramp), a radius pair, and any exact brand colours that should beat the
|
|
153
|
+
calculated ones.
|
|
154
|
+
|
|
155
|
+
### Retheming
|
|
156
|
+
|
|
157
|
+
Every token is a CSS custom property, so overriding them in your own stylesheet is enough — no rebuild
|
|
158
|
+
of this package, and no need to go through a theme file at all:
|
|
124
159
|
|
|
125
160
|
```css
|
|
126
161
|
:root {
|
|
127
162
|
--accent: oklch(0.62 0.19 253.83);
|
|
128
163
|
--radius: 0.5rem;
|
|
129
|
-
--field-radius: calc(var(--radius) * 1.5);
|
|
130
164
|
}
|
|
131
165
|
```
|
|
132
166
|
|
|
133
|
-
|
|
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.
|
|
167
|
+
Author CSS outside a cascade layer outranks everything here, so these win wherever you put them.
|
|
137
168
|
|
|
138
|
-
|
|
169
|
+
To add a theme rather than change the default one, write the same token block under your own attribute.
|
|
170
|
+
It has to redeclare the *derived* tokens too, not just the authored ones: a custom property substitutes
|
|
171
|
+
`var()` where it is declared, so an `--accent-hover` inherited from `:root` would still be mixed from
|
|
172
|
+
the root's `--accent`. Copy a generated theme and edit it, or generate one.
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
@layer theme {
|
|
176
|
+
[data-theme="ocean"] {
|
|
177
|
+
color-scheme: light;
|
|
178
|
+
--accent: oklch(0.62 0.14 220);
|
|
179
|
+
--accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%);
|
|
180
|
+
/* … */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A theme only needs to carry colours. Everything keyed on neither the palette nor the appearance —
|
|
186
|
+
`--spacing`, `--cursor-*`, the primitives, the shadows, `--backdrop` — stays on `:root` and `.dark` in
|
|
187
|
+
`themes/default.css`, both of which keep matching an element that carries a `data-theme`.
|
|
188
|
+
|
|
189
|
+
### Tokens
|
|
190
|
+
|
|
191
|
+
Base colors (`--background`, `--surface`, `--overlay`, `--muted`), interactive and status colors
|
|
192
|
+
(`--accent`, `--success`, `--warning`, `--danger`, each with a `-foreground` and a derived `-hover` and
|
|
193
|
+
`-soft`), form fields (`--field-background`, `--field-border`, `--field-radius`, …), layout knobs
|
|
194
|
+
(`--spacing`, `--border-width`, `--radius`, `--ring-offset-width`, `--cursor-interactive`), the
|
|
195
|
+
scrollbar set, and shadows.
|
|
196
|
+
|
|
197
|
+
**`themes/default.css` is the source of truth — read it rather than a list in a README**, which goes
|
|
139
198
|
stale the moment a token moves. `themes/shared/theme.css` holds what is derived from those tokens: the
|
|
140
|
-
`--radius-xs` … `--radius-4xl` scale
|
|
199
|
+
`--radius-xs` … `--radius-4xl` scale and the easing curves.
|
|
200
|
+
|
|
201
|
+
### Reduced motion
|
|
202
|
+
|
|
203
|
+
`data-reduce-motion="true"` on any ancestor forces animations off, `"false"` forces them on regardless
|
|
204
|
+
of the OS setting, and with neither the `prefers-reduced-motion` media query decides. Defined as the
|
|
205
|
+
`motion-reduce` / `motion-safe` variants in `variants/index.css`.
|
|
206
|
+
|
|
207
|
+
### Forced colors
|
|
208
|
+
|
|
209
|
+
Forced Colors Mode - Windows High Contrast - replaces author colours with the user's own palette and
|
|
210
|
+
strips `box-shadow` outright. That second part is what makes it more than a colour question here:
|
|
211
|
+
every ring in this library *is* a `box-shadow`, because that is what `ring-*` compiles to, and it sits
|
|
212
|
+
on top of `outline-none`. Left alone, a focused control would have no indicator at all.
|
|
213
|
+
|
|
214
|
+
Nothing to opt into. The three focus utilities - `focus-ring`, `focus-field-ring`,
|
|
215
|
+
`invalid-field-ring` - draw an outline back in `Highlight` under `forced-colors: active`, so any
|
|
216
|
+
component that goes through `status-focused`, `status-focused-field` or `status-invalid-field` is
|
|
217
|
+
covered without a line of its own. `status-disabled` picks up `GrayText` the same way.
|
|
218
|
+
|
|
219
|
+
The other half is state carried only by `background-color`, which the override flattens into its
|
|
220
|
+
surroundings. Where selection is *just* a background - a tag, a calendar day, a table row - apply
|
|
221
|
+
`forced-selected`, **as an `@apply` statement of its own**:
|
|
222
|
+
|
|
223
|
+
```css
|
|
224
|
+
.thing[data-selected="true"] {
|
|
225
|
+
@apply bg-accent text-accent-foreground;
|
|
226
|
+
@apply forced-selected;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Folded into the line above it, Tailwind sorts the list and hoists the nested media query over the
|
|
231
|
+
plain declarations, and the `background-color` it exists to override wins instead. Where selection
|
|
232
|
+
also moves a thumb or shows a glyph, the component writes its own `forced-colors` block, because
|
|
233
|
+
those parts need colours of their own - see `switch.css`, `radio.css`, `tabs.css`, `range-calendar.css`,
|
|
234
|
+
`slider.css` and `skeleton.css`.
|
|
235
|
+
|
|
236
|
+
Use the system colour keywords, not tokens: `Highlight` / `HighlightText` for a selected control,
|
|
237
|
+
`CanvasText` on `Canvas` for ordinary content, `ButtonBorder` for a control's edge, `GrayText` for
|
|
238
|
+
disabled. They are the only colours exempt from the override.
|
|
239
|
+
|
|
240
|
+
Watch for `transparent` used as a spacer. Forced colors preserves a transparent *background*, but a
|
|
241
|
+
transparent `border-color` is turned **opaque** - so a border held open purely to reserve layout space
|
|
242
|
+
(`slider.css` does this at both ends of the track) stops being invisible and renders as a solid block.
|
|
243
|
+
Restate those as `Canvas` inside the media query.
|
|
244
|
+
|
|
245
|
+
One trap, and `getComputedStyle` cannot see it. Chromium paints a `Canvas`-coloured **backplate**
|
|
246
|
+
behind the text of any element that has text, so that text over an image stays legible. It lands on
|
|
247
|
+
top of that element's own background, so a `Highlight` fill carrying `HighlightText` renders as a
|
|
248
|
+
solid plate with the label invisible inside it - the colours are all correct and the component is
|
|
249
|
+
unreadable. `forced-color-adjust: none` is what suppresses the backplate. `forced-selected` already
|
|
250
|
+
carries it; anything hand-rolling the same pairing needs it too, including the case where the fill
|
|
251
|
+
sits on a different element than the text (`tabs.css`, `range-calendar.css`). Reach for it only
|
|
252
|
+
alongside explicit system colours - on its own it just hands the author's palette back, which is the
|
|
253
|
+
opposite of the point.
|
|
254
|
+
|
|
255
|
+
Structure has to survive the mode as well as state. A component that is only a tinted fill and a
|
|
256
|
+
shadow - a card, an alert, a text field, a chip - renders as loose text once both are taken, which
|
|
257
|
+
reads as no component at all. So every container carries an inset `CanvasText` outline under the
|
|
258
|
+
mode and every control a `ButtonBorder` one, on the variants that actually paint; a transparent
|
|
259
|
+
surface is left alone, since framing it would invent a box that was never there. A separator is the
|
|
260
|
+
exception that proves it: the whole thing *is* its background, so it takes a colour rather than an
|
|
261
|
+
edge.
|
|
262
|
+
|
|
263
|
+
Two things guard it. `forced-colors.browser.test.ts` in the `ropav` package asserts the stylesheet
|
|
264
|
+
directly, and the Storybook package runs **every story** through an audit that renders it twice - the
|
|
265
|
+
mode off, then on over CDP - and fails on anything that painted something and stops painting it. The
|
|
266
|
+
second one exists because the first cannot see this class of bug on its own: the colour override runs
|
|
267
|
+
after the cascade and the backplate is painted later still, so a component reports every declared
|
|
268
|
+
colour correctly while rendering as a blank block. Three bugs shipped that way before the audit
|
|
269
|
+
existed.
|
|
270
|
+
|
|
271
|
+
To look at it by hand, use Chromium's rendering panel - DevTools, `Cmd+Shift+P`, "Show Rendering",
|
|
272
|
+
then *Emulate CSS media feature forced-colors* - and note that **macOS has no Forced Colors Mode at
|
|
273
|
+
all**: "Increase contrast" maps to `prefers-contrast: more`, so switching it on tests nothing. There
|
|
274
|
+
is deliberately no toolbar control in Storybook. `data-reduce-motion` is an attribute a decorator can
|
|
275
|
+
set, but this is a browser-level media feature an iframe cannot turn on for itself, and faking it with
|
|
276
|
+
a stylesheet of system colours would miss the `box-shadow` removal - the part that actually breaks.
|
|
141
277
|
|
|
142
278
|
## Build
|
|
143
279
|
|
|
@@ -118,6 +118,13 @@
|
|
|
118
118
|
@apply bg-surface;
|
|
119
119
|
border-radius: min(32px, var(--radius-3xl));
|
|
120
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
|
+
}
|
|
127
|
+
|
|
121
128
|
.accordion__trigger {
|
|
122
129
|
@media (hover: hover) {
|
|
123
130
|
&:hover:not([aria-expanded="true"]),
|
|
@@ -3,6 +3,14 @@
|
|
|
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
|
+
|
|
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
|
+
}
|
|
6
14
|
border-radius: min(32px, var(--radius-3xl));
|
|
7
15
|
}
|
|
8
16
|
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
.autocomplete__trigger {
|
|
7
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;
|
|
8
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
|
+
}
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* Transitions
|
|
11
19
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
@@ -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,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"] {
|
|
@@ -97,6 +106,16 @@
|
|
|
97
106
|
--button-bg-hover: var(--accent-hover);
|
|
98
107
|
--button-bg-pressed: var(--accent-hover);
|
|
99
108
|
--button-fg: var(--accent-foreground);
|
|
109
|
+
|
|
110
|
+
/* Solid against outlined is the one distinction that survives - forced colors has no keyword
|
|
111
|
+
* for "this is the important one", so the emphasis has to come from the fill. `Highlight` is
|
|
112
|
+
* spoken for by selection, which a button is not in. `forced-color-adjust` keeps the backplate
|
|
113
|
+
* off the label. */
|
|
114
|
+
@media (forced-colors: active) {
|
|
115
|
+
forced-color-adjust: none;
|
|
116
|
+
background-color: CanvasText;
|
|
117
|
+
color: Canvas;
|
|
118
|
+
}
|
|
100
119
|
}
|
|
101
120
|
|
|
102
121
|
.button--secondary {
|
|
@@ -130,6 +149,14 @@
|
|
|
130
149
|
--button-bg-hover: var(--danger-hover);
|
|
131
150
|
--button-bg-pressed: var(--danger-hover);
|
|
132
151
|
--button-fg: var(--danger-foreground);
|
|
152
|
+
|
|
153
|
+
/* Solid like the primary one. The danger colour itself cannot survive - there is no keyword
|
|
154
|
+
* for it - so the label is what says this is the destructive action. */
|
|
155
|
+
@media (forced-colors: active) {
|
|
156
|
+
forced-color-adjust: none;
|
|
157
|
+
background-color: CanvasText;
|
|
158
|
+
color: Canvas;
|
|
159
|
+
}
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
.button--danger-soft {
|
|
@@ -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 */
|
package/dist/components/card.css
CHANGED
|
@@ -40,12 +40,33 @@
|
|
|
40
40
|
|
|
41
41
|
.card--default {
|
|
42
42
|
@apply bg-surface;
|
|
43
|
+
|
|
44
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, so the container
|
|
45
|
+
* stops having any shape and its contents sit loose on the page. */
|
|
46
|
+
@media (forced-colors: active) {
|
|
47
|
+
outline: 1px solid CanvasText;
|
|
48
|
+
outline-offset: -1px;
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
.card--secondary {
|
|
46
53
|
@apply bg-surface-secondary;
|
|
54
|
+
|
|
55
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, so the container
|
|
56
|
+
* stops having any shape and its contents sit loose on the page. */
|
|
57
|
+
@media (forced-colors: active) {
|
|
58
|
+
outline: 1px solid CanvasText;
|
|
59
|
+
outline-offset: -1px;
|
|
60
|
+
}
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
.card--tertiary {
|
|
50
64
|
@apply bg-surface-tertiary;
|
|
65
|
+
|
|
66
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, so the container
|
|
67
|
+
* stops having any shape and its contents sit loose on the page. */
|
|
68
|
+
@media (forced-colors: active) {
|
|
69
|
+
outline: 1px solid CanvasText;
|
|
70
|
+
outline-offset: -1px;
|
|
71
|
+
}
|
|
51
72
|
}
|
package/dist/components/chip.css
CHANGED
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
background-color: var(--chip-bg);
|
|
10
10
|
color: var(--chip-fg);
|
|
11
|
+
|
|
12
|
+
/* A chip is nothing but its colour, so Forced Colors Mode leaves it as bare label text -
|
|
13
|
+
* not a weaker chip, no chip at all, indistinguishable from the copy around it. The edge
|
|
14
|
+
* brings the shape back.
|
|
15
|
+
*
|
|
16
|
+
* What cannot come back is the status. The mode has no keyword for success or warning, so
|
|
17
|
+
* every colour collapses onto one appearance and the label is what has to carry the
|
|
18
|
+
* meaning - which is the same reason a chip should not lean on colour alone in the first
|
|
19
|
+
* place. Only the emphasis split survives, on `.chip--primary` below. */
|
|
20
|
+
@media (forced-colors: active) {
|
|
21
|
+
outline: 1px solid CanvasText;
|
|
22
|
+
outline-offset: -1px;
|
|
23
|
+
}
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
.chip__label {
|
|
@@ -37,6 +50,14 @@
|
|
|
37
50
|
|
|
38
51
|
/* Variant styles */
|
|
39
52
|
.chip--primary {
|
|
53
|
+
/* The one distinction worth keeping: solid against outlined. `forced-color-adjust` is what
|
|
54
|
+
* makes it readable - without it Chromium paints a `Canvas` backplate over this fill and the
|
|
55
|
+
* label goes with it. */
|
|
56
|
+
@media (forced-colors: active) {
|
|
57
|
+
forced-color-adjust: none;
|
|
58
|
+
background-color: CanvasText;
|
|
59
|
+
color: Canvas;
|
|
60
|
+
}
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
.chip--secondary {
|
|
@@ -47,6 +47,14 @@
|
|
|
47
47
|
.close-button--default {
|
|
48
48
|
@apply bg-default text-muted;
|
|
49
49
|
|
|
50
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a control
|
|
51
|
+
* with nothing to say it is one. `ButtonBorder` is the keyword the platform uses for a
|
|
52
|
+
* control's edge. */
|
|
53
|
+
@media (forced-colors: active) {
|
|
54
|
+
outline: 1px solid ButtonBorder;
|
|
55
|
+
outline-offset: -1px;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
&:active,
|
|
51
59
|
&[data-pressed="true"] {
|
|
52
60
|
}
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
border-width: var(--border-width-field);
|
|
9
9
|
border-color: var(--field-border);
|
|
10
10
|
|
|
11
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a field that
|
|
12
|
+
* reads as bare text - `--border-width-field` is 0, so the declared border brings nothing back on
|
|
13
|
+
* its own. An outline rather than a width so nothing shifts, and the focus ring replaces it while
|
|
14
|
+
* focused, which is the right order of importance. */
|
|
15
|
+
@media (forced-colors: active) {
|
|
16
|
+
outline: 1px solid ButtonBorder;
|
|
17
|
+
outline-offset: -1px;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
/**
|
|
12
21
|
* Transitions
|
|
13
22
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
.color-swatch-picker__item {
|
|
15
15
|
@apply relative flex size-8 items-center justify-center rounded-2xl border-2 border-transparent outline-none no-highlight;
|
|
16
16
|
|
|
17
|
+
/* The unselected border is transparent purely to hold the ring's width, and forced colors turns
|
|
18
|
+
* a transparent border-color opaque - so every swatch drew the same border and the selected one
|
|
19
|
+
* stopped being the selected one. */
|
|
20
|
+
@media (forced-colors: active) {
|
|
21
|
+
border-color: Canvas;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/* Cursor */
|
|
18
25
|
cursor: var(--cursor-interactive);
|
|
19
26
|
|
|
@@ -41,6 +48,10 @@
|
|
|
41
48
|
&[data-selected="true"] {
|
|
42
49
|
border-color: var(--color-swatch-current);
|
|
43
50
|
box-shadow: var(--field-shadow);
|
|
51
|
+
|
|
52
|
+
@media (forced-colors: active) {
|
|
53
|
+
border-color: Highlight;
|
|
54
|
+
}
|
|
44
55
|
/* Shrink the swatch to reveal the white gap */
|
|
45
56
|
.color-swatch-picker__swatch {
|
|
46
57
|
transform: scale(0.77);
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
border-width: var(--border-width-field);
|
|
5
5
|
border-color: var(--field-border);
|
|
6
6
|
|
|
7
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a field that
|
|
8
|
+
* reads as bare text - `--border-width-field` is 0, so the declared border brings nothing back on
|
|
9
|
+
* its own. An outline rather than a width so nothing shifts, and the focus ring replaces it while
|
|
10
|
+
* focused, which is the right order of importance. */
|
|
11
|
+
@media (forced-colors: active) {
|
|
12
|
+
outline: 1px solid ButtonBorder;
|
|
13
|
+
outline-offset: -1px;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
17
|
* Transitions
|
|
9
18
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown.styles.d.ts","sourceRoot":"","sources":["../../../src/components/dropdown/dropdown.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"dropdown.styles.d.ts","sourceRoot":"","sources":["../../../src/components/dropdown/dropdown.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAS3B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tv } from "tailwind-variants";
|
|
2
2
|
//#region src/components/dropdown/dropdown.styles.ts
|
|
3
3
|
const dropdownVariants = tv({ slots: {
|
|
4
|
-
menu: "dropdown__menu",
|
|
4
|
+
menu: "menu dropdown__menu",
|
|
5
5
|
popover: "dropdown__popover",
|
|
6
6
|
root: "dropdown",
|
|
7
7
|
trigger: "dropdown__trigger"
|
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
@apply inline-block;
|
|
11
11
|
/* Only visible when using a custom trigger - not passing "Button" as a child - hence we use some button styles (custom trigger) */
|
|
12
12
|
@apply outline-none;
|
|
13
|
+
|
|
14
|
+
/* A custom trigger borrows the button's look by wearing its classes, and the `outline-none`
|
|
15
|
+
* above lands later in the cascade than the button's own forced-colors edge, stripping it back
|
|
16
|
+
* off. Handed back here rather than on the bare trigger: when a `Button` is passed as a child
|
|
17
|
+
* instead, this element is only a wrapper around it and an outline would draw a second ring
|
|
18
|
+
* around the real one. */
|
|
19
|
+
&.button {
|
|
20
|
+
@media (forced-colors: active) {
|
|
21
|
+
outline: 1px solid ButtonBorder;
|
|
22
|
+
outline-offset: -1px;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
13
25
|
/**
|
|
14
26
|
* Transitions
|
|
15
27
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
|
@@ -109,21 +121,14 @@
|
|
|
109
121
|
rotate: 90deg;
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
/* Menu styles
|
|
124
|
+
/* Menu styles. The block itself is `.menu`; these are the dropdown's departures from it, and
|
|
125
|
+
* they sit here rather than on `.dropdown__menu` because `menu.css` is imported after this file
|
|
126
|
+
* and would otherwise win at equal specificity. */
|
|
113
127
|
[data-slot="dropdown-menu"] {
|
|
114
|
-
@apply p-1.5 outline-none;
|
|
128
|
+
@apply gap-0.5 p-1.5 outline-none;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
[data-slot="menu-item"] {
|
|
118
132
|
@apply px-2.5;
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
|
-
|
|
122
|
-
.dropdown__menu {
|
|
123
|
-
@apply relative flex w-full flex-col gap-0.5 overflow-clip p-1;
|
|
124
|
-
|
|
125
|
-
/* This makes the separator take up 94% of the width and be centered */
|
|
126
|
-
[data-slot="separator"] {
|
|
127
|
-
@apply ms-[3%] w-[94%];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
border-width: var(--border-width-field);
|
|
5
5
|
border-color: var(--field-border);
|
|
6
6
|
|
|
7
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a field that
|
|
8
|
+
* reads as bare text - `--border-width-field` is 0, so the declared border brings nothing back on
|
|
9
|
+
* its own. An outline rather than a width so nothing shifts, and the focus ring replaces it while
|
|
10
|
+
* focused, which is the right order of importance. */
|
|
11
|
+
@media (forced-colors: active) {
|
|
12
|
+
outline: 1px solid ButtonBorder;
|
|
13
|
+
outline-offset: -1px;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
/* When textarea is present, align items to start and allow height to grow */
|
|
8
17
|
&:has([data-slot="input-group-textarea"]) {
|
|
9
18
|
@apply items-start;
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
border-width: var(--border-width-field);
|
|
5
5
|
border-color: var(--field-border);
|
|
6
6
|
|
|
7
|
+
/* Forced Colors Mode flattens the fill onto the page and strips the shadow, leaving a field that
|
|
8
|
+
* reads as bare text - `--border-width-field` is 0, so the declared border brings nothing
|
|
9
|
+
* back on its own. `ButtonBorder` is the keyword the platform uses for a control's
|
|
10
|
+
* edge; an outline rather than a border so nothing shifts, and the focus ring replaces it
|
|
11
|
+
* while focused, which is the right order of importance. */
|
|
12
|
+
@media (forced-colors: active) {
|
|
13
|
+
outline: 1px solid ButtonBorder;
|
|
14
|
+
outline-offset: -1px;
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
/**
|
|
8
18
|
* Transitions
|
|
9
19
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
package/dist/components/menu.css
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
Base menu styles
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Worn by both menus — the standalone one and the one inside a dropdown, which carries
|
|
7
|
+
* `dropdown__menu` alongside it. The two used to define the same rules twice, differing only in
|
|
8
|
+
* `gap`; that difference now lives with the dropdown's other overrides in `dropdown.css`, nested
|
|
9
|
+
* where it has the specificity to win, since this file is imported after that one.
|
|
10
|
+
*/
|
|
5
11
|
.menu {
|
|
6
12
|
@apply relative flex w-full flex-col gap-1 overflow-clip p-1;
|
|
7
13
|
|
|
@@ -32,6 +32,14 @@
|
|
|
32
32
|
|
|
33
33
|
/* Default height (matches --md) */
|
|
34
34
|
@apply h-2;
|
|
35
|
+
|
|
36
|
+
/* Track and fill are both plain backgrounds, so forced colors flattens the pair onto the
|
|
37
|
+
* page and the whole meter stops rendering - no bar, no value. */
|
|
38
|
+
@media (forced-colors: active) {
|
|
39
|
+
background-color: Canvas;
|
|
40
|
+
outline: 1px solid CanvasText;
|
|
41
|
+
outline-offset: -1px;
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
.meter__fill {
|
|
@@ -39,6 +47,10 @@
|
|
|
39
47
|
|
|
40
48
|
background-color: var(--meter-fill);
|
|
41
49
|
|
|
50
|
+
@media (forced-colors: active) {
|
|
51
|
+
background-color: Highlight;
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
/**
|
|
43
55
|
* Transitions
|
|
44
56
|
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|