@redseed/redseed-ui-tailwindcss 7.55.0 → 7.57.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/colors.css CHANGED
@@ -565,7 +565,20 @@
565
565
  --Colors-Warm-Mauve-800: #855878;
566
566
  --Colors-Warm-Mauve-900: #6a4660;
567
567
  --Colors-Warm-Mauve-950: #4c3145;
568
- /* Secondary brand accents (single tokens, Figma Brand palette · Secondary) */
568
+ /* Secondary brand accents (single tokens, Figma Brand palette · Secondary)
569
+
570
+ DO NOT USE these in product surfaces. Components and pages draw only on
571
+ the six primary identity ramps above — Journey Green, Deep Teal, Modern
572
+ Bone, Warm Yellow, Soft Lilac, Warm Mauve — plus the Grey ramp and the
573
+ semantic tokens (success / warning / error / info).
574
+
575
+ They are defined here for completeness against the Figma palette, which
576
+ is why they have no ramp: single values only, so they cannot carry text
577
+ either. Cornflower measures 3.2:1 on white and Apricot Lift 2.25:1, both
578
+ short of the 4.5:1 that WCAG 1.4.3 asks of body text.
579
+
580
+ One accepted exception: --color-button-primary-hover consumes Plum Ink
581
+ (see tokens.css). That is deliberate and predates this note. */
569
582
  --Colors-Apricot-Lift: #e89b6c;
570
583
  --Colors-Cornflower: #6e8fd6;
571
584
  --Colors-Marigold: #f2b33c;
@@ -1,19 +1,30 @@
1
1
  .rsui-badge {
2
- @apply inline-flex items-center justify-center gap-x-1;
2
+ /* whitespace-nowrap: a badge is a pill, so its label must stay on one line. Without it
3
+ a badge in a narrow column wrapped to two lines and grew to 42px tall — seen in
4
+ production on the Twinsies home page, where "1 answer to redo" and "2 answers to redo"
5
+ both broke across two lines inside a rounded-full chip. Every other chip-shaped
6
+ control in the system already declares this: Pill, ButtonSlot, TabSliderItem and
7
+ SwitcherItem. Badge was the only one that did not. */
8
+ @apply inline-flex items-center justify-center gap-x-1 whitespace-nowrap;
3
9
  @apply w-fit h-fit rounded-full;
4
10
  @apply text-sm font-medium leading-4;
5
11
  }
6
12
 
13
+ /* Horizontal padding grows with the size: it used to sit at px-2 for xs, sm and md alike,
14
+ so a md badge was exactly as wide as an xs one and its 8/8 padding collapsed to square —
15
+ on a rounded-full shape that puts the label closer to the curved ends than to the flat
16
+ top and bottom. The 8 / 10 / 12 progression matches Pill, the other chip in the system.
17
+ Vertical padding is untouched, so no badge changes height. */
7
18
  .rsui-badge--xs {
8
19
  @apply text-xs leading-3 px-2 py-0.5;
9
20
  }
10
21
 
11
22
  .rsui-badge--sm {
12
- @apply px-2 py-1;
23
+ @apply px-2.5 py-1;
13
24
  }
14
25
 
15
26
  .rsui-badge--md {
16
- @apply px-2 py-2;
27
+ @apply px-3 py-2;
17
28
  }
18
29
 
19
30
  .rsui-badge--secondary {
@@ -40,6 +51,12 @@
40
51
  @apply text-brand-500 bg-brand-50 border border-brand-200;
41
52
  }
42
53
 
54
+ /* Classic and Coach still ride the pre-rebrand pink and redseed-blue ramps rather than the
55
+ current brand ones (Skillscheck above already moved to Journey Green). Left as-is on
56
+ purpose: both categories are due to be phased out, so re-homing them onto brand colours
57
+ would be churn. There is also only one unclaimed brand ramp (Deep Teal) against two
58
+ categories — Journey Green, Warm Mauve and Modern Bone are taken by brand, AI and page
59
+ grounds. Revisit only if the phase-out stalls. */
43
60
  .rsui-badge--classic {
44
61
  @apply text-pink-500 bg-pink-50 border border-pink-200;
45
62
  }
@@ -1,5 +1,8 @@
1
+ /* ButtonCard now takes the family's tonal hover from .rsui-card--hoverable, which it opts
2
+ into via :hoverable="!disabled". It previously cancelled the card shadow and darkened the
3
+ border instead — the only bespoke hover treatment in the family, and the reason hovering
4
+ a ButtonCard looked nothing like hovering a Card. */
1
5
  .rsui-button-card {
2
- @apply hover:shadow-none hover:border-text-primary;
3
6
  }
4
7
 
5
8
  .rsui-button-card--disabled {
@@ -53,8 +53,13 @@
53
53
  }
54
54
 
55
55
  /* modifier xs size */
56
+ /* px-2.5, not px-1.5: horizontal padding steps 10/12/14/16/18/20 across xs..2xl, and
57
+ xs was the only size where px equalled py — every other size runs 1.4-1.6x wider than
58
+ tall, so an xs label sat 6px from the border and read visibly cramped. Height is
59
+ unchanged (py untouched); only the button's width grows. --xs-icon keeps its own p-1.5
60
+ because a square icon button is correct at that size. */
56
61
  .rsui-button-slot--xs {
57
- @apply text-sm px-1.5 py-1.5 gap-1;
62
+ @apply text-sm px-2.5 py-1.5 gap-1;
58
63
  }
59
64
 
60
65
  /* modifier xs size with icon */
@@ -3,13 +3,20 @@
3
3
  @apply text-button-tertiary bg-transparent border-transparent;
4
4
 
5
5
  /* default hover state */
6
- @apply hover:text-button-tertiary-hover hover:bg-transparent hover:border-button-tertiary-hover;
6
+ /* A ghost button fills faintly on hover; it must not sprout an outline. This used to
7
+ set border-button-tertiary-hover, taking the border from transparent to solid
8
+ #124943 — so hovering a borderless button made a hard dark rule appear, while the
9
+ fill stayed transparent. That is the inverse of the intended ghost behaviour, and
10
+ the opposite of primary (fill darkens) and secondary (outline darkens). */
11
+ @apply hover:text-button-tertiary-hover hover:bg-background-secondary hover:border-transparent;
7
12
 
8
13
  /* default focus state */
9
14
  @apply focus-visible:ring-button-tertiary focus-visible:border-transparent;
10
15
 
11
16
  /* default active state */
12
- @apply active:bg-transparent active:border-transparent;
17
+ /* Pressed goes one step darker than hover (transparent -> Grey-50 -> Grey-100) so the
18
+ press reads as deeper rather than clearing the hover fill back to nothing. */
19
+ @apply active:text-button-tertiary-active active:bg-background-tertiary-invert active:border-transparent;
13
20
 
14
21
  /* default disabled state */
15
22
  @apply disabled:bg-transparent disabled:border-transparent disabled:text-text-disabled;
@@ -68,6 +68,9 @@
68
68
  @apply relative border-0;
69
69
  }
70
70
 
71
+ /* Positioning only. The dashed outline itself is drawn by the <rect> inside this element —
72
+ it is an <svg>, and the rect strokes 1px with stroke-dasharray 8 16. Do not add CSS border
73
+ utilities here: they paint a second dashed edge on top of the SVG stroke. */
71
74
  .rsui-card__draft-border {
72
75
  @apply absolute inset-0 size-full pointer-events-none rounded-xl border-border-secondary;
73
76
  }
@@ -79,8 +82,20 @@
79
82
  @apply overflow-x-hidden;
80
83
  }
81
84
 
82
- .rsui-card--hoverable {
83
- @apply hover:shadow-md;
85
+ /* Hover state layer. The card family is an Outlined system — flat, 1px border, no resting
86
+ elevation — so hover tints the surface rather than lifting it. Two reasons this is a
87
+ ::before scrim and not a background-color swap: every colour variant (secondary, brand,
88
+ success, info, warning, error, draft, ai) has to survive underneath it, and a scrim
89
+ composites over the ai variant's gradient where a flat colour could not. ::before rather
90
+ than ::after so it paints beneath the card's content in DOM order. */
91
+ .rsui-card--hoverable::before {
92
+ content: '';
93
+ @apply absolute inset-0 rounded-xl pointer-events-none;
94
+ @apply bg-background-primary-contrast opacity-0 transition-opacity duration-200;
95
+ }
96
+
97
+ .rsui-card--hoverable:hover::before {
98
+ @apply opacity-5;
84
99
  }
85
100
 
86
101
  .rsui-card--clickable {
@@ -105,6 +120,12 @@
105
120
  colour alone (WCAG 1.4.1). */
106
121
  .rsui-card__action-layer--active {
107
122
  @apply ring-2 ring-offset-2 ring-brand-500;
123
+
124
+ /* Hold the brand ring through focus. Without this the focus-visible rule above repaints
125
+ the ring border-primary grey, so focusing a selected card silently dropped its
126
+ selection cue — the ring is the same geometry for both states, so only colour carries
127
+ the difference. */
128
+ @apply focus-visible:ring-brand-500;
108
129
  }
109
130
 
110
131
  .rsui-card__action-layer-label {
@@ -28,6 +28,17 @@
28
28
  @apply @md/card-header:items-center;
29
29
  }
30
30
 
31
+ /* Centring only holds while the text block is a lone title. `--single-line` means the *title*
32
+ is clamped to one line, not that the header is one row — add a subtitle and a badge and the
33
+ block is 78px tall, where centring a 48px avatar left it sitting 15px low against the text
34
+ it belongs to. Seen in production on the Twinsies meeting cards. Top-align as soon as the
35
+ block carries more than the title; a lone title is shorter than the avatar, so centring
36
+ stays correct there. */
37
+ .rsui-card-header__header--single-line:has(.rsui-card-header__subtitle),
38
+ .rsui-card-header__header--single-line:has(.rsui-card-header__badge) {
39
+ @apply @md/card-header:items-start;
40
+ }
41
+
31
42
  .rsui-card-header__avatar {
32
43
  @apply w-12 overflow-hidden flex items-center justify-center;
33
44
  }
@@ -83,7 +83,10 @@
83
83
  /* Success state styling */
84
84
  /* Increase specificity so it overrides base .rsui-card styles */
85
85
  .rsui-card.rsui-checkbox-card--success {
86
- @apply bg-success-100 border-border-success;
86
+ /* bg-background-success, not the raw bg-success-100 palette step: this was the only
87
+ success surface in the library painting #DCFAE6 while Card, Badge, MessageBox and
88
+ Section all use the #ECFDF3 token — two different success greens, same border. */
89
+ @apply bg-background-success border-border-success;
87
90
  }
88
91
 
89
92
  .rsui-checkbox-card--success .rsui-checkbox-card__title-text {
@@ -78,7 +78,9 @@
78
78
  }
79
79
 
80
80
  .rsui-comment__body {
81
- @apply py-3 px-4 rounded-xl border;
81
+ /* 14px vertical, matching the row rhythm ListItem uses — a comment is a row of prose
82
+ and read tight against its neighbour at 12px. */
83
+ @apply py-3.5 px-4 rounded-xl border;
82
84
  @apply rounded-tl-none ml-8;
83
85
  @apply text-text-primary bg-background-primary;
84
86
  @apply border-border-primary;
@@ -7,7 +7,9 @@
7
7
  }
8
8
 
9
9
  .rsui-form-field-checkbox__check {
10
- @apply size-6 shrink-0 relative bg-background-primary rounded-lg cursor-pointer;
10
+ /* Centres the tick. The input is absolutely positioned so it stays out of flow and keeps
11
+ filling the box; only the SVG is laid out here. */
12
+ @apply size-6 shrink-0 relative flex items-center justify-center bg-background-primary rounded-lg cursor-pointer;
11
13
  @apply has-[:disabled]:text-text-disabled has-[:disabled]:cursor-default;
12
14
  }
13
15
 
@@ -16,11 +18,21 @@
16
18
  @apply w-full h-full text-text-primary border border-border-primary text-transparent bg-none;
17
19
  @apply focus-visible:ring-3 focus-visible:ring-border-primary focus-visible:border-border-primary;
18
20
  @apply invalid:border-border-error invalid:ring-0;
21
+
22
+ /* Checked fills the box and the tick inverts, which is how every mainstream checkbox
23
+ reads. It used to stay transparent, so a checked box was a white square with a dark
24
+ tick — and the success variant already filled, so the default was the odd one out. */
25
+ @apply checked:bg-text-primary checked:border-text-primary;
26
+
19
27
  @apply disabled:text-text-disabled disabled:bg-background-disabled disabled:cursor-default;
28
+ @apply disabled:checked:bg-background-disabled disabled:checked:border-border-disabled;
20
29
  }
21
30
 
31
+ /* The tick is inset rather than size-full. At size-full a 24px glyph sat in a 24px box with
32
+ no breathing room, so its strokes ran into the border and the control read cramped. Each
33
+ size keeps roughly a 4px optical inset — see the sm/xs overrides further down. */
22
34
  .rsui-form-field-checkbox__check svg {
23
- @apply size-full relative z-1 pointer-events-none stroke-[2.5] origin-center;
35
+ @apply size-4 relative z-1 pointer-events-none stroke-[2.5] origin-center;
24
36
  /* Short, cute draw-on: the tick strokes itself in with a tiny pop.
25
37
  dasharray is set a touch above the check path length so the resting
26
38
  state has no gap. Disabled under reduced-motion below. */
@@ -28,6 +40,12 @@
28
40
  animation: rsui-checkbox-tick 200ms ease-out;
29
41
  }
30
42
 
43
+ /* Invert the tick against the filled box. Scoped to :not(:disabled) because a disabled
44
+ checked box keeps the pale disabled fill, where an inverted tick would disappear. */
45
+ .rsui-form-field-checkbox__check:has(input:checked:not(:disabled)) svg {
46
+ @apply text-text-on-color;
47
+ }
48
+
31
49
  @keyframes rsui-checkbox-tick {
32
50
  0% { stroke-dashoffset: 30; transform: scale(0.8); }
33
51
  60% { transform: scale(1.06); }
@@ -53,6 +71,10 @@
53
71
  @apply rounded-md;
54
72
  }
55
73
 
74
+ .rsui-form-field-checkbox__check--sm svg {
75
+ @apply size-3;
76
+ }
77
+
56
78
  .rsui-form-field-checkbox__label--sm {
57
79
  @apply leading-5 text-sm;
58
80
  }
@@ -65,6 +87,10 @@
65
87
  @apply rounded;
66
88
  }
67
89
 
90
+ .rsui-form-field-checkbox__check--xs svg {
91
+ @apply size-2;
92
+ }
93
+
68
94
  .rsui-form-field-checkbox__label--xs {
69
95
  @apply leading-4 text-xs;
70
96
  }
@@ -153,6 +153,38 @@
153
153
  @apply hover:bg-background-brand-contrast;
154
154
  }
155
155
 
156
+ /*
157
+ * Square off the edge each endpoint faces the range on, so the band closes.
158
+ *
159
+ * The base `.rsui-form-field-date-range__day` is `rounded-lg` on all four
160
+ * corners and the endpoints only override the background, so an endpoint used to
161
+ * sit between two square in-range fills as a rounded square — leaving a white
162
+ * notch at each corner where its curve pulled away from its neighbour. The band
163
+ * read as interrupted by the endpoint rather than containing it.
164
+ *
165
+ * MUI solves the same problem by drawing a continuous highlight band behind the
166
+ * cells and putting the endpoint circle on top of it. That needs a second
167
+ * element per cell; squaring the inner edge of the endpoint's own fill gets the
168
+ * same join out of the existing one.
169
+ */
170
+ .rsui-form-field-date-range__day--range-start {
171
+ @apply rounded-r-none;
172
+ }
173
+
174
+ .rsui-form-field-date-range__day--range-end {
175
+ @apply rounded-l-none;
176
+ }
177
+
178
+ /*
179
+ * Both modifiers land on the same day for a single-day window — `today`, or a
180
+ * range picked start-and-end on one date. There is no band to join, so restore
181
+ * the full radius. This selector is deliberately last and doubly-classed so it
182
+ * outranks the two above.
183
+ */
184
+ .rsui-form-field-date-range__day--range-start.rsui-form-field-date-range__day--range-end {
185
+ @apply rounded-lg;
186
+ }
187
+
156
188
  /*
157
189
  * The day the presets resolve against — "today" as the server sees it.
158
190
  *
@@ -1,4 +1,15 @@
1
+ /* The field group owns its own spacing. It used to render a bare list of siblings and
2
+ inherit spacing from `.rsui-form-slot__content > *`, so anywhere that ancestor was absent —
3
+ including every one of its own stories — the fields and the submit button collapsed flush
4
+ against each other with a 0px gap. gap-space-md (16px) matches the mt-4 that rule applied,
5
+ so the group is unchanged inside FormSlot and correct everywhere else. */
6
+ .rsui-form-fields-login {
7
+ @apply flex flex-col gap-space-md;
8
+ }
9
+
10
+ /* pt-4 dropped: the group's gap-space-md now supplies the 16px above this link, and
11
+ keeping both stacked them into 32px. */
1
12
  .rsui-form-login__signup {
2
- @apply text-sm pt-4;
13
+ @apply text-sm;
3
14
  }
4
15
 
@@ -1,4 +1,15 @@
1
+ /* The field group owns its own spacing. It used to render a bare list of siblings and
2
+ inherit spacing from `.rsui-form-slot__content > *`, so anywhere that ancestor was absent —
3
+ including every one of its own stories — the fields and the submit button collapsed flush
4
+ against each other with a 0px gap. gap-space-md (16px) matches the mt-4 that rule applied,
5
+ so the group is unchanged inside FormSlot and correct everywhere else. */
6
+ .rsui-form-fields-register {
7
+ @apply flex flex-col gap-space-md;
8
+ }
9
+
10
+ /* pt-4 dropped: the group's gap-space-md now supplies the 16px above this link, and
11
+ keeping both stacked them into 32px. */
1
12
  .rsui-form-register__signin {
2
- @apply text-sm pt-4;
13
+ @apply text-sm;
3
14
  }
4
15
 
@@ -88,42 +88,52 @@
88
88
 
89
89
  .rsui-icon--primary {
90
90
  @apply *:text-text-primary;
91
+ --rsui-icon-pulse: var(--color-text-primary);
91
92
  }
92
93
 
93
94
  .rsui-icon--secondary {
94
95
  @apply *:text-text-secondary;
96
+ --rsui-icon-pulse: var(--color-text-secondary);
95
97
  }
96
98
 
97
99
  .rsui-icon--brand {
98
100
  @apply *:text-text-brand;
101
+ --rsui-icon-pulse: var(--color-text-brand);
99
102
  }
100
103
 
101
104
  .rsui-icon--success {
102
105
  @apply *:text-text-success;
106
+ --rsui-icon-pulse: var(--color-text-success);
103
107
  }
104
108
 
105
109
  .rsui-icon--info {
106
110
  @apply *:text-text-info;
111
+ --rsui-icon-pulse: var(--color-text-info);
107
112
  }
108
113
 
109
114
  .rsui-icon--warning {
110
115
  @apply *:text-text-warning;
116
+ --rsui-icon-pulse: var(--color-text-warning);
111
117
  }
112
118
 
113
119
  .rsui-icon--error {
114
120
  @apply *:text-text-error;
121
+ --rsui-icon-pulse: var(--color-text-error);
115
122
  }
116
123
 
117
124
  .rsui-icon--ai {
118
125
  @apply *:text-text-ai;
126
+ --rsui-icon-pulse: var(--color-text-ai);
119
127
  }
120
128
 
121
129
  .rsui-icon--disabled {
122
130
  @apply *:text-text-disabled;
131
+ --rsui-icon-pulse: var(--color-text-disabled);
123
132
  }
124
133
 
125
134
  .rsui-icon--classic {
126
135
  @apply *:text-pink-500;
136
+ --rsui-icon-pulse: var(--color-pink-500);
127
137
  }
128
138
 
129
139
  .rsui-icon--primary.rsui-icon--invert {
@@ -216,52 +226,145 @@
216
226
  @apply *:text-text-on-color;
217
227
  }
218
228
 
229
+ /*
230
+ * Both flags together is the one combination that puts the glyph back on its
231
+ * variant colour, against a tinted fill — so each of these restates the pulse
232
+ * token alongside it, undoing the on-colour default set below. Kept here rather
233
+ * than in a block of its own so the ring colour is edited in the same place as
234
+ * the glyph colour it has to match.
235
+ */
219
236
  .rsui-icon--primary.rsui-icon--background.rsui-icon--invert {
220
237
  @apply bg-background-primary-invert;
221
238
  @apply *:text-text-primary;
239
+ --rsui-icon-pulse: var(--color-text-primary);
222
240
  }
223
241
 
224
242
  .rsui-icon--secondary.rsui-icon--background.rsui-icon--invert {
225
243
  @apply bg-background-secondary-invert;
226
244
  @apply *:text-text-secondary;
245
+ --rsui-icon-pulse: var(--color-text-secondary);
227
246
  }
228
247
 
229
248
  .rsui-icon--brand.rsui-icon--background.rsui-icon--invert {
230
249
  @apply bg-background-brand-invert;
231
250
  @apply *:text-text-brand;
251
+ --rsui-icon-pulse: var(--color-text-brand);
232
252
  }
233
253
 
234
254
  .rsui-icon--success.rsui-icon--background.rsui-icon--invert {
235
255
  @apply bg-background-success-invert;
236
256
  @apply *:text-text-success;
257
+ --rsui-icon-pulse: var(--color-text-success);
237
258
  }
238
259
 
239
260
  .rsui-icon--info.rsui-icon--background.rsui-icon--invert {
240
261
  @apply bg-background-info-invert;
241
262
  @apply *:text-text-info;
263
+ --rsui-icon-pulse: var(--color-text-info);
242
264
  }
243
265
 
244
266
  .rsui-icon--warning.rsui-icon--background.rsui-icon--invert {
245
267
  @apply bg-background-warning-invert;
246
268
  @apply *:text-text-warning;
269
+ --rsui-icon-pulse: var(--color-text-warning);
247
270
  }
248
271
 
249
272
  .rsui-icon--error.rsui-icon--background.rsui-icon--invert {
250
273
  @apply bg-background-error-invert;
251
274
  @apply *:text-text-error;
275
+ --rsui-icon-pulse: var(--color-text-error);
252
276
  }
253
277
 
254
278
  .rsui-icon--ai.rsui-icon--background.rsui-icon--invert {
255
279
  @apply bg-background-ai-invert;
256
280
  @apply *:text-text-ai;
281
+ --rsui-icon-pulse: var(--color-text-ai);
257
282
  }
258
283
 
259
284
  .rsui-icon--disabled.rsui-icon--background.rsui-icon--invert {
260
285
  @apply bg-background-disabled-invert;
261
286
  @apply *:text-text-disabled;
287
+ --rsui-icon-pulse: var(--color-text-disabled);
262
288
  }
263
289
 
264
290
  .rsui-icon--classic.rsui-icon--background.rsui-icon--invert {
265
291
  @apply bg-pink-50;
266
292
  @apply *:text-pink-500;
267
- }
293
+ --rsui-icon-pulse: var(--color-pink-500);
294
+ }
295
+ /*
296
+ * On a filled or inverted icon the glyph is repainted to an on-colour token, so
297
+ * the ring follows it rather than staying on a variant colour it no longer
298
+ * matches. Same specificity as the base variant rules and declared after them,
299
+ * so it wins on source order — but it loses to the two-flag rules above, which
300
+ * is the point: those put the glyph back on its variant colour and take the ring
301
+ * with them.
302
+ */
303
+ .rsui-icon--background,
304
+ .rsui-icon--invert {
305
+ --rsui-icon-pulse: var(--color-text-on-color);
306
+ }
307
+
308
+ /*
309
+ * Optional attention pulse: a ring expands out from the icon and fades, a few
310
+ * times, when the icon arrives.
311
+ *
312
+ * Deliberately a fixed number of iterations rather than `infinite`. An animation
313
+ * that repeats for more than five seconds needs a pause control (WCAG 2.2.2), and
314
+ * an icon has nowhere sensible to put one — three bursts draw the eye on arrival
315
+ * without taking on that obligation.
316
+ *
317
+ * Duration x iterations is the number that matters there, and it must stay under
318
+ * five seconds: 1.4s x 3 leaves 800ms of headroom. Slowing the ring to 1.8s to
319
+ * soften it further would put the total at 5.4s and trip the criterion, so the
320
+ * softening is done with opacity, border width and travel instead.
321
+ *
322
+ * The ring takes its colour from `--rsui-icon-pulse`, which each colour variant
323
+ * sets alongside its glyph rule.
324
+ *
325
+ * A custom property rather than `currentColor`: the variants colour the SVG child
326
+ * (`*:text-…`) and not the wrapper, so an inherited ring comes out near-black.
327
+ * Setting `color` on the wrapper instead would fix the base variants but silently
328
+ * miss `--invert` and `--background`, which repaint the glyph and would leave the
329
+ * ring on the old colour. A property the combination rules can override keeps the
330
+ * answer in one place — and, unlike a wrapper colour, it is inert for everything
331
+ * that does not read it.
332
+ *
333
+ * `pointer-events-none` because the ring overflows the icon's box and would
334
+ * otherwise sit over whatever is beside it.
335
+ */
336
+ .rsui-icon--pulse {
337
+ @apply relative;
338
+ }
339
+
340
+ .rsui-icon--pulse::after {
341
+ @apply content-[''] absolute inset-0 rounded-full pointer-events-none;
342
+ border: 1px solid var(--rsui-icon-pulse, currentColor);
343
+ /* Invisible at rest, in both directions. Without this the ring reverts to its
344
+ un-animated state once the iterations finish — full opacity at scale 1 —
345
+ and a solid ring stays welded around the icon for the life of the page. */
346
+ @apply opacity-0;
347
+ animation: rsui-icon-pulse 1.4s ease-out 3;
348
+ }
349
+
350
+ @keyframes rsui-icon-pulse {
351
+ 0% {
352
+ opacity: 0.35;
353
+ transform: scale(1);
354
+ }
355
+ 100% {
356
+ opacity: 0;
357
+ transform: scale(1.8);
358
+ }
359
+ }
360
+
361
+ /*
362
+ * The pulse is decoration on top of an icon that is already visible, so removing
363
+ * it costs nothing — no state is conveyed by the animation alone.
364
+ */
365
+ @media (prefers-reduced-motion: reduce) {
366
+ .rsui-icon--pulse::after {
367
+ animation: none;
368
+ display: none;
369
+ }
370
+ }
@@ -1,5 +1,9 @@
1
1
  .rsui-list-item {
2
- @apply relative flex flex-col rounded-xl p-3 overflow-hidden;
2
+ /* 14px vertical rather than the 12px of a flat p-3: at 12px a two-line row reads as
3
+ cramped against its neighbours, which is the note that came back from laying these
4
+ out on a real page. Horizontal padding is unchanged. Half-steps are already used
5
+ across the button sizes, so 3.5 is in keeping. */
6
+ @apply relative flex flex-col rounded-xl px-3 py-3.5 overflow-hidden;
3
7
  }
4
8
 
5
9
  .rsui-list-item--clickable {
@@ -69,6 +73,65 @@
69
73
  @apply mt-2 flex flex-wrap gap-x-4 gap-y-2;
70
74
  }
71
75
 
76
+ /* Leading icon. Inline, unlike .rsui-list-item__avatar above, which hangs outside the row
77
+ box on a negative offset — an avatar identifies a person, an icon classifies the row, so
78
+ they sit differently and can be used together. */
79
+ .rsui-list-item__icon {
80
+ @apply shrink-0 size-8 rounded-lg overflow-hidden;
81
+ @apply flex items-center justify-center;
82
+ @apply bg-background-secondary;
83
+ }
84
+
85
+ .rsui-list-item__icon svg {
86
+ @apply size-4;
87
+ }
88
+
89
+ /* Accent rail — a pseudo-element, so an accented row has the same DOM as a plain one.
90
+ Needs z-1 to sit above .rsui-list-item__bg, which is absolute and would otherwise paint
91
+ over it; the root's rounded-xl clips the rail into the corner. */
92
+ .rsui-list-item--accented::before {
93
+ content: '';
94
+ @apply absolute left-0 top-0 bottom-0 w-1 z-1 pointer-events-none;
95
+ }
96
+
97
+ .rsui-list-item--accent-warning::before {
98
+ @apply bg-background-warning-contrast;
99
+ }
100
+
101
+ .rsui-list-item--accent-info::before {
102
+ @apply bg-background-info-contrast;
103
+ }
104
+
105
+ .rsui-list-item--accent-success::before {
106
+ @apply bg-background-success-contrast;
107
+ }
108
+
109
+ .rsui-list-item--accent-error::before {
110
+ @apply bg-background-error-contrast;
111
+ }
112
+
113
+ .rsui-list-item--accent-neutral::before {
114
+ @apply bg-background-primary-contrast;
115
+ }
116
+
117
+ /* The icon tile takes the accent's own tint when both are used, so the rail and the tile
118
+ read as one mark rather than two. */
119
+ .rsui-list-item--accent-warning .rsui-list-item__icon {
120
+ @apply bg-background-warning;
121
+ }
122
+
123
+ .rsui-list-item--accent-info .rsui-list-item__icon {
124
+ @apply bg-background-info;
125
+ }
126
+
127
+ .rsui-list-item--accent-success .rsui-list-item__icon {
128
+ @apply bg-background-success;
129
+ }
130
+
131
+ .rsui-list-item--accent-error .rsui-list-item__icon {
132
+ @apply bg-background-error;
133
+ }
134
+
72
135
  .rsui-list-item__description {
73
136
  @apply line-clamp-2 mt-2 text-base;
74
137
  }