@spree/dashboard-ui 6.0.1 → 6.0.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spree/dashboard-ui",
3
3
  "license": "MIT",
4
- "version": "6.0.1",
4
+ "version": "6.0.3",
5
5
  "description": "Spree Dashboard design system — shadcn primitives, headless composed components, and design tokens. Source-only package; consumers compile via their own Vite/Tailwind setup.",
6
6
  "author": "Vendo Connect Inc.",
7
7
  "homepage": "https://spreecommerce.org",
@@ -61,11 +61,9 @@
61
61
  "@dnd-kit/utilities": "3.2.2",
62
62
  "@fontsource-variable/geist": "5.3.0",
63
63
  "@fontsource-variable/geist-mono": "5.3.0",
64
- "@tanstack/react-hotkeys": "^0.16.0",
64
+ "@tanstack/react-hotkeys": "0.16.0",
65
65
  "@tanstack/react-table": "9.3.1",
66
66
  "@tiptap/core": "3.32.0",
67
- "@tiptap/extension-bubble-menu": "3.32.0",
68
- "@tiptap/extension-floating-menu": "3.32.0",
69
67
  "@tiptap/extension-image": "3.32.0",
70
68
  "@tiptap/extension-link": "3.32.0",
71
69
  "@tiptap/extension-placeholder": "3.32.0",
@@ -75,7 +73,6 @@
75
73
  "@uiw/react-json-view": "1.12.2",
76
74
  "class-variance-authority": "0.7.1",
77
75
  "clsx": "2.1.1",
78
- "cmdk": "1.1.1",
79
76
  "date-fns": "4.4.0",
80
77
  "date-fns-tz": "3.2.0",
81
78
  "flag-icons": "7.6.0",
@@ -83,10 +80,9 @@
83
80
  "react-colorful": "5.8.1",
84
81
  "react-day-picker": "10.0.2",
85
82
  "recharts": "3.10.1",
86
- "shadcn": "4.22.1",
87
83
  "tailwind-merge": "3.7.0",
88
84
  "tw-animate-css": "1.4.0",
89
- "@spree/core": "^6.0.1"
85
+ "@spree/core": "^6.0.3"
90
86
  },
91
87
  "devDependencies": {
92
88
  "@tanstack/react-router": "^1.170.45",
package/src/shadcn.css ADDED
@@ -0,0 +1,640 @@
1
+ /* Vendored from shadcn@4.22.1 (dist/tailwind.css, MIT) so host apps do not
2
+ * install the shadcn CLI to get these variants and keyframes. */
3
+
4
+ @theme inline {
5
+ @keyframes accordion-down {
6
+ from {
7
+ height: 0;
8
+ }
9
+ to {
10
+ height: var(
11
+ --radix-accordion-content-height,
12
+ var(--accordion-panel-height, auto)
13
+ );
14
+ }
15
+ }
16
+
17
+ @keyframes accordion-up {
18
+ from {
19
+ height: var(
20
+ --radix-accordion-content-height,
21
+ var(--accordion-panel-height, auto)
22
+ );
23
+ }
24
+ to {
25
+ height: 0;
26
+ }
27
+ }
28
+ }
29
+
30
+ /* Custom variants */
31
+ @custom-variant data-open {
32
+ &:where([data-state="open"]),
33
+ &:where([data-open]:not([data-open="false"])) {
34
+ @slot;
35
+ }
36
+ }
37
+
38
+ @custom-variant data-closed {
39
+ &:where([data-state="closed"]),
40
+ &:where([data-closed]:not([data-closed="false"])) {
41
+ @slot;
42
+ }
43
+ }
44
+
45
+ @custom-variant data-checked {
46
+ &:where([data-state="checked"]),
47
+ &:where([data-checked]:not([data-checked="false"])) {
48
+ @slot;
49
+ }
50
+ }
51
+
52
+ @custom-variant data-unchecked {
53
+ &:where([data-state="unchecked"]),
54
+ &:where([data-unchecked]:not([data-unchecked="false"])) {
55
+ @slot;
56
+ }
57
+ }
58
+
59
+ @custom-variant data-selected {
60
+ &:where([data-selected="true"]) {
61
+ @slot;
62
+ }
63
+ }
64
+
65
+ @custom-variant data-disabled {
66
+ &:where([data-disabled="true"]),
67
+ &:where([data-disabled]:not([data-disabled="false"])) {
68
+ @slot;
69
+ }
70
+ }
71
+
72
+ @custom-variant data-active {
73
+ &:where([data-state="active"]),
74
+ &:where([data-active]:not([data-active="false"])) {
75
+ @slot;
76
+ }
77
+ }
78
+
79
+ @custom-variant data-horizontal {
80
+ &:where([data-orientation="horizontal"]) {
81
+ @slot;
82
+ }
83
+ }
84
+
85
+ @custom-variant data-vertical {
86
+ &:where([data-orientation="vertical"]) {
87
+ @slot;
88
+ }
89
+ }
90
+
91
+ @utility no-scrollbar {
92
+ -ms-overflow-style: none;
93
+ scrollbar-width: none;
94
+
95
+ &::-webkit-scrollbar {
96
+ display: none;
97
+ }
98
+ }
99
+
100
+ /* scroll-fade */
101
+ @property --scroll-fade-t {
102
+ syntax: "<length-percentage>";
103
+ inherits: false;
104
+ initial-value: 0px;
105
+ }
106
+ @property --scroll-fade-b {
107
+ syntax: "<length-percentage>";
108
+ inherits: false;
109
+ initial-value: 0px;
110
+ }
111
+ @property --scroll-fade-s {
112
+ syntax: "<length-percentage>";
113
+ inherits: false;
114
+ initial-value: 0px;
115
+ }
116
+ @property --scroll-fade-e {
117
+ syntax: "<length-percentage>";
118
+ inherits: false;
119
+ initial-value: 0px;
120
+ }
121
+ @property --scroll-fade-mask {
122
+ syntax: "*";
123
+ inherits: false;
124
+ }
125
+
126
+ @theme inline {
127
+ @keyframes scroll-fade-reveal-t {
128
+ from {
129
+ --scroll-fade-t: 0px;
130
+ }
131
+ to {
132
+ --scroll-fade-t: var(--_scroll-fade-size-t, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
133
+ }
134
+ }
135
+ @keyframes scroll-fade-reveal-b {
136
+ from {
137
+ --scroll-fade-b: var(--_scroll-fade-size-b, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
138
+ }
139
+ to {
140
+ --scroll-fade-b: 0px;
141
+ }
142
+ }
143
+ @keyframes scroll-fade-reveal-s {
144
+ from {
145
+ --scroll-fade-s: 0px;
146
+ }
147
+ to {
148
+ --scroll-fade-s: var(--_scroll-fade-size-s, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
149
+ }
150
+ }
151
+ @keyframes scroll-fade-reveal-e {
152
+ from {
153
+ --scroll-fade-e: var(--_scroll-fade-size-e, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
154
+ }
155
+ to {
156
+ --scroll-fade-e: 0px;
157
+ }
158
+ }
159
+ }
160
+
161
+ @utility scroll-fade {
162
+ --_scroll-fade-size-t: var(
163
+ --scroll-fade-t-size,
164
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
165
+ );
166
+ --_scroll-fade-size-b: var(
167
+ --scroll-fade-b-size,
168
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
169
+ );
170
+ --scroll-fade-block: linear-gradient(
171
+ to bottom,
172
+ transparent 0,
173
+ #000 var(--scroll-fade-t, 0px),
174
+ #000 calc(100% - var(--scroll-fade-b, 0px)),
175
+ transparent 100%
176
+ );
177
+ -webkit-mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
178
+ mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
179
+ -webkit-mask-composite: source-in;
180
+ mask-composite: intersect;
181
+ -webkit-mask-repeat: no-repeat;
182
+ mask-repeat: no-repeat;
183
+
184
+ @supports (animation-timeline: scroll()) {
185
+ animation:
186
+ scroll-fade-reveal-t 1ms ease-in-out,
187
+ scroll-fade-reveal-b 1ms ease-in-out;
188
+ animation-timeline: scroll(self y), scroll(self y);
189
+ animation-range:
190
+ 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24)),
191
+ calc(100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))) 100%;
192
+ animation-fill-mode: both;
193
+ }
194
+
195
+ @supports not (animation-timeline: scroll()) {
196
+ --scroll-fade-t: var(--_scroll-fade-size-t);
197
+ --scroll-fade-b: var(--_scroll-fade-size-b);
198
+ }
199
+ }
200
+
201
+ @utility scroll-fade-y {
202
+ --_scroll-fade-size-t: var(
203
+ --scroll-fade-t-size,
204
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
205
+ );
206
+ --_scroll-fade-size-b: var(
207
+ --scroll-fade-b-size,
208
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
209
+ );
210
+ --scroll-fade-block: linear-gradient(
211
+ to bottom,
212
+ transparent 0,
213
+ #000 var(--scroll-fade-t, 0px),
214
+ #000 calc(100% - var(--scroll-fade-b, 0px)),
215
+ transparent 100%
216
+ );
217
+ -webkit-mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
218
+ mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
219
+ -webkit-mask-composite: source-in;
220
+ mask-composite: intersect;
221
+ -webkit-mask-repeat: no-repeat;
222
+ mask-repeat: no-repeat;
223
+
224
+ @supports (animation-timeline: scroll()) {
225
+ animation:
226
+ scroll-fade-reveal-t 1ms ease-in-out,
227
+ scroll-fade-reveal-b 1ms ease-in-out;
228
+ animation-timeline: scroll(self y), scroll(self y);
229
+ animation-range:
230
+ 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24)),
231
+ calc(100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))) 100%;
232
+ animation-fill-mode: both;
233
+ }
234
+
235
+ @supports not (animation-timeline: scroll()) {
236
+ --scroll-fade-t: var(--_scroll-fade-size-t);
237
+ --scroll-fade-b: var(--_scroll-fade-size-b);
238
+ }
239
+ }
240
+
241
+ @utility scroll-fade-x {
242
+ --_scroll-fade-size-s: var(
243
+ --scroll-fade-s-size,
244
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
245
+ );
246
+ --_scroll-fade-size-e: var(
247
+ --scroll-fade-e-size,
248
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
249
+ );
250
+ --scroll-fade-inline: linear-gradient(
251
+ to right,
252
+ transparent 0,
253
+ #000 var(--scroll-fade-s, 0px),
254
+ #000 calc(100% - var(--scroll-fade-e, 0px)),
255
+ transparent 100%
256
+ );
257
+ &:where([dir="rtl"], [dir="rtl"] *) {
258
+ --scroll-fade-inline: linear-gradient(
259
+ to left,
260
+ transparent 0,
261
+ #000 var(--scroll-fade-s, 0px),
262
+ #000 calc(100% - var(--scroll-fade-e, 0px)),
263
+ transparent 100%
264
+ );
265
+ }
266
+ -webkit-mask-image: var(--scroll-fade-mask, var(--scroll-fade-inline));
267
+ mask-image: var(--scroll-fade-mask, var(--scroll-fade-inline));
268
+ -webkit-mask-composite: source-in;
269
+ mask-composite: intersect;
270
+ -webkit-mask-repeat: no-repeat;
271
+ mask-repeat: no-repeat;
272
+
273
+ @supports (animation-timeline: scroll()) {
274
+ animation:
275
+ scroll-fade-reveal-s 1ms ease-in-out,
276
+ scroll-fade-reveal-e 1ms ease-in-out;
277
+ animation-timeline: scroll(self inline), scroll(self inline);
278
+ animation-range:
279
+ 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24)),
280
+ calc(100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))) 100%;
281
+ animation-fill-mode: both;
282
+ }
283
+
284
+ @supports not (animation-timeline: scroll()) {
285
+ --scroll-fade-s: var(--_scroll-fade-size-s);
286
+ --scroll-fade-e: var(--_scroll-fade-size-e);
287
+ }
288
+ }
289
+
290
+ @utility scroll-fade-t {
291
+ --_scroll-fade-size-t: var(
292
+ --scroll-fade-t-size,
293
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
294
+ );
295
+ --scroll-fade-mask: linear-gradient(
296
+ to bottom,
297
+ transparent 0,
298
+ #000 var(--scroll-fade-t, 0px),
299
+ #000 100%
300
+ );
301
+ -webkit-mask-image: var(--scroll-fade-mask);
302
+ mask-image: var(--scroll-fade-mask);
303
+ -webkit-mask-composite: source-in;
304
+ mask-composite: intersect;
305
+ -webkit-mask-repeat: no-repeat;
306
+ mask-repeat: no-repeat;
307
+
308
+ @supports (animation-timeline: scroll()) {
309
+ animation: scroll-fade-reveal-t 1ms ease-in-out;
310
+ animation-timeline: scroll(self y);
311
+ animation-range: 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24));
312
+ animation-fill-mode: both;
313
+ }
314
+
315
+ @supports not (animation-timeline: scroll()) {
316
+ --scroll-fade-t: var(--_scroll-fade-size-t);
317
+ }
318
+ }
319
+
320
+ @utility scroll-fade-b {
321
+ --_scroll-fade-size-b: var(
322
+ --scroll-fade-b-size,
323
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
324
+ );
325
+ --scroll-fade-mask: linear-gradient(
326
+ to bottom,
327
+ #000 0,
328
+ #000 calc(100% - var(--scroll-fade-b, 0px)),
329
+ transparent 100%
330
+ );
331
+ -webkit-mask-image: var(--scroll-fade-mask);
332
+ mask-image: var(--scroll-fade-mask);
333
+ -webkit-mask-composite: source-in;
334
+ mask-composite: intersect;
335
+ -webkit-mask-repeat: no-repeat;
336
+ mask-repeat: no-repeat;
337
+
338
+ @supports (animation-timeline: scroll()) {
339
+ animation: scroll-fade-reveal-b 1ms ease-in-out;
340
+ animation-timeline: scroll(self y);
341
+ animation-range: calc(
342
+ 100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))
343
+ )
344
+ 100%;
345
+ animation-fill-mode: both;
346
+ }
347
+
348
+ @supports not (animation-timeline: scroll()) {
349
+ --scroll-fade-b: var(--_scroll-fade-size-b);
350
+ }
351
+ }
352
+
353
+ @utility scroll-fade-l {
354
+ --_scroll-fade-size-s: var(
355
+ --scroll-fade-s-size,
356
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
357
+ );
358
+ --scroll-fade-mask: linear-gradient(
359
+ to right,
360
+ transparent 0,
361
+ #000 var(--scroll-fade-s, 0px),
362
+ #000 100%
363
+ );
364
+ -webkit-mask-image: var(--scroll-fade-mask);
365
+ mask-image: var(--scroll-fade-mask);
366
+ -webkit-mask-composite: source-in;
367
+ mask-composite: intersect;
368
+ -webkit-mask-repeat: no-repeat;
369
+ mask-repeat: no-repeat;
370
+
371
+ @supports (animation-timeline: scroll()) {
372
+ animation: scroll-fade-reveal-s 1ms ease-in-out;
373
+ animation-timeline: scroll(self x);
374
+ animation-range: 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24));
375
+ animation-fill-mode: both;
376
+ }
377
+
378
+ @supports not (animation-timeline: scroll()) {
379
+ --scroll-fade-s: var(--_scroll-fade-size-s);
380
+ }
381
+ }
382
+
383
+ @utility scroll-fade-r {
384
+ --_scroll-fade-size-e: var(
385
+ --scroll-fade-e-size,
386
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
387
+ );
388
+ --scroll-fade-mask: linear-gradient(
389
+ to right,
390
+ #000 0,
391
+ #000 calc(100% - var(--scroll-fade-e, 0px)),
392
+ transparent 100%
393
+ );
394
+ -webkit-mask-image: var(--scroll-fade-mask);
395
+ mask-image: var(--scroll-fade-mask);
396
+ -webkit-mask-composite: source-in;
397
+ mask-composite: intersect;
398
+ -webkit-mask-repeat: no-repeat;
399
+ mask-repeat: no-repeat;
400
+
401
+ @supports (animation-timeline: scroll()) {
402
+ animation: scroll-fade-reveal-e 1ms ease-in-out;
403
+ animation-timeline: scroll(self x);
404
+ animation-range: calc(
405
+ 100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))
406
+ )
407
+ 100%;
408
+ animation-fill-mode: both;
409
+ }
410
+
411
+ @supports not (animation-timeline: scroll()) {
412
+ --scroll-fade-e: var(--_scroll-fade-size-e);
413
+ }
414
+ }
415
+
416
+ @utility scroll-fade-s {
417
+ --_scroll-fade-size-s: var(
418
+ --scroll-fade-s-size,
419
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
420
+ );
421
+ --scroll-fade-mask: linear-gradient(
422
+ to right,
423
+ transparent 0,
424
+ #000 var(--scroll-fade-s, 0px),
425
+ #000 100%
426
+ );
427
+ &:where([dir="rtl"], [dir="rtl"] *) {
428
+ --scroll-fade-mask: linear-gradient(
429
+ to left,
430
+ transparent 0,
431
+ #000 var(--scroll-fade-s, 0px),
432
+ #000 100%
433
+ );
434
+ }
435
+ -webkit-mask-image: var(--scroll-fade-mask);
436
+ mask-image: var(--scroll-fade-mask);
437
+ -webkit-mask-composite: source-in;
438
+ mask-composite: intersect;
439
+ -webkit-mask-repeat: no-repeat;
440
+ mask-repeat: no-repeat;
441
+
442
+ @supports (animation-timeline: scroll()) {
443
+ animation: scroll-fade-reveal-s 1ms ease-in-out;
444
+ animation-timeline: scroll(self inline);
445
+ animation-range: 0 var(--scroll-fade-reveal, calc(var(--spacing) * 24));
446
+ animation-fill-mode: both;
447
+ }
448
+
449
+ @supports not (animation-timeline: scroll()) {
450
+ --scroll-fade-s: var(--_scroll-fade-size-s);
451
+ }
452
+ }
453
+
454
+ @utility scroll-fade-e {
455
+ --_scroll-fade-size-e: var(
456
+ --scroll-fade-e-size,
457
+ var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10)))
458
+ );
459
+ --scroll-fade-mask: linear-gradient(
460
+ to right,
461
+ #000 0,
462
+ #000 calc(100% - var(--scroll-fade-e, 0px)),
463
+ transparent 100%
464
+ );
465
+ &:where([dir="rtl"], [dir="rtl"] *) {
466
+ --scroll-fade-mask: linear-gradient(
467
+ to left,
468
+ #000 0,
469
+ #000 calc(100% - var(--scroll-fade-e, 0px)),
470
+ transparent 100%
471
+ );
472
+ }
473
+ -webkit-mask-image: var(--scroll-fade-mask);
474
+ mask-image: var(--scroll-fade-mask);
475
+ -webkit-mask-composite: source-in;
476
+ mask-composite: intersect;
477
+ -webkit-mask-repeat: no-repeat;
478
+ mask-repeat: no-repeat;
479
+
480
+ @supports (animation-timeline: scroll()) {
481
+ animation: scroll-fade-reveal-e 1ms ease-in-out;
482
+ animation-timeline: scroll(self inline);
483
+ animation-range: calc(
484
+ 100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))
485
+ )
486
+ 100%;
487
+ animation-fill-mode: both;
488
+ }
489
+
490
+ @supports not (animation-timeline: scroll()) {
491
+ --scroll-fade-e: var(--_scroll-fade-size-e);
492
+ }
493
+ }
494
+
495
+ @utility scroll-fade-* {
496
+ --scroll-fade-size: calc(var(--spacing) * --value(integer));
497
+ --scroll-fade-size: --value([length], [percentage]);
498
+ }
499
+
500
+ @utility scroll-fade-t-* {
501
+ --scroll-fade-t-size: calc(var(--spacing) * --value(integer));
502
+ --scroll-fade-t-size: --value([length], [percentage]);
503
+ }
504
+
505
+ @utility scroll-fade-b-* {
506
+ --scroll-fade-b-size: calc(var(--spacing) * --value(integer));
507
+ --scroll-fade-b-size: --value([length], [percentage]);
508
+ }
509
+
510
+ @utility scroll-fade-s-* {
511
+ --scroll-fade-s-size: calc(var(--spacing) * --value(integer));
512
+ --scroll-fade-s-size: --value([length], [percentage]);
513
+ }
514
+
515
+ @utility scroll-fade-e-* {
516
+ --scroll-fade-e-size: calc(var(--spacing) * --value(integer));
517
+ --scroll-fade-e-size: --value([length], [percentage]);
518
+ }
519
+
520
+ @utility scroll-fade-none {
521
+ --scroll-fade-mask: none;
522
+ }
523
+
524
+ /* shimmer */
525
+ @property --shimmer-angle {
526
+ syntax: "<angle>";
527
+ inherits: true;
528
+ initial-value: 20deg;
529
+ }
530
+ @property --shimmer-image {
531
+ syntax: "*";
532
+ inherits: false;
533
+ }
534
+ @property --shimmer-text-fill {
535
+ syntax: "*";
536
+ inherits: false;
537
+ }
538
+
539
+ @theme inline {
540
+ @keyframes tw-shimmer {
541
+ from {
542
+ background-position: 100% 0;
543
+ }
544
+ to {
545
+ background-position: 0 0;
546
+ }
547
+ }
548
+ }
549
+
550
+ @utility shimmer {
551
+ --_spread: var(--shimmer-spread, calc(3ch + 40px));
552
+ --_base: currentColor;
553
+ --_highlight: var(
554
+ --shimmer-color,
555
+ oklch(from currentColor l c h / calc(alpha* 0.2))
556
+ );
557
+
558
+ background-image: var(
559
+ --shimmer-image,
560
+ linear-gradient(
561
+ calc(90deg + var(--shimmer-angle)),
562
+ var(--_base) calc(50% - var(--_spread)),
563
+ color-mix(in oklch, var(--_highlight), var(--_base) 50%)
564
+ calc(50% - var(--_spread) * 0.5),
565
+ var(--_highlight) 50%,
566
+ color-mix(in oklch, var(--_highlight), var(--_base) 50%)
567
+ calc(50% + var(--_spread) * 0.5),
568
+ var(--_base) calc(50% + var(--_spread))
569
+ )
570
+ );
571
+ background-repeat: no-repeat;
572
+ background-size: calc(200% + var(--_spread) * 2) 100%;
573
+ background-position: 0 0;
574
+ background-clip: text;
575
+ -webkit-background-clip: text;
576
+ -webkit-text-fill-color: var(--shimmer-text-fill, transparent);
577
+ animation: tw-shimmer var(--shimmer-duration, 2s) linear infinite;
578
+
579
+ @variant dark {
580
+ --_highlight: var(
581
+ --shimmer-color,
582
+ oklch(from currentColor max(0.8, calc(l + 0.4)) c h / calc(alpha + 0.4))
583
+ );
584
+ }
585
+
586
+ &:where([dir="rtl"], [dir="rtl"] *) {
587
+ animation-direction: reverse;
588
+ }
589
+
590
+ @media (prefers-reduced-motion: reduce) {
591
+ animation: none;
592
+ background-image: none;
593
+ -webkit-text-fill-color: currentColor;
594
+ }
595
+ }
596
+
597
+ @utility shimmer-once {
598
+ animation-iteration-count: 1;
599
+ }
600
+
601
+ @utility shimmer-reverse {
602
+ animation-direction: reverse;
603
+ }
604
+
605
+ @utility shimmer-none {
606
+ --shimmer-image: none;
607
+ --shimmer-text-fill: currentColor;
608
+ }
609
+
610
+ @utility shimmer-color-* {
611
+ --shimmer-color: --value(--color, [color]);
612
+ --shimmer-color: color-mix(
613
+ in oklch,
614
+ --value(--color, [color]) calc(--modifier(integer) * 1%),
615
+ transparent
616
+ );
617
+ }
618
+
619
+ @utility shimmer-duration-* {
620
+ --shimmer-duration: calc(--value(integer) * 1ms);
621
+ }
622
+
623
+ @utility shimmer-spread-* {
624
+ --shimmer-spread: calc(var(--spacing) * --value(integer));
625
+ --shimmer-spread: --value([length], [percentage]);
626
+ }
627
+
628
+ @utility shimmer-angle-* {
629
+ --shimmer-angle: calc(--value(integer) * 1deg);
630
+ }
631
+
632
+ /* Kept for backwards compatibility. Unlayered, so it still wins over other
633
+ utilities on a plain .shimmer element. Variants are handled in the utility. */
634
+ @media (prefers-reduced-motion: reduce) {
635
+ .shimmer {
636
+ animation: none;
637
+ background-image: none;
638
+ -webkit-text-fill-color: currentColor;
639
+ }
640
+ }
@@ -0,0 +1,36 @@
1
+ import { cn } from '../lib/utils'
2
+
3
+ /** A browser with a logo in `BrowserIcon`. */
4
+ export type BrowserName = 'chrome' | 'firefox' | 'opera' | 'safari'
5
+
6
+ // Path data from Simple Icons (https://simpleicons.org, CC0), copied rather
7
+ // than depended on: the package is a single 5 MB module. Microsoft's logos,
8
+ // Edge's among them, are no longer in Simple Icons.
9
+ const LOGOS: Record<BrowserName, { color: string; path: string }> = {
10
+ chrome: {
11
+ color: '#4285F4',
12
+ path: 'M12 0C8.21 0 4.831 1.757 2.632 4.501l3.953 6.848A5.454 5.454 0 0 1 12 6.545h10.691A12 12 0 0 0 12 0zM1.931 5.47A11.943 11.943 0 0 0 0 12c0 6.012 4.42 10.991 10.189 11.864l3.953-6.847a5.45 5.45 0 0 1-6.865-2.29zm13.342 2.166a5.446 5.446 0 0 1 1.45 7.09l.002.001h-.002l-5.344 9.257c.206.01.413.016.621.016 6.627 0 12-5.373 12-12 0-1.54-.29-3.011-.818-4.364zM12 16.364a4.364 4.364 0 1 1 0-8.728 4.364 4.364 0 0 1 0 8.728Z',
13
+ },
14
+ firefox: {
15
+ color: '#FF7139',
16
+ path: 'M8.824 7.287c.008 0 .004 0 0 0zm-2.8-1.4c.006 0 .003 0 0 0zm16.754 2.161c-.505-1.215-1.53-2.528-2.333-2.943.654 1.283 1.033 2.57 1.177 3.53l.002.02c-1.314-3.278-3.544-4.6-5.366-7.477-.091-.147-.184-.292-.273-.446a3.545 3.545 0 01-.13-.24 2.118 2.118 0 01-.172-.46.03.03 0 00-.027-.03.038.038 0 00-.021 0l-.006.001a.037.037 0 00-.01.005L15.624 0c-2.585 1.515-3.657 4.168-3.932 5.856a6.197 6.197 0 00-2.305.587.297.297 0 00-.147.37c.057.162.24.24.396.17a5.622 5.622 0 012.008-.523l.067-.005a5.847 5.847 0 011.957.222l.095.03a5.816 5.816 0 01.616.228c.08.036.16.073.238.112l.107.055a5.835 5.835 0 01.368.211 5.953 5.953 0 012.034 2.104c-.62-.437-1.733-.868-2.803-.681 4.183 2.09 3.06 9.292-2.737 9.02a5.164 5.164 0 01-1.513-.292 4.42 4.42 0 01-.538-.232c-1.42-.735-2.593-2.121-2.74-3.806 0 0 .537-2 3.845-2 .357 0 1.38-.998 1.398-1.287-.005-.095-2.029-.9-2.817-1.677-.422-.416-.622-.616-.8-.767a3.47 3.47 0 00-.301-.227 5.388 5.388 0 01-.032-2.842c-1.195.544-2.124 1.403-2.8 2.163h-.006c-.46-.584-.428-2.51-.402-2.913-.006-.025-.343.176-.389.206-.406.29-.787.616-1.136.974-.397.403-.76.839-1.085 1.303a9.816 9.816 0 00-1.562 3.52c-.003.013-.11.487-.19 1.073-.013.09-.026.181-.037.272a7.8 7.8 0 00-.069.667l-.002.034-.023.387-.001.06C.386 18.795 5.593 24 12.016 24c5.752 0 10.527-4.176 11.463-9.661.02-.149.035-.298.052-.448.232-1.994-.025-4.09-.753-5.844z',
17
+ },
18
+ opera: {
19
+ color: '#FF1B2D',
20
+ path: 'M8.051 5.238c-1.328 1.566-2.186 3.883-2.246 6.48v.564c.061 2.598.918 4.912 2.246 6.479 1.721 2.236 4.279 3.654 7.139 3.654 1.756 0 3.4-.537 4.807-1.471C17.879 22.846 15.074 24 12 24c-.192 0-.383-.004-.57-.014C5.064 23.689 0 18.436 0 12 0 5.371 5.373 0 12 0h.045c3.055.012 5.84 1.166 7.953 3.055-1.408-.93-3.051-1.471-4.81-1.471-2.858 0-5.417 1.42-7.14 3.654h.003zM24 12c0 3.556-1.545 6.748-4.002 8.945-3.078 1.5-5.946.451-6.896-.205 3.023-.664 5.307-4.32 5.307-8.74 0-4.422-2.283-8.075-5.307-8.74.949-.654 3.818-1.703 6.896-.205C22.455 5.25 24 8.445 24 12z',
21
+ },
22
+ safari: {
23
+ color: '#006CFF',
24
+ path: 'M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm-.004.953h.006c.063 0 .113.05.113.113v1.842c0 .063-.05.113-.113.113h-.006a.112.112 0 0 1-.113-.113V1.066c0-.063.05-.113.113-.113zm-.941.041c.056.001.104.046.11.104l.077.918a.112.112 0 0 1-.101.12h-.01a.11.11 0 0 1-.12-.1l-.08-.919a.112.112 0 0 1 .102-.12h.01l.012-.003zm1.892 0H12.965a.113.113 0 0 1 .103.121l-.08.92a.111.111 0 0 1-.12.102h-.009a.111.111 0 0 1-.101-.121l.078-.92a.112.112 0 0 1 .111-.102zm-2.838.123a.11.11 0 0 1 .106.092l.32 1.818c.01.06-.03.119-.09.13l-.01.001a.111.111 0 0 1-.128-.09l-.32-1.818a.111.111 0 0 1 .09-.129l.01-.002a.103.103 0 0 1 .022-.002zm3.784.002h.021l.008.002c.061.01.102.07.092.131l-.32 1.814c-.011.062-.07.101-.132.09h-.005a.113.113 0 0 1-.092-.13l.32-1.815a.111.111 0 0 1 .108-.092zm-4.715.203c.048.002.09.035.103.084l.239.893a.112.112 0 0 1-.079.139l-.005.001a.114.114 0 0 1-.14-.08l-.237-.894a.11.11 0 0 1 .078-.137l.006-.002a.123.123 0 0 1 .035-.004zm5.644 0a.11.11 0 0 1 .033.004l.006.002c.06.016.097.079.08.139l-.24.892a.112.112 0 0 1-.137.08l-.005-.002a.114.114 0 0 1-.08-.138l.24-.893a.112.112 0 0 1 .103-.084zm-6.562.285a.11.11 0 0 1 .107.073L9 3.42a.107.107 0 0 1-.064.139l-.012.005a.11.11 0 0 1-.14-.066L8.15 1.76a.11.11 0 0 1 .065-.14l.014-.005a.106.106 0 0 1 .03-.008zm7.469.002c.014 0 .028.001.042.006l.012.006c.057.02.087.082.067.139l-.633 1.738a.11.11 0 0 1-.14.066l-.013-.003A.11.11 0 0 1 15 3.42l.633-1.738a.108.108 0 0 1 .096-.073zm-8.352.366a.112.112 0 0 1 .105.064l.393.838a.112.112 0 0 1-.055.148l-.008.004a.11.11 0 0 1-.146-.054l-.395-.838a.112.112 0 0 1 .055-.149l.008-.004a.11.11 0 0 1 .043-.01zm9.246 0a.11.11 0 0 1 .043.01l.006.003a.11.11 0 0 1 .053.149l-.391.838a.112.112 0 0 1-.148.054l-.006-.002a.112.112 0 0 1-.055-.148l.393-.84a.112.112 0 0 1 .105-.064zm-10.092.44c.04-.002.08.018.102.056l.922 1.597a.113.113 0 0 1-.041.155l-.006.002a.113.113 0 0 1-.154-.041l-.922-1.598a.113.113 0 0 1 .04-.154l.007-.002a.11.11 0 0 1 .052-.016zm10.94.001c.018 0 .035.004.052.014l.004.002a.114.114 0 0 1 .041.156l-.923 1.596a.114.114 0 0 1-.157.04l-.004-.001a.112.112 0 0 1-.04-.155l.925-1.595a.113.113 0 0 1 .102-.057zM5.729 2.93a.11.11 0 0 1 .093.047l.532.753a.114.114 0 0 1-.028.159l-.004.002a.114.114 0 0 1-.158-.028l-.531-.752a.114.114 0 0 1 .027-.158l.006-.002a.113.113 0 0 1 .063-.021zm12.542 0a.11.11 0 0 1 .063.02l.006.003a.112.112 0 0 1 .027.156l-.531.756a.112.112 0 0 1-.156.028l-.006-.004a.112.112 0 0 1-.028-.157l.532-.755a.11.11 0 0 1 .093-.047zm.747.578a.11.11 0 0 1 .08.027l.006.004c.047.04.053.111.013.158L17.932 5.11a.11.11 0 0 1-.157.016l-.005-.006a.11.11 0 0 1-.014-.156l1.185-1.414a.114.114 0 0 1 .077-.041zM4.984 3.51a.11.11 0 0 1 .077.039L6.244 4.96a.112.112 0 0 1-.014.158l-.003.004a.112.112 0 0 1-.159-.014L4.883 3.697a.112.112 0 0 1 .013-.158l.006-.004a.111.111 0 0 1 .082-.025zm-.714.64c.027 0 .055.01.076.032l.658.66a.107.107 0 0 1 0 .152l-.01.01a.107.107 0 0 1-.152 0l-.66-.658a.11.11 0 0 1 0-.155l.01-.01a.111.111 0 0 1 .078-.03zm15.462 0c.028 0 .055.01.077.032l.007.007a.109.109 0 0 1 0 .155l-.658.66a.109.109 0 0 1-.154 0l-.008-.008a.109.109 0 0 1 0-.154l.658-.66a.11.11 0 0 1 .078-.032zm.707.66c.038 0 .071.02.092.075a.112.112 0 0 1-.023.117l-7.606 8.08c-3.084 2.024-6.149 4.04-9.222 6.05-.078.051-.17.082-.211-.028a.112.112 0 0 1 .023-.118l7.594-8.08c3.084-2.023 6.161-4.039 9.234-6.049a.247.247 0 0 1 .12-.046zm-16.824.045a.109.109 0 0 1 .08.026l1.416 1.187a.11.11 0 0 1 .014.157l-.006.005a.11.11 0 0 1-.156.014L3.549 5.057a.109.109 0 0 1-.014-.155l.006-.007a.108.108 0 0 1 .074-.04zm17.336.756c.036 0 .072.017.094.05l.004.003a.114.114 0 0 1-.028.158l-.753.53a.112.112 0 0 1-.157-.028l-.004-.004a.114.114 0 0 1 .028-.158l.754-.53a.113.113 0 0 1 .062-.02zm-17.904.002c.02 0 .042.007.06.02l.76.531c.05.035.06.103.026.152l-.006.01a.109.109 0 0 1-.153.026l-.76-.532a.109.109 0 0 1-.025-.152l.006-.01a.108.108 0 0 1 .092-.045zm-.512.803c.018 0 .036.006.053.016l1.596.923a.111.111 0 0 1 .04.153l-.003.006a.111.111 0 0 1-.153.04L2.473 6.63a.111.111 0 0 1-.041-.152l.004-.006a.11.11 0 0 1 .1-.055zm18.932 0a.11.11 0 0 1 .1.055l.001.004a.113.113 0 0 1-.04.154l-1.596.926a.113.113 0 0 1-.155-.041l-.002-.004a.113.113 0 0 1 .041-.155l1.596-.925a.115.115 0 0 1 .055-.014zm-19.373.846c.014 0 .029.003.043.01l.838.392a.11.11 0 0 1 .052.147l-.004.01a.11.11 0 0 1-.146.052l-.838-.393a.11.11 0 0 1-.053-.146l.004-.01a.109.109 0 0 1 .104-.062zm19.81.002a.11.11 0 0 1 .106.062l.002.008a.11.11 0 0 1-.053.146l-.838.393a.11.11 0 0 1-.146-.053l-.004-.008a.11.11 0 0 1 .052-.146l.838-.393a.11.11 0 0 1 .043-.01zm-20.183.88c.014 0 .028.001.043.006l1.732.631a.112.112 0 0 1 .067.145l-.002.006a.11.11 0 0 1-.143.066l-1.732-.63a.113.113 0 0 1-.069-.145l.002-.004a.115.115 0 0 1 .102-.074zm20.549 0a.113.113 0 0 1 .11.075l.003.004a.115.115 0 0 1-.069.146l-1.732.629a.112.112 0 0 1-.145-.066l-.001-.006a.113.113 0 0 1 .068-.145l1.732-.63a.11.11 0 0 1 .034-.006zm-20.836.909a.11.11 0 0 1 .033.004l.892.24c.06.016.096.077.08.137l-.002.007a.11.11 0 0 1-.136.079l-.895-.239a.113.113 0 0 1-.078-.138l.002-.006a.113.113 0 0 1 .104-.084zm21.13.002a.115.115 0 0 1 .106.084v.004a.112.112 0 0 1-.078.138l-.893.239a.112.112 0 0 1-.138-.079v-.005a.112.112 0 0 1 .078-.14l.892-.237a.11.11 0 0 1 .033-.004zm-21.335.93.023.001 1.814.323c.062.01.101.069.09.13v.006a.111.111 0 0 1-.13.09l-1.815-.322a.113.113 0 0 1-.092-.131l.002-.006a.11.11 0 0 1 .108-.092zm21.519.001h.022c.052.002.1.038.109.092v.006c.01.062-.03.12-.092.13l-1.814.321a.113.113 0 0 1-.131-.092v-.005a.113.113 0 0 1 .092-.131l1.814-.32zm-21.644.944h.011l.922.084a.11.11 0 0 1 .102.119l-.002.01a.11.11 0 0 1-.121.1l-.922-.083a.11.11 0 0 1-.1-.12v-.009a.111.111 0 0 1 .11-.101zm21.779.002h.012c.056 0 .106.043.11.101v.008a.111.111 0 0 1-.1.121l-.923.08a.111.111 0 0 1-.12-.101v-.008a.111.111 0 0 1 .1-.121l.92-.08zm-11.82.73L6.091 16.95c2.02-1.324 4.039-2.646 6.066-3.976l-1.095-1.31zm11.87.219c.063 0 .114.05.114.113v.004c0 .063-.05.113-.113.113l-1.844.004a.113.113 0 0 1-.113-.113v-.004c0-.063.05-.113.113-.113l1.844-.004zm-21.869.002h1.844c.062 0 .112.05.112.111v.008c0 .062-.05.111-.112.111H1.064a.111.111 0 0 1-.11-.111v-.008c0-.061.049-.111.11-.111zm.952.875h.011a.11.11 0 0 1 .11.101v.006a.111.111 0 0 1-.102.121l-.922.08a.11.11 0 0 1-.119-.101l-.002-.006a.111.111 0 0 1 .102-.121l.922-.08zm19.955 0h.011l.922.08a.11.11 0 0 1 .102.119v.008a.112.112 0 0 1-.121.101l-.922-.08a.11.11 0 0 1-.102-.119v-.008a.111.111 0 0 1 .11-.101zm-18.924.705c.053.001.098.04.107.094l.002.004c.011.061-.03.12-.092.13l-1.812.32a.113.113 0 0 1-.13-.091v-.004a.115.115 0 0 1 .09-.133l1.811-.318a.117.117 0 0 1 .024-.002zm17.902 0c.008 0 .016 0 .024.002l1.816.32c.061.011.1.07.09.131v.004a.113.113 0 0 1-.131.092l-1.816-.32a.112.112 0 0 1-.09-.131v-.004a.113.113 0 0 1 .107-.094zM2.332 14.477a.11.11 0 0 1 .104.082l.002.005c.016.06-.02.121-.08.137l-.891.24a.112.112 0 0 1-.137-.08l-.002-.006a.112.112 0 0 1 .08-.136l.89-.239a.112.112 0 0 1 .034-.003zm19.332 0c.011 0 .024 0 .035.003l.893.239c.06.016.096.077.08.136l-.002.006a.111.111 0 0 1-.137.078l-.894-.238a.111.111 0 0 1-.078-.137l.002-.005a.109.109 0 0 1 .101-.082zm-18.213.517a.11.11 0 0 1 .11.074l.002.004a.112.112 0 0 1-.067.145l-1.732.63a.113.113 0 0 1-.145-.068l-.002-.004a.113.113 0 0 1 .069-.144L3.418 15a.11.11 0 0 1 .033-.006zm17.086 0c.015 0 .029 0 .043.006l1.734.63a.111.111 0 0 1 .067.143l-.002.008a.111.111 0 0 1-.143.067l-1.734-.631a.111.111 0 0 1-.066-.143l.002-.008a.111.111 0 0 1 .1-.072zM2.92 16.117a.109.109 0 0 1 .103.063l.004.01a.108.108 0 0 1-.052.144l-.838.393a.11.11 0 0 1-.147-.055l-.004-.008a.11.11 0 0 1 .053-.146l.838-.391a.112.112 0 0 1 .043-.01zm18.158 0a.11.11 0 0 1 .043.01l.838.39c.056.027.08.093.055.149l-.002.004a.112.112 0 0 1-.149.055l-.838-.391a.112.112 0 0 1-.054-.148l.002-.004a.112.112 0 0 1 .105-.065zm-16.957.315c.04-.001.078.02.1.056l.004.004a.11.11 0 0 1-.041.153l-1.596.921a.113.113 0 0 1-.154-.04l-.002-.005a.113.113 0 0 1 .04-.154l1.596-.922a.109.109 0 0 1 .053-.013zm15.756 0c.018 0 .036.004.053.013l1.597.924a.11.11 0 0 1 .041.152l-.002.004a.11.11 0 0 1-.152.041l-1.598-.921a.113.113 0 0 1-.04-.155l.001-.002a.111.111 0 0 1 .1-.056zm.328 1.193a.11.11 0 0 1 .06.021l.758.534c.05.035.061.102.026.152l-.004.008a.111.111 0 0 1-.154.027l-.756-.535a.109.109 0 0 1-.028-.152l.006-.008a.11.11 0 0 1 .092-.047zm-16.412.002c.035 0 .072.016.094.047l.004.008a.109.109 0 0 1-.028.152l-.756.531a.108.108 0 0 1-.152-.025l-.006-.008a.109.109 0 0 1 .028-.152l.755-.534a.107.107 0 0 1 .061-.019zm15.162.102a.112.112 0 0 1 .082.025l1.414 1.187a.11.11 0 0 1 .014.157l-.004.004a.113.113 0 0 1-.158.013L18.89 17.93a.11.11 0 0 1-.014-.157l.004-.005a.108.108 0 0 1 .074-.04zm-12.812 1.12a.11.11 0 0 1 .08.026l.007.008a.11.11 0 0 1 .014.154L5.06 20.451a.11.11 0 0 1-.155.012l-.008-.006a.11.11 0 0 1-.013-.154l1.185-1.414a.11.11 0 0 1 .075-.04zm11.703 0c.032 0 .065.015.088.042l1.181 1.41c.04.048.035.12-.013.16l-.002.002a.114.114 0 0 1-.16-.014l-1.182-1.41a.114.114 0 0 1 .013-.16l.002-.002a.115.115 0 0 1 .073-.027zm-12.928.114c.027 0 .054.01.074.031l.014.012a.107.107 0 0 1 0 .15l-.662.66a.105.105 0 0 1-.149 0l-.011-.011a.105.105 0 0 1 0-.149l.66-.662a.105.105 0 0 1 .074-.031zm14.164 0c.027 0 .053.01.074.031l.66.662a.106.106 0 0 1 0 .15l-.011.012a.106.106 0 0 1-.15-.002l-.66-.66a.106.106 0 0 1 .001-.15l.01-.012a.108.108 0 0 1 .076-.031zm-11.627.797c.018 0 .034.006.05.015l.007.004a.11.11 0 0 1 .04.15l-.921 1.598a.11.11 0 0 1-.15.041l-.008-.004a.111.111 0 0 1-.04-.152l.922-1.596a.113.113 0 0 1 .1-.056zm9.088.002a.11.11 0 0 1 .1.054l.925 1.596a.113.113 0 0 1-.04.154h-.005a.11.11 0 0 1-.152-.039l-.926-1.595a.113.113 0 0 1 .041-.155l.004-.002a.108.108 0 0 1 .053-.013zm-10.285.324c.021 0 .043.008.062.021l.004.002c.051.036.063.106.028.157l-.53.755a.112.112 0 0 1-.156.028l-.004-.002a.112.112 0 0 1-.027-.156l.53-.756a.113.113 0 0 1 .093-.05zm11.484.002c.036 0 .072.015.094.047l.53.756c.035.05.023.12-.028.156l-.004.002a.112.112 0 0 1-.156-.028l-.53-.755a.112.112 0 0 1 .028-.157l.004-.002a.112.112 0 0 1 .062-.02zm-8.863.342a.11.11 0 0 1 .043.006l.012.005c.056.02.084.081.064.137l-.633 1.74a.105.105 0 0 1-.136.063l-.014-.004a.106.106 0 0 1-.065-.137l.633-1.74a.107.107 0 0 1 .096-.07zm6.232 0a.107.107 0 0 1 .106.07l.633 1.738a.107.107 0 0 1-.065.137l-.015.006a.107.107 0 0 1-.137-.065L15 20.578a.107.107 0 0 1 .064-.137l.014-.005a.117.117 0 0 1 .033-.006zm-4.695.41c.008 0 .014 0 .021.002l.006.002c.062.01.101.067.09.129l-.318 1.812a.113.113 0 0 1-.131.092l-.004-.002a.111.111 0 0 1-.092-.129l.32-1.812a.113.113 0 0 1 .108-.094zm3.146.002c.008-.002.015 0 .022 0a.111.111 0 0 1 .107.092l.32 1.812c.012.061-.03.12-.091.131l-.004.002a.113.113 0 0 1-.13-.092l-.321-1.812a.113.113 0 0 1 .092-.131l.005-.002zm-5.79.119a.11.11 0 0 1 .042.01l.004.002a.114.114 0 0 1 .055.15l-.393.834a.112.112 0 0 1-.148.055l-.004-.002a.112.112 0 0 1-.055-.149l.393-.836a.112.112 0 0 1 .105-.064zm8.458 0a.108.108 0 0 1 .104.062l.39.84a.11.11 0 0 1-.052.147l-.008.004a.11.11 0 0 1-.146-.055l-.391-.838a.11.11 0 0 1 .053-.146l.008-.004a.11.11 0 0 1 .042-.01zm-4.236.018H12c.063 0 .115.05.115.113l.002 1.84c0 .063-.05.113-.113.113h-.006a.113.113 0 0 1-.113-.113l-.004-1.838c0-.063.05-.115.113-.115zm-2.592.578c.011 0 .022 0 .034.004l.005.002c.06.016.095.077.079.136l-.24.893a.111.111 0 0 1-.137.078l-.006-.002a.111.111 0 0 1-.078-.137l.24-.89a.113.113 0 0 1 .103-.084zm5.196.002a.11.11 0 0 1 .103.082l.24.89a.11.11 0 0 1-.078.137l-.006.002a.11.11 0 0 1-.136-.078l-.24-.89a.11.11 0 0 1 .078-.138l.005-.002a.112.112 0 0 1 .034-.003zm-3.475.302h.01l.008.002c.061.006.107.06.101.121l-.08.92a.112.112 0 0 1-.121.102h-.008a.11.11 0 0 1-.1-.121l.08-.922a.111.111 0 0 1 .11-.102zm1.736 0h.02a.11.11 0 0 1 .107.102l.08.924a.11.11 0 0 1-.101.119l-.008.002a.11.11 0 0 1-.12-.102l-.08-.924a.112.112 0 0 1 .102-.12z',
25
+ },
26
+ }
27
+
28
+ /** A browser's logo in its brand colour. Decorative: pair it with a label. */
29
+ export function BrowserIcon({ browser, className }: { browser: BrowserName; className?: string }) {
30
+ const { color, path } = LOGOS[browser]
31
+ return (
32
+ <svg viewBox="0 0 24 24" fill={color} aria-hidden className={cn('size-4', className)}>
33
+ <path d={path} />
34
+ </svg>
35
+ )
36
+ }
@@ -149,8 +149,10 @@ export {
149
149
  SunIcon,
150
150
  SwatchBookIcon,
151
151
  TableIcon,
152
+ TabletIcon,
152
153
  TagIcon,
153
154
  TagsIcon,
155
+ TerminalIcon,
154
156
  TicketPercentIcon,
155
157
  Trash2Icon,
156
158
  TrashIcon,
@@ -175,3 +177,4 @@ export {
175
177
  XIcon,
176
178
  ZapIcon,
177
179
  } from 'lucide-react'
180
+ export { BrowserIcon, type BrowserName } from './browser-icon'
package/src/styles.css CHANGED
@@ -1,6 +1,6 @@
1
1
  @import "tailwindcss";
2
2
  @import "tw-animate-css";
3
- @import "shadcn/tailwind.css";
3
+ @import "./shadcn.css";
4
4
  @import "@fontsource-variable/geist";
5
5
  @import "@fontsource-variable/geist-mono";
6
6
 
package/src/ui/badge.tsx CHANGED
@@ -78,6 +78,9 @@ type StatusTone = 'success' | 'warning' | 'destructive' | 'info' | 'neutral'
78
78
  */
79
79
  const statusToneMap: Record<string, StatusTone> = {
80
80
  active: 'success',
81
+ // Staff accounts: locked after too many failed sign-ins, deactivated by an admin.
82
+ locked: 'warning',
83
+ deactivated: 'neutral',
81
84
  authorized: 'info',
82
85
  complete: 'success',
83
86
  completed: 'success',
@@ -1,4 +1,4 @@
1
- import { Command as CommandPrimitive } from 'cmdk'
1
+ import { Autocomplete as AutocompletePrimitive } from '@base-ui/react'
2
2
  import i18n from 'i18next'
3
3
  import * as React from 'react'
4
4
  import { cn } from '../lib/utils'
@@ -12,16 +12,67 @@ import {
12
12
  } from '../spree/icons'
13
13
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog'
14
14
 
15
- function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
15
+ const CommandContext = React.createContext<{
16
+ query: string
17
+ setQuery: (query: string) => void
18
+ shouldFilter: boolean
19
+ onInputChange: React.RefObject<((value: string) => void) | undefined>
20
+ }>({ query: '', setQuery: () => {}, shouldFilter: false, onInputChange: { current: undefined } })
21
+
22
+ /**
23
+ * A search field over a list of actions, on Base UI's Autocomplete rendered
24
+ * inline. The query lives on `CommandInput`. Rows whose `value` or text does not
25
+ * contain the query are hidden unless `shouldFilter` is false.
26
+ */
27
+ function Command({
28
+ className,
29
+ children,
30
+ shouldFilter = true,
31
+ ...props
32
+ }: React.ComponentProps<'div'> & { shouldFilter?: boolean }) {
33
+ const [query, setQuery] = React.useState('')
34
+ const onInputChange = React.useRef<((value: string) => void) | undefined>(undefined)
35
+
36
+ const context = React.useMemo(
37
+ () => ({ query, setQuery, shouldFilter, onInputChange }),
38
+ [query, shouldFilter],
39
+ )
40
+
16
41
  return (
17
- <CommandPrimitive
18
- data-slot="command"
19
- className={cn(
20
- 'flex size-full flex-col overflow-hidden rounded-2xl! bg-popover text-popover-foreground',
21
- className,
22
- )}
23
- {...props}
24
- />
42
+ <AutocompletePrimitive.Root
43
+ inline
44
+ open
45
+ autoHighlight="always"
46
+ keepHighlight
47
+ filter={null}
48
+ value={query}
49
+ onValueChange={(next, eventDetails) => {
50
+ // Choosing a row would otherwise write that row's value into the field.
51
+ if (eventDetails.reason === 'item-press') return
52
+ setQuery(next)
53
+ onInputChange.current?.(next)
54
+ }}
55
+ onOpenChange={(_open, eventDetails) => {
56
+ // The list is always shown, so Escape belongs to the dialog around it.
57
+ if (eventDetails.reason === 'escape-key') {
58
+ eventDetails.cancel()
59
+ eventDetails.allowPropagation()
60
+ }
61
+ }}
62
+ >
63
+ <CommandContext.Provider value={context}>
64
+ <div
65
+ data-slot="command"
66
+ className={cn(
67
+ 'flex size-full flex-col overflow-hidden rounded-2xl! bg-popover text-popover-foreground',
68
+ className,
69
+ )}
70
+ {...props}
71
+ >
72
+ {children}
73
+ </div>
74
+ </CommandContext.Provider>
75
+ </AutocompletePrimitive.Root>
25
76
  )
26
77
  }
27
78
 
@@ -72,18 +123,32 @@ function CommandDialog({
72
123
  function CommandInput({
73
124
  className,
74
125
  loading = false,
126
+ value,
127
+ onValueChange,
75
128
  ...props
76
- }: React.ComponentProps<typeof CommandPrimitive.Input> & {
129
+ }: Omit<AutocompletePrimitive.Input.Props, 'value'> & {
130
+ value?: string
131
+ onValueChange?: (value: string) => void
77
132
  /** Shows a spinner at the end of the field while results are in flight. */
78
133
  loading?: boolean
79
134
  }) {
135
+ const { query, setQuery, onInputChange } = React.useContext(CommandContext)
136
+ React.useLayoutEffect(() => {
137
+ onInputChange.current = onValueChange
138
+ }, [onInputChange, onValueChange])
139
+ // A controlled field shows only what its owner accepts: a reset from outside,
140
+ // or the previous text when an edit is rejected.
141
+ React.useLayoutEffect(() => {
142
+ if (value !== undefined && value !== query) setQuery(value)
143
+ }, [value, query, setQuery])
144
+
80
145
  return (
81
146
  <div
82
147
  data-slot="command-input-wrapper"
83
148
  className="flex h-14 shrink-0 items-center gap-3 border-b border-border px-5"
84
149
  >
85
150
  <SearchIcon className="size-5 shrink-0 text-muted-foreground" />
86
- <CommandPrimitive.Input
151
+ <AutocompletePrimitive.Input
87
152
  data-slot="command-input"
88
153
  aria-busy={loading || undefined}
89
154
  className={cn(
@@ -115,12 +180,12 @@ function CommandInput({
115
180
  * every keystroke. Pass `className="h-…"` to change it, or `h-auto max-h-…`
116
181
  * for the shrink-to-fit behaviour a short static menu wants.
117
182
  */
118
- function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
183
+ function CommandList({ className, ...props }: AutocompletePrimitive.List.Props) {
119
184
  return (
120
- <CommandPrimitive.List
185
+ <AutocompletePrimitive.List
121
186
  data-slot="command-list"
122
187
  className={cn(
123
- 'h-80 min-h-0 scroll-py-2 overflow-x-hidden overflow-y-auto p-2 outline-none',
188
+ 'group/command-list h-80 min-h-0 scroll-py-2 overflow-x-hidden overflow-y-auto p-2 outline-none',
124
189
  className,
125
190
  )}
126
191
  {...props}
@@ -128,14 +193,15 @@ function CommandList({ className, ...props }: React.ComponentProps<typeof Comman
128
193
  )
129
194
  }
130
195
 
131
- function CommandEmpty({
132
- className,
133
- ...props
134
- }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
196
+ /** The message for an empty list. Hidden while the list has any rows. */
197
+ function CommandEmpty({ className, ...props }: React.ComponentProps<'div'>) {
135
198
  return (
136
- <CommandPrimitive.Empty
199
+ <div
137
200
  data-slot="command-empty"
138
- className={cn('py-10 text-center text-sm text-muted-foreground', className)}
201
+ className={cn(
202
+ 'py-10 text-center text-sm text-muted-foreground group-has-[[data-slot=command-item]]/command-list:hidden',
203
+ className,
204
+ )}
139
205
  {...props}
140
206
  />
141
207
  )
@@ -143,26 +209,35 @@ function CommandEmpty({
143
209
 
144
210
  function CommandGroup({
145
211
  className,
212
+ heading,
213
+ children,
146
214
  ...props
147
- }: React.ComponentProps<typeof CommandPrimitive.Group>) {
215
+ }: AutocompletePrimitive.Group.Props & { heading?: React.ReactNode }) {
148
216
  return (
149
- <CommandPrimitive.Group
217
+ <AutocompletePrimitive.Group
150
218
  data-slot="command-group"
151
219
  className={cn(
152
- 'overflow-hidden text-foreground not-last:pb-2 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:pt-2 **:[[cmdk-group-heading]]:pb-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground',
220
+ 'overflow-hidden text-foreground not-last:pb-2 not-has-[[data-slot=command-item]]:hidden',
153
221
  className,
154
222
  )}
155
223
  {...props}
156
- />
224
+ >
225
+ {heading && (
226
+ <AutocompletePrimitive.GroupLabel
227
+ data-slot="command-group-heading"
228
+ className="px-3 pt-2 pb-1.5 text-xs font-medium text-muted-foreground"
229
+ >
230
+ {heading}
231
+ </AutocompletePrimitive.GroupLabel>
232
+ )}
233
+ {children}
234
+ </AutocompletePrimitive.Group>
157
235
  )
158
236
  }
159
237
 
160
- function CommandSeparator({
161
- className,
162
- ...props
163
- }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
238
+ function CommandSeparator({ className, ...props }: AutocompletePrimitive.Separator.Props) {
164
239
  return (
165
- <CommandPrimitive.Separator
240
+ <AutocompletePrimitive.Separator
166
241
  data-slot="command-separator"
167
242
  className={cn('-mx-2 my-1 h-px bg-border-subtle', className)}
168
243
  {...props}
@@ -170,23 +245,47 @@ function CommandSeparator({
170
245
  )
171
246
  }
172
247
 
248
+ /**
249
+ * One row. `onSelect` runs on click and on Enter while the row is highlighted,
250
+ * and receives the row's `value`.
251
+ */
173
252
  function CommandItem({
174
253
  className,
175
254
  children,
255
+ value,
256
+ keywords,
257
+ onSelect,
176
258
  ...props
177
- }: React.ComponentProps<typeof CommandPrimitive.Item>) {
259
+ }: Omit<AutocompletePrimitive.Item.Props, 'onClick' | 'onSelect' | 'value'> & {
260
+ value?: string
261
+ keywords?: string[]
262
+ onSelect?: (value: string) => void
263
+ }) {
264
+ const { query, shouldFilter } = React.useContext(CommandContext)
265
+ const text = value ?? (typeof children === 'string' ? children : '')
266
+ const needle = query.trim().toLowerCase()
267
+ if (
268
+ shouldFilter &&
269
+ needle &&
270
+ ![text, ...(keywords ?? [])].some((term) => term.toLowerCase().includes(needle))
271
+ ) {
272
+ return null
273
+ }
274
+
178
275
  return (
179
- <CommandPrimitive.Item
276
+ <AutocompletePrimitive.Item
180
277
  data-slot="command-item"
278
+ value={value}
279
+ onClick={() => onSelect?.(text)}
181
280
  className={cn(
182
- "group/command-item relative flex cursor-pointer items-center gap-3 rounded-md px-3 py-2.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-accent-strong data-selected:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-selected:*:[svg]:text-foreground",
281
+ "group/command-item relative flex cursor-pointer items-center gap-3 rounded-md px-3 py-2.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent-strong data-highlighted:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:*:[svg]:text-foreground",
183
282
  className,
184
283
  )}
185
284
  {...props}
186
285
  >
187
286
  {children}
188
287
  <CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
189
- </CommandPrimitive.Item>
288
+ </AutocompletePrimitive.Item>
190
289
  )
191
290
  }
192
291
 
@@ -195,7 +294,7 @@ function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>)
195
294
  <span
196
295
  data-slot="command-shortcut"
197
296
  className={cn(
198
- 'ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground',
297
+ 'ml-auto text-xs tracking-widest text-muted-foreground group-data-highlighted/command-item:text-foreground',
199
298
  className,
200
299
  )}
201
300
  {...props}