@redseed/redseed-ui-tailwindcss 7.63.0 → 7.65.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/components/card.css +76 -0
- package/components/metric_card.css +213 -2
- package/package.json +1 -1
package/components/card.css
CHANGED
|
@@ -153,6 +153,82 @@
|
|
|
153
153
|
@apply absolute inset-0 size-full pointer-events-none rounded-xl border-border-secondary;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/* ── Category accent ──────────────────────────────────────────────────────────
|
|
157
|
+
A colour-coded edge for TAXONOMY, which the semantic variants above cannot
|
|
158
|
+
express: they say what state a card is in, not which category it belongs to.
|
|
159
|
+
|
|
160
|
+
Two deliberate choices:
|
|
161
|
+
|
|
162
|
+
1. It is a BORDER, not a pseudo-element. The card's ::before is already the
|
|
163
|
+
hover scrim and its ::after is free, but a border is what actually delivers
|
|
164
|
+
the behaviour the API promises — "the accent replaces that edge's border
|
|
165
|
+
rather than sitting alongside it". A border on that edge IS that edge's
|
|
166
|
+
border, so there is no double rule to suppress, it composes with `bordered`
|
|
167
|
+
for free (unbordered cards have 0-width edges, so only the accent paints),
|
|
168
|
+
and the content inset comes along with it instead of needing separate
|
|
169
|
+
padding. It also stays clear of the hover scrim and the action layer, both
|
|
170
|
+
of which are `absolute inset-0` and therefore resolve against the PADDING
|
|
171
|
+
box — so neither tints nor covers the accent.
|
|
172
|
+
|
|
173
|
+
2. Thickness is a token. On the ARC insights page the same "this card belongs
|
|
174
|
+
to domain X" idea was hand-rolled twice in one sitting at 4px and 3px,
|
|
175
|
+
because nothing told the author what the weight should be. 4px — the
|
|
176
|
+
heavier of the two — so the bar still reads as a category marker against
|
|
177
|
+
the 1px hairline on the card's other three edges.
|
|
178
|
+
|
|
179
|
+
These rules are (0,2,0) and sit AFTER the variant border rules on purpose.
|
|
180
|
+
`.rsui-card--bordered.rsui-card--draft` zeroes every border width and
|
|
181
|
+
`.rsui-card--bordered.rsui-card--ai-bold` makes every border colour
|
|
182
|
+
transparent, both at the same specificity; landing later lets the accent edge
|
|
183
|
+
survive on those two variants rather than being silently swallowed.
|
|
184
|
+
|
|
185
|
+
`left` maps to the INLINE-START edge, not the physical left, matching the
|
|
186
|
+
logical `padding-inline` this stylesheet already uses. The accent marks the
|
|
187
|
+
leading edge of the card, so it should follow writing direction. */
|
|
188
|
+
.rsui-card {
|
|
189
|
+
/* Neutral when the consumer supplies no `color`, so an accent edge is never
|
|
190
|
+
invisible. Same fallback token as Pill's resting accent. */
|
|
191
|
+
--rsui-card-accent: var(--color-background-secondary-contrast);
|
|
192
|
+
|
|
193
|
+
--rsui-card-accent-w: 0.25rem; /* space-xxs, 4px */
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.rsui-card.rsui-card--accent-left {
|
|
197
|
+
border-inline-start-style: solid;
|
|
198
|
+
border-inline-start-width: var(--rsui-card-accent-w);
|
|
199
|
+
border-inline-start-color: var(--rsui-card-accent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.rsui-card.rsui-card--accent-top {
|
|
203
|
+
border-block-start-style: solid;
|
|
204
|
+
border-block-start-width: var(--rsui-card-accent-w);
|
|
205
|
+
border-block-start-color: var(--rsui-card-accent);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Hold the accent clear of the action layer's ring.
|
|
209
|
+
|
|
210
|
+
The hover scrim and the action layer are both `absolute inset-0`, so they stop
|
|
211
|
+
at the padding box and cannot reach the border — but a RING is not bounded by
|
|
212
|
+
its element. `ring-2 ring-offset-2` paints `0 0 0 2px` of offset colour and
|
|
213
|
+
then `0 0 0 4px` of ring colour OUTWARD from the layer's border box, and a
|
|
214
|
+
child's box-shadow paints above its parent's border. Measured against this
|
|
215
|
+
accent that is a 4px band landing exactly on a 4px edge, so a `pressed` or
|
|
216
|
+
`current` card — or merely a focused one, since focus-visible uses the same
|
|
217
|
+
geometry — lost its category colour entirely at the moment it mattered most.
|
|
218
|
+
|
|
219
|
+
Pulling the layer in by the accent width on that edge puts the ring's outer
|
|
220
|
+
limit at the padding box, where the accent begins. Both then render: ring
|
|
221
|
+
around the content, accent around the card. Only the accented edge moves, so
|
|
222
|
+
every other card keeps the ring exactly where it was, and the 4px of click
|
|
223
|
+
area given up sits under a decorative bar. */
|
|
224
|
+
.rsui-card--accent-left .rsui-card__action-layer {
|
|
225
|
+
inset-inline-start: var(--rsui-card-accent-w);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.rsui-card--accent-top .rsui-card__action-layer {
|
|
229
|
+
inset-block-start: var(--rsui-card-accent-w);
|
|
230
|
+
}
|
|
231
|
+
|
|
156
232
|
/* Prevent table row hover background from bleeding past card rounded corners.
|
|
157
233
|
Opt out while a dropdown menu is open inside the card — otherwise the menu
|
|
158
234
|
gets clipped by the card's overflow boundary. */
|
|
@@ -21,8 +21,17 @@
|
|
|
21
21
|
@apply shrink-0 grow-0 size-10;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/* The flexing text column, not the label itself — the label now shares it with
|
|
25
|
+
the optional microcopy, and the two stack. Kept on the wrapper so a tile with
|
|
26
|
+
no description renders exactly as it did before.
|
|
27
|
+
|
|
28
|
+
8px, not the 4px TIGHT PAIR value, even though label -> description is the same
|
|
29
|
+
shape as the three pairs that do take it. CardSpacing.test.js caps the family at
|
|
30
|
+
exactly three justified exceptions and argues that a fourth means the brand scale
|
|
31
|
+
is missing a step rather than the family needing another escape hatch (#317).
|
|
32
|
+
That argument belongs on #317, not smuggled in here — so this sits on the rhythm. */
|
|
33
|
+
.rsui-metric-card__heading-text {
|
|
34
|
+
@apply flex-1 min-w-0 flex flex-col gap-space-xs;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
.rsui-metric-card__number {
|
|
@@ -39,6 +48,167 @@
|
|
|
39
48
|
@apply text-text-brand;
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/* Microcopy. Explains what the metric measures, so a number does not need a
|
|
52
|
+
support doc beside it. In the flow rather than behind a Tooltip on an info
|
|
53
|
+
icon: hover hides the explanation from touch and from keyboard, and this is
|
|
54
|
+
the sentence that makes the tile legible at all.
|
|
55
|
+
|
|
56
|
+
The cost is vertical rhythm — in a CardGroup row of tiles, uneven copy lengths
|
|
57
|
+
make tiles ragged unless the group equalises height. Muted and a step smaller
|
|
58
|
+
than the label, wrapping freely with no truncation, because a clipped
|
|
59
|
+
explanation is worse than a tall tile. */
|
|
60
|
+
.rsui-metric-card__description {
|
|
61
|
+
@apply text-text-tertiary text-sm font-normal;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── The figure block ─────────────────────────────────────────────────────────
|
|
65
|
+
The number and the comparison that qualifies it, as one box. It exists so the
|
|
66
|
+
two placements are a single property on one wrapper rather than two template
|
|
67
|
+
branches: everything else about the comparison — the a11y wiring, the
|
|
68
|
+
sentiment modifiers, the tonal overrides — stays placement-agnostic.
|
|
69
|
+
|
|
70
|
+
The 8px gap is not arbitrary: before this wrapper existed the two sat directly
|
|
71
|
+
in MetricCard's FlexContainer, whose default gap is space-xs. Matching it is
|
|
72
|
+
what makes the default `below` placement render byte-for-byte as it did. */
|
|
73
|
+
.rsui-metric-card__figure {
|
|
74
|
+
@apply flex flex-col gap-space-xs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ── Inline placement ─────────────────────────────────────────────────────────
|
|
78
|
+
The reference shape: `2,000 ↗100% vs last month` on one row. Worth the option
|
|
79
|
+
because it buys back a line of vertical height, which is the difference between
|
|
80
|
+
a dashboard row of these fitting above the fold and not.
|
|
81
|
+
|
|
82
|
+
Centred, not baseline-aligned. The comparison is a 20px line box beside a 36px
|
|
83
|
+
one, and sitting them on a shared baseline hangs the small text off the bottom
|
|
84
|
+
of the figure — it reads as a footnote that slipped up a line rather than as
|
|
85
|
+
part of the same object. Centring is what the reference shows.
|
|
86
|
+
|
|
87
|
+
16px, and deliberately NOT the reference's 12px. The brand runs an 8px rhythm --
|
|
88
|
+
8, 16, 24, 32 -- and CardSpacing.test.js bans 12 outright across the card family
|
|
89
|
+
on the grounds that mixing it in is what made the family read untidy. So the
|
|
90
|
+
reference value rounds up onto the scale rather than earning a fourth exception.
|
|
91
|
+
Up rather than down because the gap is wider than the 8px stack gap on purpose:
|
|
92
|
+
stacked, the two are on separate lines and the leading already separates them;
|
|
93
|
+
side by side they compete for one reading line, and 8px there reads as a wide
|
|
94
|
+
space inside a single phrase rather than as a break between two things.
|
|
95
|
+
|
|
96
|
+
It wraps. A metric tile has no width contract — it is whatever the dashboard
|
|
97
|
+
grid hands it — and `175.95% Increase vs last month` beside a five-digit figure
|
|
98
|
+
will not fit a narrow column. Wrapping degrades to the `below` shape, which is
|
|
99
|
+
the right failure: the alternative is an overflowing row or a truncated
|
|
100
|
+
comparison, and a clipped percentage is a wrong number, not a terse one. */
|
|
101
|
+
.rsui-metric-card__figure--inline {
|
|
102
|
+
@apply flex-row flex-wrap items-center gap-space-md;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ── Previous-period comparison ───────────────────────────────────────────────
|
|
106
|
+
Its own element, not a Badge or a Pill. Both of those are pill-shaped chips
|
|
107
|
+
with a border and a filled ground, which under a text-3xl number reads as a
|
|
108
|
+
second object competing with the figure rather than as a caption qualifying
|
|
109
|
+
it. This is a line of text with an arrow, which is what the reference shows.
|
|
110
|
+
|
|
111
|
+
The arrow is DECORATIVE (aria-hidden in the template, per the pattern __icon
|
|
112
|
+
already uses) — the meaning is carried by the text the consumer passes in.
|
|
113
|
+
|
|
114
|
+
WCAG 1.4.1 is satisfied on two separate axes, which is worth stating plainly
|
|
115
|
+
because conflating them is how the first pass got it wrong:
|
|
116
|
+
|
|
117
|
+
DIRECTION the arrow plus the consumer's word ("Increase" / "Decrease").
|
|
118
|
+
A bare coloured percentage would not do it, which is why the
|
|
119
|
+
content is a slot the consumer fills rather than a number this
|
|
120
|
+
component formats.
|
|
121
|
+
SENTIMENT the badge below. Direction and the word say the figure ROSE;
|
|
122
|
+
they say nothing about whether rising is good, and hue alone
|
|
123
|
+
was carrying that. */
|
|
124
|
+
.rsui-metric-card__comparison {
|
|
125
|
+
@apply inline-flex items-center gap-space-xs;
|
|
126
|
+
@apply text-sm font-medium;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* EVERY sentiment is circled, not just bad news. The asymmetric version — a badge
|
|
130
|
+
on the negative tile and a bare arrow everywhere else — read as an inconsistency
|
|
131
|
+
rather than as a signal: a treatment that appears on one state out of three looks
|
|
132
|
+
like the other two are missing something.
|
|
133
|
+
|
|
134
|
+
20px, which matches text-sm's line box exactly, so circling every arrow shifts no
|
|
135
|
+
row height against the uncircled version this replaces. */
|
|
136
|
+
.rsui-metric-card__comparison-icon {
|
|
137
|
+
@apply inline-flex items-center justify-center shrink-0 size-5 rounded-full;
|
|
138
|
+
/* 1.5px, the weight Pill's own ring already uses, so the system is not learning
|
|
139
|
+
a second hairline. border-box keeps the outer box at 20px either way. */
|
|
140
|
+
@apply border-[1.5px] border-current;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.rsui-metric-card__comparison-icon svg {
|
|
144
|
+
@apply size-3;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── The quieter half of the comparison ───────────────────────────────────────
|
|
148
|
+
The reference splits the line in two: the change itself takes the sentiment
|
|
149
|
+
colour, and the phrase qualifying it ("vs last month") drops to tertiary ink.
|
|
150
|
+
The point is emphasis, not meaning — the eye should land on the figure and the
|
|
151
|
+
change first, and a whole line in one saturated colour flattens that.
|
|
152
|
+
|
|
153
|
+
Colour only. The reference sets both halves at text-sm Medium, so the weight is
|
|
154
|
+
inherited rather than stepped down; lightening the weight as well would push the
|
|
155
|
+
phrase past quiet into illegible at 14px.
|
|
156
|
+
|
|
157
|
+
A direct declaration, which beats the sentiment colour inherited from the parent
|
|
158
|
+
without needing to out-specify it. */
|
|
159
|
+
.rsui-metric-card__comparison-context {
|
|
160
|
+
@apply text-text-tertiary;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* On the two dark tones the comparison has already given up its semantic colour
|
|
164
|
+
for the tile's muted ink, so the change and its qualifier land on the same
|
|
165
|
+
value and the split collapses. That is cosmetic rather than a lost signal — the
|
|
166
|
+
two-tone is an emphasis device, and nothing about sentiment or direction rides
|
|
167
|
+
on it — and the alternative is inventing a second readable ink per tone to
|
|
168
|
+
preserve a nicety. tone-light keeps its semantic colour, so it keeps the split. */
|
|
169
|
+
.rsui-metric-card--tone-green .rsui-metric-card__comparison-context,
|
|
170
|
+
.rsui-metric-card--tone-teal .rsui-metric-card__comparison-context {
|
|
171
|
+
color: var(--rsui-metric-card-ink-muted);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* ── The non-colour sentiment cue ─────────────────────────────────────────────
|
|
175
|
+
Raised in review on #333, and still the constraint this has to satisfy: `trend`
|
|
176
|
+
and the consumer's word carry the DIRECTION, but nothing carries the SENTIMENT
|
|
177
|
+
except hue. In the up-is-bad case both tiles render an up arrow and the word
|
|
178
|
+
"Increase", so favourable and unfavourable are separated by colour alone
|
|
179
|
+
(WCAG 1.4.1) — and on the two dark tones, where the colour is dropped for
|
|
180
|
+
contrast, they would be identical outright.
|
|
181
|
+
|
|
182
|
+
So the circle is now on every sentiment, and FILL is what carries the meaning
|
|
183
|
+
instead of the circle's presence:
|
|
184
|
+
|
|
185
|
+
positive / neutral ringed, open centre
|
|
186
|
+
negative solid, arrow knocked out of the fill
|
|
187
|
+
|
|
188
|
+
That keeps the treatment symmetric — the thing the presence-or-absence version
|
|
189
|
+
got wrong — while leaving a difference that survives greyscale and survives the
|
|
190
|
+
colour being stripped on a dark tone, which presence alone was carrying before.
|
|
191
|
+
A solid mark also still reads heavier than an open one, so bad news keeps the
|
|
192
|
+
extra weight without being the only state that gets a mark at all.
|
|
193
|
+
|
|
194
|
+
Direction is untouched — an up arrow that is bad news still points up. */
|
|
195
|
+
.rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon {
|
|
196
|
+
@apply border-transparent text-text-on-color;
|
|
197
|
+
background-color: var(--color-text-error);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.rsui-metric-card__comparison--positive {
|
|
201
|
+
@apply text-text-success;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.rsui-metric-card__comparison--negative {
|
|
205
|
+
@apply text-text-error;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.rsui-metric-card__comparison--neutral {
|
|
209
|
+
@apply text-text-tertiary;
|
|
210
|
+
}
|
|
211
|
+
|
|
42
212
|
/* ---------------------------------------------------------------------------
|
|
43
213
|
Tonal variants
|
|
44
214
|
|
|
@@ -99,6 +269,47 @@
|
|
|
99
269
|
color: var(--rsui-metric-card-ink-muted);
|
|
100
270
|
}
|
|
101
271
|
|
|
272
|
+
.rsui-metric-card--toned .rsui-metric-card__description {
|
|
273
|
+
color: var(--rsui-metric-card-ink-muted);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* The semantic status inks are unreadable on the two DARK tiles, measured:
|
|
277
|
+
|
|
278
|
+
ink on tone-green #0e5942 on tone-teal #0f373c
|
|
279
|
+
--color-text-success #067647 1.46:1 2.26:1
|
|
280
|
+
--color-text-error #b42318 1.26:1 1.96:1
|
|
281
|
+
|
|
282
|
+
Both invisible — the same failure this file already documents for text-brand
|
|
283
|
+
on a toned tile. So the comparison gives up its colour there and takes the
|
|
284
|
+
tile's muted ink, exactly as the active state gives up its brand number, and
|
|
285
|
+
the arrow direction plus the consumer's word carry the meaning on their own.
|
|
286
|
+
Journey Green 200 measures 5.92:1 and Deep Teal 300 6.36:1, both clearing AA.
|
|
287
|
+
|
|
288
|
+
tone-light is deliberately NOT in this list. Its surface is Modern Bone 200
|
|
289
|
+
(#f9f8f6), where success measures 5.36:1 and error 6.19:1 — both pass AA, so
|
|
290
|
+
there is nothing to fix and stripping the colour would only cost a signal.
|
|
291
|
+
|
|
292
|
+
(0,2,0), so it outranks the (0,1,0) sentiment modifiers wherever they sit. */
|
|
293
|
+
.rsui-metric-card--tone-green .rsui-metric-card__comparison,
|
|
294
|
+
.rsui-metric-card--tone-teal .rsui-metric-card__comparison {
|
|
295
|
+
color: var(--rsui-metric-card-ink-muted);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* The solid badge goes with it. Filling it with the error ink on a dark tile would
|
|
299
|
+
reproduce exactly the 1.26:1 the rule above exists to avoid, so it takes the
|
|
300
|
+
tile's muted ink and puts the arrow in the tile's own surface colour — the
|
|
301
|
+
same 5.92:1 and 6.36:1 pairing, read the other way round.
|
|
302
|
+
|
|
303
|
+
The ringed badge needs no rule of its own: its border is `currentColor`, and the
|
|
304
|
+
rule above has already moved the comparison's ink to the tile's muted value, so
|
|
305
|
+
the ring follows it. This is the tone where solid-vs-ringed does its real work,
|
|
306
|
+
because once the colour is stripped it is the only sentiment signal left. */
|
|
307
|
+
.rsui-metric-card--tone-green .rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon,
|
|
308
|
+
.rsui-metric-card--tone-teal .rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon {
|
|
309
|
+
background-color: var(--rsui-metric-card-ink-muted);
|
|
310
|
+
color: var(--rsui-metric-card-surface);
|
|
311
|
+
}
|
|
312
|
+
|
|
102
313
|
/* The active state's brand-coloured number is colour reinforcement only — the
|
|
103
314
|
Card action layer carries the actual selection ring (WCAG 1.4.1). On a tonal
|
|
104
315
|
surface that reinforcement has to go: text-brand is Brand-500 (#136c52),
|