@filipgutica/ui 1.16.0 → 1.17.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -16,7 +16,8 @@
16
16
  min-width: 0;
17
17
  max-width: 100%;
18
18
  overflow: hidden;
19
- border: 1px solid var(--color-border-subtle);
19
+ border: 1px solid transparent;
20
+ box-shadow: var(--shadow-raise);
20
21
  border-radius: var(--radius-panel);
21
22
  background: var(--color-code);
22
23
  color: var(--color-code-text);
@@ -239,12 +240,15 @@
239
240
  }
240
241
 
241
242
  .fg-button[data-variant="secondary"] {
242
- border-color: var(--color-border);
243
243
  color: var(--color-text);
244
- background: transparent;
244
+ background: var(--color-surface-muted);
245
+ }
246
+
247
+ .fg-button[data-variant="secondary"]:hover:not(:disabled) {
248
+ color: var(--color-text);
249
+ background: color-mix(in srgb, var(--color-text) 8%, var(--color-surface-muted));
245
250
  }
246
251
 
247
- .fg-button[data-variant="secondary"]:hover:not(:disabled),
248
252
  .fg-button[data-variant="ghost"]:hover:not(:disabled) {
249
253
  color: var(--color-text);
250
254
  background: var(--color-row-hover);
@@ -288,10 +292,9 @@
288
292
  .fg-input,
289
293
  .fg-select {
290
294
  width: 100%;
291
- border-color: var(--color-input-border);
292
295
  color: var(--color-input-text);
293
- background: var(--color-input-bg);
294
- box-shadow: none;
296
+ background: var(--color-well);
297
+ box-shadow: var(--shadow-sink);
295
298
  line-height: 1.35;
296
299
  }
297
300
 
@@ -348,7 +351,7 @@
348
351
  min-height: var(--control-sm);
349
352
  gap: var(--space-2);
350
353
  padding: var(--space-2) var(--space-3);
351
- border-radius: var(--radius-control);
354
+ border-radius: calc(var(--radius-panel) - var(--space-1));
352
355
  color: var(--color-text);
353
356
  background: transparent;
354
357
  cursor: pointer;
@@ -419,7 +422,7 @@
419
422
  margin-top: 0.1rem;
420
423
  place-items: center;
421
424
  border: 1px solid var(--color-input-border);
422
- border-radius: var(--radius-control);
425
+ border-radius: var(--radius-inline);
423
426
  color: var(--color-button-text);
424
427
  background: var(--color-input-bg);
425
428
  box-shadow: var(--shadow-control);
@@ -494,25 +497,24 @@
494
497
  min-width: 0;
495
498
  padding: var(--space-3);
496
499
  overflow: hidden;
497
- border: 1px solid var(--color-border-subtle);
500
+ border: 1px solid transparent;
498
501
  border-radius: var(--radius-panel);
499
502
  color: var(--color-text);
500
503
  background: var(--color-surface-raised);
501
- box-shadow: none;
504
+ box-shadow: var(--shadow-raise);
502
505
  text-align: left;
503
506
  cursor: pointer;
504
507
  transition: transform var(--duration-fast) var(--ease-out);
505
508
  }
506
509
 
507
- .fg-radio-card:hover:not([data-disabled]) {
508
- border-color: var(--color-border);
510
+ .fg-radio-card:hover:not([data-disabled], [data-state="checked"]) {
511
+ background: color-mix(in srgb, var(--color-surface-muted) 45%, var(--color-surface-raised));
509
512
  }
510
513
 
511
- /* Checked choices use the accent, as the checkbox does; blue stays for links and focus. */
514
+ /* Depth and a check badge distinguish the choice without an accent ring. */
512
515
  .fg-radio-card[data-state="checked"] {
513
- border-color: var(--color-accent);
514
- outline: 1px solid var(--color-accent);
515
- outline-offset: -2px;
516
+ background: var(--color-well);
517
+ box-shadow: var(--shadow-sink);
516
518
  }
517
519
 
518
520
  .fg-radio-card[data-disabled] {
@@ -541,14 +543,26 @@
541
543
  }
542
544
 
543
545
  .fg-radio-card[data-state="checked"] .fg-radio-card__indicator {
544
- border: 0.3rem solid var(--color-accent);
546
+ border-color: var(--color-accent);
547
+ color: var(--color-accent-text);
548
+ background: var(--color-accent);
549
+ }
550
+
551
+ .fg-radio-card[data-state="checked"] .fg-radio-card__indicator::after {
552
+ content: "";
553
+ position: absolute;
554
+ inset: 2px 4px 4px;
555
+ border-right: 2px solid currentColor;
556
+ border-bottom: 2px solid currentColor;
557
+ transform: rotate(45deg);
545
558
  }
546
559
 
547
560
  .fg-badge {
548
561
  display: inline-flex;
549
562
  align-items: center;
550
563
  width: fit-content;
551
- border: 1px solid var(--color-border-subtle);
564
+ gap: 6px;
565
+ border: 1px solid transparent;
552
566
  border-radius: var(--radius-pill);
553
567
  padding: 0.18rem 0.56rem;
554
568
  color: var(--color-muted);
@@ -558,41 +572,53 @@
558
572
  line-height: 1.25;
559
573
  }
560
574
 
561
- /* A tone badge is a tinted pill whose text carries the status colour, darkened toward the text
562
- * colour so it reads at small sizes on the tint. */
563
- .fg-badge[data-tone]:not([data-tone="neutral"]) { border-color: transparent; }
564
- .fg-badge[data-tone="info"] { color: color-mix(in srgb, var(--color-info) 80%, var(--color-text)); background: var(--color-info-surface); }
565
- .fg-badge[data-tone="success"] { color: color-mix(in srgb, var(--color-success) 80%, var(--color-text)); background: var(--color-success-surface); }
566
- .fg-badge[data-tone="warning"] { color: color-mix(in srgb, var(--color-warning) 80%, var(--color-text)); background: var(--color-warning-surface); }
575
+ /* Status tint and text use the same hue; neutral metadata has no status dot. */
576
+ .fg-badge[data-tone]:not([data-tone="neutral"]) {
577
+ color: color-mix(in srgb, var(--fg-badge-tone) 75%, var(--color-text));
578
+ background: color-mix(in srgb, var(--fg-badge-tone) 22%, var(--color-surface-raised));
579
+ }
580
+ :root.dark .fg-badge[data-tone]:not([data-tone="neutral"]) {
581
+ color: color-mix(in srgb, var(--fg-badge-tone) 85%, var(--color-text));
582
+ }
583
+ .fg-badge[data-tone]:not([data-tone="neutral"])::before {
584
+ content: "";
585
+ flex: none;
586
+ width: 6px;
587
+ height: 6px;
588
+ border-radius: 50%;
589
+ background: currentColor;
590
+ }
591
+ .fg-badge[data-tone="info"] { --fg-badge-tone: var(--color-info); }
592
+ .fg-badge[data-tone="success"] { --fg-badge-tone: var(--color-success); }
593
+ .fg-badge[data-tone="warning"] { --fg-badge-tone: var(--color-warning); }
567
594
  .fg-badge[data-tone="danger"],
568
- .fg-badge[data-tone="error"] { color: color-mix(in srgb, var(--color-error) 80%, var(--color-text)); background: var(--color-error-surface); }
595
+ .fg-badge[data-tone="error"] { --fg-badge-tone: var(--color-error); }
569
596
 
570
597
  .fg-alert {
571
598
  display: flex;
572
599
  align-items: flex-start;
573
600
  gap: var(--space-3);
574
601
  padding: var(--space-3) var(--space-4);
575
- border: 1px solid var(--color-border-subtle);
602
+ border: 1px solid transparent;
576
603
  border-radius: var(--radius-panel);
577
604
  color: var(--color-text);
578
605
  background: var(--color-surface);
579
606
  }
580
607
 
581
- .fg-alert[data-tone="info"] { border-color: color-mix(in srgb, var(--color-info) 35%, var(--color-border-subtle)); background: var(--color-info-surface); }
582
- .fg-alert[data-tone="success"] { border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border-subtle)); background: var(--color-success-surface); }
583
- .fg-alert[data-tone="warning"] { border-color: color-mix(in srgb, var(--color-warning) 35%, var(--color-border-subtle)); background: var(--color-warning-surface); }
608
+ .fg-alert[data-tone="info"] { background: var(--color-info-surface); }
609
+ .fg-alert[data-tone="success"] { background: var(--color-success-surface); }
610
+ .fg-alert[data-tone="warning"] { background: var(--color-warning-surface); }
584
611
  .fg-alert[data-tone="danger"],
585
612
  .fg-alert[data-tone="error"] {
586
- border-color: color-mix(in srgb, var(--color-error) 35%, var(--color-border-subtle));
587
613
  color: var(--color-status-text, var(--color-error));
588
614
  background: var(--color-error-surface);
589
615
  }
590
616
 
591
617
  .fg-surface {
592
- border: 1px solid var(--color-border-subtle);
618
+ border: 1px solid transparent;
593
619
  border-radius: var(--radius-panel);
594
620
  background: var(--color-surface-raised);
595
- box-shadow: none;
621
+ box-shadow: var(--shadow-raise);
596
622
  }
597
623
 
598
624
  .fg-surface[data-padding="compact"] { padding: var(--space-3); }
@@ -601,11 +627,11 @@
601
627
  .fg-card {
602
628
  min-width: 0;
603
629
  overflow: hidden;
604
- border: 1px solid var(--color-border-subtle);
630
+ border: 1px solid transparent;
605
631
  border-radius: var(--radius-panel);
606
632
  color: var(--color-text);
607
633
  background: var(--color-surface-raised);
608
- box-shadow: none;
634
+ box-shadow: var(--shadow-raise);
609
635
  }
610
636
 
611
637
  .fg-card__header {
@@ -725,15 +751,16 @@
725
751
  /* Segmented control: one pressed choice on a muted track, so it reads as a raised thumb in both themes. */
726
752
  .fg-segmented {
727
753
  --fg-segmented-item: var(--control-md);
728
- /* The thumb is lighter than the track in both themes; the dark raised surface is darker than the track. */
729
- --fg-segmented-thumb: light-dark(var(--color-row-selected), color-mix(in srgb, var(--color-text) 10%, var(--color-surface-muted)));
754
+ --fg-segmented-thumb: var(--color-surface-raised);
730
755
  display: inline-flex;
731
756
  flex: none;
732
757
  /* The fill alone marks the track. The transparent border keeps its size, and forced colors draw it. */
733
758
  border: 1px solid transparent;
734
759
  border-radius: var(--radius-control);
735
- background: var(--color-surface-muted);
760
+ background: var(--color-well);
761
+ box-shadow: var(--shadow-sink);
736
762
  }
763
+ :root.dark .fg-segmented { --fg-segmented-thumb: color-mix(in srgb, var(--color-surface-raised) 88%, white); }
737
764
  .fg-segmented[data-size="sm"] { --fg-segmented-item: var(--control-sm); font-size: var(--text-sm); }
738
765
  .fg-segmented[data-size="lg"] { --fg-segmented-item: var(--control-lg); font-size: var(--text-lg); }
739
766
  .fg-segmented__item {
@@ -754,16 +781,16 @@
754
781
  }
755
782
  .fg-segmented[data-icon-only="true"] .fg-segmented__item { width: var(--fg-segmented-item); padding: 0; }
756
783
  /* The thumb is inset inside each full-size button, so it floats on the track and the hit target keeps its size. */
757
- .fg-segmented__item::before { content: ""; position: absolute; inset: 2px; z-index: -1; border-radius: var(--radius-inline); }
784
+ .fg-segmented__item::before { content: ""; position: absolute; inset: 2px; z-index: -1; border-radius: var(--radius-control); }
758
785
  .fg-segmented__item:hover:not(:disabled, [aria-pressed="true"]) { color: var(--color-text); }
759
786
  .fg-segmented__item:hover:not(:disabled, [aria-pressed="true"])::before { background: color-mix(in srgb, var(--fg-segmented-thumb) 45%, transparent); }
760
- /* The ring gives the pressed choice 3:1 against the track in both themes (WCAG 1.4.11). */
761
- .fg-segmented__item[aria-pressed="true"] { color: var(--color-text); }
787
+ /* The accent label or icon marks the pressed choice at 3:1 against its thumb. */
788
+ .fg-segmented__item[aria-pressed="true"] { color: var(--color-accent); }
762
789
  /* Drawn inside the button, the ring hugs the thumb instead of reaching the next choice. */
763
790
  .fg-segmented .fg-segmented__item:focus-visible { outline-offset: -2px; }
764
791
  .fg-segmented__item[aria-pressed="true"]::before {
765
792
  background: var(--fg-segmented-thumb);
766
- box-shadow: inset 0 0 0 1px var(--color-border);
793
+ box-shadow: var(--shadow-raise);
767
794
  }
768
795
 
769
796
  .fg-switch { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
@@ -801,7 +828,8 @@
801
828
  height: 1rem;
802
829
  border: 1px solid var(--color-border);
803
830
  border-radius: var(--radius-pill);
804
- background: var(--color-surface-muted);
831
+ background: var(--color-well);
832
+ box-shadow: var(--shadow-sink);
805
833
  transition: background-color var(--duration) var(--ease-out), border-color var(--duration) var(--ease-out);
806
834
  }
807
835
 
@@ -813,7 +841,8 @@
813
841
  width: 0.625rem;
814
842
  height: 0.625rem;
815
843
  border-radius: var(--radius-pill);
816
- background: var(--color-border);
844
+ background: var(--color-surface-raised);
845
+ box-shadow: var(--shadow-raise);
817
846
  content: "";
818
847
  transition: transform var(--duration) var(--ease-out), background-color var(--duration) var(--ease-out);
819
848
  }
@@ -990,8 +1019,22 @@
990
1019
  .fg-toggle-button:focus-visible,
991
1020
  .fg-segmented__item:focus-visible,
992
1021
  .fg-switch__control:focus-visible .fg-switch__track {
993
- outline: 2px solid var(--color-focus);
1022
+ outline: 2px solid color-mix(in srgb, var(--color-accent) 80%, transparent);
994
1023
  outline-offset: 2px;
1024
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1025
+ }
1026
+
1027
+ .fg-input:focus-visible,
1028
+ .fg-select:focus-visible,
1029
+ .fg-radio-card[data-state="checked"]:focus-visible,
1030
+ .fg-switch__control:focus-visible .fg-switch__track {
1031
+ box-shadow: var(--shadow-sink), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1032
+ }
1033
+ .fg-dialog__content:focus-visible {
1034
+ box-shadow: var(--shadow-dialog), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1035
+ }
1036
+ .fg-radio-card:not([data-state="checked"]):focus-visible {
1037
+ box-shadow: var(--shadow-raise), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
995
1038
  }
996
1039
 
997
1040
  /* The control is a larger transparent hit area, so the ring follows the visible track instead. */
@@ -1025,6 +1068,12 @@
1025
1068
  }
1026
1069
 
1027
1070
  @media (forced-colors: active) {
1071
+ .fg-radio-card[data-state="checked"] .fg-radio-card__indicator {
1072
+ forced-color-adjust: none;
1073
+ border-color: Highlight;
1074
+ color: HighlightText;
1075
+ background: Highlight;
1076
+ }
1028
1077
  /* Forced colors drop the fill and the ring, so the pressed choice takes the system highlight. */
1029
1078
  .fg-segmented__item[aria-pressed="true"] { forced-color-adjust: none; color: HighlightText; }
1030
1079
  .fg-segmented__item[aria-pressed="true"]::before { forced-color-adjust: none; background: Highlight; }
package/src/site.css CHANGED
@@ -207,7 +207,7 @@
207
207
  min-height: var(--row);
208
208
  align-items: center;
209
209
  padding-inline: var(--space-3);
210
- border-radius: var(--radius-control);
210
+ border-radius: var(--radius-inline);
211
211
  color: var(--color-muted);
212
212
  font-family: var(--font-mono);
213
213
  font-size: var(--text-sm);
@@ -418,7 +418,7 @@
418
418
  .fg-capture-viewer .fg-capture__screen {
419
419
  max-height: calc(100dvh - var(--space-16) * 3);
420
420
  overflow: auto;
421
- border-radius: var(--radius-control);
421
+ border-radius: var(--radius-inline);
422
422
  }
423
423
 
424
424
  /* Command table (UiCommandList). Wide commands scroll inside it on larger
@@ -479,23 +479,37 @@
479
479
  * rule runs down the leading column to the next step. */
480
480
  .fg-site-steps > li {
481
481
  position: relative;
482
- display: grid;
483
- grid-template-columns: var(--space-8) minmax(0, 1fr);
484
- row-gap: var(--space-2);
482
+ display: flow-root;
483
+ min-width: 0;
484
+ padding-inline-start: var(--space-8);
485
485
  counter-increment: fg-site-step;
486
486
  }
487
- .fg-site-steps > li > * { grid-column: 2; }
488
- .fg-site-steps > li::before {
487
+ /* Keep text and inline elements in normal flow; only the number occupies the gutter. */
488
+ .fg-site-steps > li > * + * { margin-block-start: var(--space-2); }
489
+ .fg-site-steps > li::before,
490
+ .fg-site-steps > li > h3:first-child::before {
489
491
  content: counter(fg-site-step);
490
- grid-area: 1 / 1;
491
- align-self: baseline;
492
492
  color: var(--color-project);
493
493
  font-family: var(--font-mono);
494
494
  font-size: var(--text-sm);
495
495
  font-weight: var(--weight-semibold);
496
496
  font-variant-numeric: tabular-nums;
497
+ line-height: var(--leading);
498
+ }
499
+ .fg-site-steps > li::before {
500
+ position: absolute;
501
+ inset-inline-start: 0;
502
+ top: 0;
503
+ }
504
+ /* A titled step keeps the number on the heading's baseline and preserves its line box. */
505
+ .fg-site-steps > li:has(> h3:first-child)::before { content: none; }
506
+ .fg-site-steps > li > h3:first-child::before {
507
+ display: inline-block;
508
+ width: var(--space-8);
509
+ margin-inline-start: calc(-1 * var(--space-8));
510
+ /* A unit line height keeps the number from making a wrapped title's line boxes taller. */
511
+ line-height: 1;
497
512
  }
498
- .fg-site-steps > li > :first-child { align-self: baseline; }
499
513
  .fg-site-steps > li:not(:last-child)::after {
500
514
  content: '';
501
515
  position: absolute;
package/src/theme.css CHANGED
@@ -37,6 +37,7 @@
37
37
  --color-surface: #e9ebed;
38
38
  --color-surface-raised: #fbfbfc;
39
39
  --color-surface-muted: #e2e5e8;
40
+ --color-well: var(--color-surface-muted);
40
41
  --color-text: #101315;
41
42
  --color-muted: #50575e;
42
43
  --color-link: #0b5f7c;
@@ -78,15 +79,17 @@
78
79
  --color-warning-surface: #e7e2de;
79
80
  --color-success-surface: #d9e5e1;
80
81
  --color-info-surface: #d9e3f2;
81
- --radius-inline: 3px;
82
- --radius-control: 4px;
83
- --radius-panel: 6px;
82
+ --radius-inline: 6px;
83
+ --radius-control: 999px;
84
+ --radius-panel: 16px;
84
85
  --radius-pill: 999px;
85
86
  --duration-fast: 120ms;
86
87
  --duration: 180ms;
87
88
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
88
89
  --shadow-control: none;
89
90
  --shadow-dialog: 0 8px 24px rgb(16 16 24 / 12%), 0 0 0 1px var(--color-border-subtle);
91
+ --shadow-raise: 2px 2px 6px rgb(130 142 158 / 35%), -2px -2px 6px rgb(255 255 255 / 80%);
92
+ --shadow-sink: inset 1.2px 1.2px 3.6px rgb(130 142 158 / 35%), inset -1.2px -1.2px 3.6px rgb(255 255 255 / 80%);
90
93
 
91
94
  --color-project: #4b45d1;
92
95
  --color-accent-text: var(--color-bg);
@@ -106,6 +109,7 @@
106
109
  --color-surface: #13171a;
107
110
  --color-surface-raised: #181d21;
108
111
  --color-surface-muted: #20262b;
112
+ --color-well: color-mix(in srgb, var(--color-bg) 87.5%, black);
109
113
  --color-text: #e6e9eb;
110
114
  --color-muted: #9aa3ab;
111
115
  --color-link: #67c1df;
@@ -144,6 +148,8 @@
144
148
  --color-shadow: rgb(0 0 0 / 32%);
145
149
  --color-overlay: rgb(0 0 0 / 62%);
146
150
  --shadow-dialog: 0 8px 24px rgb(0 0 0 / 45%), 0 0 0 1px var(--color-border-subtle);
151
+ --shadow-raise: 2px 2px 6px rgb(0 0 0 / 50%), -2px -2px 6px rgb(255 255 255 / 5%);
152
+ --shadow-sink: inset 1.2px 1.2px 3.6px rgb(0 0 0 / 55%), inset -1.2px -1.2px 3.6px rgb(255 255 255 / 5%);
147
153
 
148
154
  --color-project: #a5a1ff;
149
155
  --color-accent-text: var(--color-bg);