canny-ui 1.0.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.
@@ -0,0 +1,79 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Alert — contextual status banner. Composes neutral + semantic tones.
4
+
5
+ .alert {
6
+ position: relative;
7
+ display: flex;
8
+ align-items: flex-start;
9
+ gap: $space-3;
10
+ padding: $space-3 $space-4;
11
+ background: var(--bg-surface-lowest);
12
+ color: var(--text-primary);
13
+ border: 1px solid var(--border-color);
14
+ border-left-width: 3px;
15
+ border-radius: var(--radius-md);
16
+ font-family: var(--font-body);
17
+ font-size: 0.875rem;
18
+ line-height: 1.5;
19
+
20
+ &-title {
21
+ font-weight: 600;
22
+ line-height: 1.3;
23
+ margin: 0 0 2px;
24
+ }
25
+
26
+ &-body {
27
+ color: var(--text-secondary);
28
+ margin: 0;
29
+ }
30
+
31
+ &-close {
32
+ margin-left: auto;
33
+ background: transparent;
34
+ border: 0;
35
+ color: var(--text-muted);
36
+ cursor: pointer;
37
+ padding: 2px;
38
+ border-radius: var(--radius-xs);
39
+ line-height: 1;
40
+ font-size: 1rem;
41
+
42
+ &:hover { color: var(--text-primary); background: var(--bg-surface-low); }
43
+ }
44
+
45
+ // Soft variants — tonal background, semantic left rail
46
+ &.danger {
47
+ background: var(--color-danger-soft);
48
+ border-color: var(--color-danger);
49
+ .alert-title { color: var(--color-danger); }
50
+ }
51
+ &.warning {
52
+ background: var(--color-warning-soft);
53
+ border-color: var(--color-warning);
54
+ .alert-title { color: var(--color-warning); }
55
+ }
56
+ &.success {
57
+ background: var(--color-success-soft);
58
+ border-color: var(--color-success);
59
+ .alert-title { color: var(--color-success); }
60
+ }
61
+ &.info {
62
+ background: var(--color-info-soft);
63
+ border-color: var(--color-info);
64
+ .alert-title { color: var(--color-info); }
65
+ }
66
+
67
+ // Solid variant — high-priority announcements
68
+ &.solid {
69
+ border: 0;
70
+ background: var(--color-neutral);
71
+ color: var(--color-neutral-on);
72
+ .alert-body { color: var(--color-neutral-on); opacity: 0.8; }
73
+ .alert-close { color: var(--color-neutral-on); opacity: 0.7; &:hover { opacity: 1; background: rgba(255,255,255,0.1); } }
74
+ &.danger { background: var(--color-danger); color: var(--color-danger-on); }
75
+ &.warning { background: var(--color-warning); color: var(--color-warning-on); }
76
+ &.success { background: var(--color-success); color: var(--color-success-on); }
77
+ &.info { background: var(--color-info); color: var(--color-info-on); }
78
+ }
79
+ }
@@ -0,0 +1,92 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Avatar — image or initial identity chip. Sizes xs/sm/md/lg/xl.
4
+
5
+ .avatar {
6
+ position: relative;
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ flex-shrink: 0;
11
+ width: 32px;
12
+ height: 32px;
13
+ background: var(--color-primary-soft);
14
+ color: var(--color-primary);
15
+ border-radius: 50%;
16
+ font-family: var(--font-body);
17
+ font-size: 0.8125rem;
18
+ font-weight: 600;
19
+ letter-spacing: -0.01em;
20
+ user-select: none;
21
+
22
+ // Clip images/SVGs to the circle but NOT pseudo-elements
23
+ // (so presence dots can escape the boundary).
24
+ img, svg {
25
+ width: 100%;
26
+ height: 100%;
27
+ object-fit: cover;
28
+ display: block;
29
+ border-radius: inherit;
30
+ }
31
+
32
+ // Presence dot — absolutely positioned, escapes the circle by ~15%
33
+ // and is ringed in the page surface color so it reads on any background.
34
+ &.online::after,
35
+ &.offline::after,
36
+ &.busy::after {
37
+ content: '';
38
+ position: absolute;
39
+ bottom: 0;
40
+ right: 0;
41
+ width: 30%;
42
+ height: 30%;
43
+ min-width: 8px;
44
+ min-height: 8px;
45
+ border-radius: 50%;
46
+ transform: translate(15%, 15%);
47
+ box-shadow: 0 0 0 2px var(--bg-surface-lowest);
48
+ z-index: 1;
49
+ }
50
+ &.online::after { background: var(--color-success); }
51
+ &.offline::after { background: var(--text-muted); }
52
+ &.busy::after { background: var(--color-danger); }
53
+
54
+ // Sizes
55
+ &.xs { width: 20px; height: 20px; font-size: 0.625rem; }
56
+ &.sm { width: 24px; height: 24px; font-size: 0.6875rem; }
57
+ &.lg { width: 40px; height: 40px; font-size: 0.9375rem; }
58
+ &.xl { width: 56px; height: 56px; font-size: 1.125rem; }
59
+
60
+ // Square variant
61
+ &.square { border-radius: var(--radius-sm); }
62
+ }
63
+
64
+ // Stack — overlapping avatars.
65
+ // Presence dots still appear because each avatar manages its own overflow.
66
+ .avatar-stack {
67
+ display: inline-flex;
68
+ isolation: isolate;
69
+
70
+ .avatar {
71
+ box-shadow: 0 0 0 2px var(--bg-surface-lowest);
72
+ & + .avatar { margin-left: -8px; }
73
+
74
+ // Right-most avatar renders above its neighbors so the presence dot
75
+ // never hides behind the next chip.
76
+ &:hover,
77
+ &.online,
78
+ &.offline,
79
+ &.busy { z-index: 2; }
80
+ }
81
+ .avatar.xs + .avatar.xs { margin-left: -6px; }
82
+ .avatar.sm + .avatar.sm { margin-left: -7px; }
83
+ .avatar.lg + .avatar.lg { margin-left: -10px; }
84
+ .avatar.xl + .avatar.xl { margin-left: -14px; }
85
+
86
+ // Presence dots inside a stack need a ring that matches the stack "border".
87
+ .avatar.online::after,
88
+ .avatar.offline::after,
89
+ .avatar.busy::after {
90
+ box-shadow: 0 0 0 2px var(--bg-surface-lowest);
91
+ }
92
+ }
@@ -0,0 +1,417 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Enterprise button hierarchy — flat fills, predictable states, clear tiers.
4
+ // Inspired by Linear, Stripe, Vercel. No gradients, no ambient glow.
5
+ //
6
+ // Motion: button dimensions never change. Feedback lives *inside* the button
7
+ // via an MUI-style click-position ripple (rendered by canny-ui-react) plus
8
+ // shadow depth cues. Plain HTML usage gets color/shadow states only.
9
+
10
+ .btn {
11
+ position: relative;
12
+ overflow: hidden;
13
+ isolation: isolate;
14
+ display: inline-flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ gap: $space-2;
18
+ padding: 0 $space-4;
19
+ min-height: 36px;
20
+ border: 0;
21
+ border-radius: var(--radius-sm);
22
+ background: transparent;
23
+ color: var(--text-primary);
24
+ text-decoration: none;
25
+ font-family: var(--font-body);
26
+ font-size: 0.875rem;
27
+ font-weight: 500;
28
+ letter-spacing: -0.005em;
29
+ line-height: 1;
30
+ cursor: pointer;
31
+ user-select: none;
32
+ white-space: nowrap;
33
+ -webkit-tap-highlight-color: transparent;
34
+ transition:
35
+ background-color var(--transition-fast),
36
+ border-color var(--transition-fast),
37
+ box-shadow 180ms var(--ease-standard),
38
+ color var(--transition-fast);
39
+
40
+ &:disabled,
41
+ &[aria-disabled='true'] {
42
+ opacity: 0.5;
43
+ cursor: not-allowed;
44
+ pointer-events: none;
45
+ }
46
+
47
+ &:focus-visible {
48
+ outline: none;
49
+ box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
50
+ }
51
+
52
+ // ── Content wrapper — keeps children above the ripple layer ───────────
53
+ .btn-content {
54
+ position: relative;
55
+ z-index: 1;
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ gap: inherit;
60
+ }
61
+
62
+ // ── Ripple layer — animated by motion in canny-ui-react ───────────────
63
+ .btn-ripple {
64
+ position: absolute;
65
+ z-index: 0;
66
+ border-radius: 50%;
67
+ pointer-events: none;
68
+ transform-origin: center;
69
+ // Default: dark ripple for light backgrounds (outline, ghost, soft)
70
+ background: rgba(0, 0, 0, 0.18);
71
+ }
72
+
73
+ // Solid/secondary have dark fills — use a bright ripple for contrast
74
+ &.solid .btn-ripple,
75
+ &.secondary .btn-ripple {
76
+ background: rgba(255, 255, 255, 0.55);
77
+ }
78
+
79
+ // Reduced motion — suppress ripple entirely
80
+ @media (prefers-reduced-motion: reduce) {
81
+ .btn-ripple { display: none; }
82
+ }
83
+
84
+ // ── Solid — primary (indigo) ────────────────────────────────────────────
85
+ &.solid {
86
+ background: var(--color-primary);
87
+ color: var(--color-primary-on);
88
+ box-shadow: var(--shadow-xs);
89
+
90
+ &:hover:not(:disabled) { background: var(--color-primary-hover); box-shadow: var(--shadow-sm); }
91
+ &:active:not(:disabled) { background: var(--color-primary-active); box-shadow: var(--shadow-xs), inset 0 1px 0 0 rgba(0, 0, 0, 0.08); }
92
+
93
+ &.danger {
94
+ background: var(--color-danger);
95
+ color: var(--color-danger-on);
96
+ &:hover:not(:disabled) { background: var(--color-danger-hover); }
97
+ }
98
+ &.warning {
99
+ background: var(--color-warning);
100
+ color: var(--color-warning-on);
101
+ &:hover:not(:disabled) { background: var(--color-warning-hover); }
102
+ }
103
+ &.success {
104
+ background: var(--color-success);
105
+ color: var(--color-success-on);
106
+ &:hover:not(:disabled) { background: var(--color-success-hover); }
107
+ }
108
+ &.info {
109
+ background: var(--color-info);
110
+ color: var(--color-info-on);
111
+ &:hover:not(:disabled) { background: var(--color-info-hover); }
112
+ }
113
+ }
114
+
115
+ // ── Secondary — neutral dark (zinc-900) ────────────────────────────────
116
+ &.secondary {
117
+ background: var(--color-neutral);
118
+ color: var(--color-neutral-on);
119
+ box-shadow: var(--shadow-xs);
120
+
121
+ &:hover:not(:disabled) { background: var(--color-neutral-hover); box-shadow: var(--shadow-sm); }
122
+ &:active:not(:disabled) { box-shadow: var(--shadow-xs), inset 0 1px 0 0 rgba(0, 0, 0, 0.12); }
123
+ }
124
+
125
+ // ── Outline — 1px border, white surface ────────────────────────────────
126
+ &.outline {
127
+ background: var(--bg-surface-lowest);
128
+ color: var(--text-primary);
129
+ box-shadow: inset 0 0 0 1px var(--border-color), var(--shadow-xs);
130
+
131
+ &:hover:not(:disabled) {
132
+ background: var(--bg-surface-low);
133
+ box-shadow: inset 0 0 0 1px var(--border-color-strong), var(--shadow-sm);
134
+ }
135
+ &:active:not(:disabled) {
136
+ box-shadow: inset 0 0 0 1px var(--border-color-strong), inset 0 1px 0 0 rgba(0, 0, 0, 0.04);
137
+ }
138
+
139
+ &.danger { color: var(--color-danger); }
140
+ &.warning { color: var(--color-warning); }
141
+ &.success { color: var(--color-success); }
142
+ &.info { color: var(--color-info); }
143
+ }
144
+
145
+ // ── Dashed — Ant Design-style, border intent-colors on hover ───────────
146
+ &.dashed {
147
+ background: var(--bg-surface-lowest);
148
+ color: var(--text-primary);
149
+ border: 1px dashed var(--border-color);
150
+ transition:
151
+ background-color var(--transition-fast),
152
+ border-color var(--transition-fast),
153
+ color var(--transition-fast),
154
+ box-shadow 180ms var(--ease-standard);
155
+
156
+ &:hover:not(:disabled) {
157
+ background: var(--bg-surface-low);
158
+ border-color: var(--color-primary);
159
+ color: var(--color-primary);
160
+ }
161
+ &:active:not(:disabled) {
162
+ background: var(--bg-surface-edge);
163
+ border-color: var(--color-primary-active);
164
+ }
165
+
166
+ &.danger { &:hover:not(:disabled) { border-color: var(--color-danger); color: var(--color-danger); } }
167
+ &.warning { &:hover:not(:disabled) { border-color: var(--color-warning); color: var(--color-warning); } }
168
+ &.success { &:hover:not(:disabled) { border-color: var(--color-success); color: var(--color-success); } }
169
+ &.info { &:hover:not(:disabled) { border-color: var(--color-info); color: var(--color-info); } }
170
+ }
171
+
172
+ // ── Ghost — text-only, tints on hover ──────────────────────────────────
173
+ &.ghost {
174
+ background: transparent;
175
+ color: var(--text-primary);
176
+
177
+ &:hover:not(:disabled) { background: var(--bg-surface-low); }
178
+ &:active:not(:disabled) { background: var(--bg-surface-edge); }
179
+
180
+ &.danger { color: var(--color-danger); &:hover:not(:disabled) { background: var(--color-danger-soft); } }
181
+ &.warning { color: var(--color-warning); &:hover:not(:disabled) { background: var(--color-warning-soft); } }
182
+ &.success { color: var(--color-success); &:hover:not(:disabled) { background: var(--color-success-soft); } }
183
+ &.info { color: var(--color-info); &:hover:not(:disabled) { background: var(--color-info-soft); } }
184
+ }
185
+
186
+ // ── Soft — tonal fill, gentle presence ─────────────────────────────────
187
+ &.soft {
188
+ background: var(--color-primary-soft);
189
+ color: var(--color-primary);
190
+
191
+ &:hover:not(:disabled) { background: var(--bg-surface-edge); }
192
+
193
+ &.danger { background: var(--color-danger-soft); color: var(--color-danger); }
194
+ &.warning { background: var(--color-warning-soft); color: var(--color-warning); }
195
+ &.success { background: var(--color-success-soft); color: var(--color-success); }
196
+ &.info { background: var(--color-info-soft); color: var(--color-info); }
197
+ }
198
+
199
+ // ── Link — inline text action (no ripple, no overflow clip) ────────────
200
+ &.link {
201
+ overflow: visible;
202
+ background: transparent;
203
+ color: var(--text-accent);
204
+ padding: 0;
205
+ min-height: auto;
206
+ border-radius: 0;
207
+ text-decoration: underline;
208
+ text-decoration-thickness: 1px;
209
+ text-underline-offset: 0.2em;
210
+
211
+ &:hover:not(:disabled) { color: var(--color-primary-hover); }
212
+
213
+ .btn-ripple { display: none; }
214
+ }
215
+
216
+ // ── Sizes ──────────────────────────────────────────────────────────────
217
+ &.sm {
218
+ padding: 0 $space-3;
219
+ min-height: 28px;
220
+ font-size: 0.8125rem;
221
+ }
222
+ &.lg {
223
+ padding: 0 $space-5;
224
+ min-height: 44px;
225
+ font-size: 0.9375rem;
226
+ }
227
+
228
+ // Icon-only square variant
229
+ &.icon {
230
+ padding: 0;
231
+ width: 36px;
232
+ &.sm { width: 28px; }
233
+ &.lg { width: 44px; }
234
+ }
235
+
236
+ // ── Shapes ─────────────────────────────────────────────────────────────
237
+ &.round {
238
+ border-radius: 999px;
239
+ }
240
+
241
+ &.circle {
242
+ padding: 0;
243
+ width: 36px;
244
+ border-radius: 50%;
245
+ &.sm { width: 28px; }
246
+ &.lg { width: 44px; }
247
+ }
248
+
249
+ // ── Block — full width ─────────────────────────────────────────────────
250
+ &.block {
251
+ display: flex;
252
+ width: 100%;
253
+ }
254
+
255
+ // ── Loading — spinner takes over, interactions disabled ────────────────
256
+ &.loading {
257
+ pointer-events: none;
258
+ cursor: default;
259
+ }
260
+
261
+ // ── Toggle (pressed) state ─ aria-pressed="true" driven by Radix Toggle
262
+ &[aria-pressed='true'],
263
+ &[data-state='on'] {
264
+ &.ghost { background: var(--color-primary-soft); color: var(--color-primary); }
265
+ &.outline { background: var(--color-primary-soft); color: var(--color-primary); box-shadow: inset 0 0 0 1px var(--color-primary), var(--shadow-xs); }
266
+ &.soft { background: var(--color-primary); color: var(--color-primary-on); }
267
+ &.dashed { background: var(--color-primary-soft); color: var(--color-primary); border-color: var(--color-primary); }
268
+ }
269
+ }
270
+
271
+ // ── Floating Action Button (FAB) ───────────────────────────────────────
272
+ // Elevated circular primary action. Standard=56px, sm=40px, lg=64px.
273
+ // `.extended` morphs into a pill with icon+label.
274
+ .btn.fab {
275
+ width: 56px;
276
+ min-height: 56px;
277
+ padding: 0;
278
+ border-radius: 50%;
279
+ box-shadow: var(--shadow-lg);
280
+ font-weight: 600;
281
+
282
+ &:hover:not(:disabled) {
283
+ box-shadow: 0 12px 20px -6px rgba(9, 9, 11, 0.16), 0 4px 8px -2px rgba(9, 9, 11, 0.08);
284
+ }
285
+ &:active:not(:disabled) {
286
+ box-shadow: var(--shadow-md), inset 0 1px 0 0 rgba(0, 0, 0, 0.08);
287
+ }
288
+
289
+ &.sm { width: 40px; min-height: 40px; font-size: 0.875rem; }
290
+ &.lg { width: 64px; min-height: 64px; font-size: 1rem; }
291
+
292
+ // Extended FAB — pill with icon + text
293
+ &.extended {
294
+ width: auto;
295
+ border-radius: 999px;
296
+ padding: 0 $space-5;
297
+ gap: $space-2;
298
+ min-width: 80px;
299
+
300
+ &.sm { padding: 0 $space-4; min-width: 64px; }
301
+ &.lg { padding: 0 $space-6; min-width: 96px; }
302
+ }
303
+
304
+ // Fixed-corner position utilities — z-index 50 sits above normal content
305
+ &.fixed-bottom-right { position: fixed; bottom: $space-6; right: $space-6; z-index: 50; }
306
+ &.fixed-bottom-left { position: fixed; bottom: $space-6; left: $space-6; z-index: 50; }
307
+ &.fixed-top-right { position: fixed; top: $space-6; right: $space-6; z-index: 50; }
308
+ &.fixed-top-left { position: fixed; top: $space-6; left: $space-6; z-index: 50; }
309
+ }
310
+
311
+ // ── Button group — attached horizontal/vertical row ────────────────────
312
+ .btn-group {
313
+ display: inline-flex;
314
+ vertical-align: middle;
315
+ isolation: isolate;
316
+
317
+ > .btn {
318
+ border-radius: 0;
319
+ position: relative;
320
+
321
+ &:not(:first-child) { margin-left: -1px; }
322
+ &:first-child { border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
323
+ &:last-child { border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
324
+
325
+ // Stack on top when interacting so the border/ring is fully visible
326
+ &:hover:not(:disabled),
327
+ &:focus-visible,
328
+ &[aria-pressed='true'],
329
+ &[data-state='on'] { z-index: 1; }
330
+ }
331
+
332
+ // Vertical variant
333
+ &.vertical {
334
+ flex-direction: column;
335
+ > .btn {
336
+ &:not(:first-child) { margin-left: 0; margin-top: -1px; }
337
+ &:first-child { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
338
+ &:last-child { border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
339
+ }
340
+ }
341
+
342
+ // Spaced variant — small gap, no border collapse (toolbar-style)
343
+ &.spaced {
344
+ gap: $space-1;
345
+ > .btn {
346
+ border-radius: var(--radius-sm);
347
+ &:not(:first-child) { margin-left: 0; }
348
+ &:not(:last-child) { margin-right: 0; }
349
+ }
350
+ }
351
+
352
+ // Pill/round group
353
+ &.round > .btn {
354
+ &:first-child { border-top-left-radius: 999px; border-bottom-left-radius: 999px; padding-left: $space-5; }
355
+ &:last-child { border-top-right-radius: 999px; border-bottom-right-radius: 999px; padding-right: $space-5; }
356
+ }
357
+ }
358
+
359
+ // ── Spinner primitive — used by Button's loading state ──────────────────
360
+ .btn-spinner {
361
+ display: inline-block;
362
+ width: 1em;
363
+ height: 1em;
364
+ border: 2px solid currentColor;
365
+ border-right-color: transparent;
366
+ border-radius: 50%;
367
+ animation: cny-btn-spin 700ms linear infinite;
368
+ flex-shrink: 0;
369
+ vertical-align: -0.125em;
370
+ }
371
+
372
+ @keyframes cny-btn-spin {
373
+ to { transform: rotate(360deg); }
374
+ }
375
+
376
+ @media (prefers-reduced-motion: reduce) {
377
+ .btn-spinner { animation-duration: 1400ms; }
378
+ }
379
+
380
+ // ── Forced colors (Windows High Contrast) — ensure borders & focus ─────
381
+ @media (forced-colors: active) {
382
+ .btn {
383
+ border: 1px solid ButtonBorder;
384
+ forced-color-adjust: none;
385
+ color: ButtonText;
386
+ background: ButtonFace;
387
+
388
+ &:focus-visible {
389
+ outline: 2px solid SelectedItem;
390
+ outline-offset: 2px;
391
+ box-shadow: none;
392
+ }
393
+
394
+ &.solid,
395
+ &.secondary {
396
+ background: SelectedItem;
397
+ color: SelectedItemText;
398
+ border-color: SelectedItem;
399
+ }
400
+
401
+ &[aria-pressed='true'],
402
+ &[data-state='on'] {
403
+ background: SelectedItem;
404
+ color: SelectedItemText;
405
+ }
406
+
407
+ &:disabled,
408
+ &[aria-disabled='true'] {
409
+ color: GrayText;
410
+ border-color: GrayText;
411
+ }
412
+
413
+ .btn-ripple { display: none; }
414
+ }
415
+
416
+ .btn-spinner { border-color: ButtonText; border-right-color: transparent; }
417
+ }
@@ -0,0 +1,66 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Enterprise card — real 1px border on surface-lowest, subtle shadow, no ribbon.
4
+
5
+ .card {
6
+ position: relative;
7
+ background: var(--bg-surface-lowest);
8
+ border-radius: var(--radius-lg);
9
+ box-shadow: var(--shadow-card);
10
+ padding: $space-6;
11
+ transition:
12
+ box-shadow var(--transition-fast),
13
+ border-color var(--transition-fast);
14
+
15
+ &.interactive {
16
+ cursor: pointer;
17
+ &:hover { box-shadow: var(--shadow-lift); }
18
+ }
19
+
20
+ // Accent strips — hairline, not 3px slabs. Editorial ribbon.
21
+ &.danger,
22
+ &.warning,
23
+ &.success,
24
+ &.info {
25
+ overflow: hidden;
26
+ &::before {
27
+ content: '';
28
+ position: absolute;
29
+ left: 0;
30
+ top: $space-6;
31
+ bottom: $space-6;
32
+ width: 2px;
33
+ border-radius: 1px;
34
+ }
35
+ }
36
+ &.danger::before { background: var(--color-danger); }
37
+ &.warning::before { background: var(--color-warning); }
38
+ &.success::before { background: var(--color-success); }
39
+ &.info::before { background: var(--color-info); }
40
+
41
+ // Flush — transparent stacking variant (for lists of rows)
42
+ &.flush {
43
+ background: transparent;
44
+ box-shadow: none;
45
+ border-top: 1px solid var(--hairline-color);
46
+ border-radius: 0;
47
+ padding: $space-5 0;
48
+ }
49
+
50
+ &-header {
51
+ display: flex;
52
+ align-items: baseline;
53
+ justify-content: space-between;
54
+ gap: $space-4;
55
+ margin-bottom: $space-4;
56
+ }
57
+ &-footer {
58
+ margin-top: $space-5;
59
+ padding-top: $space-4;
60
+ border-top: 1px solid var(--hairline-color);
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ gap: $space-3;
65
+ }
66
+ }