@fluixi-ui/tokens 0.4.0-alpha.1 → 0.4.0-alpha.2

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.
@@ -1,7 +1,85 @@
1
1
  @layer flx.components {
2
- /* Accordion */
3
- :where([role='region']) {
4
- padding-bottom: var(--flx-ui-space-3);
2
+ /* ----------------------------------------------------------------------------
3
+ * Accordion — a bordered stack of collapsible sections. The trigger is a full
4
+ * width header row with a chevron that rotates on open; content reveals with a
5
+ * gentle slide.
6
+ * ------------------------------------------------------------------------- */
7
+ :where([data-accordion]) {
8
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
9
+ border-radius: var(--flx-ui-radius-md);
10
+ overflow: hidden;
11
+ }
12
+ :where([data-accordion-item]:not(:last-child)) {
13
+ border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
14
+ }
15
+
16
+ :where([data-accordion-trigger]) {
17
+ box-sizing: border-box;
18
+ width: 100%;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ gap: var(--flx-ui-space-3);
23
+ padding: var(--flx-ui-space-3) var(--flx-ui-space-4);
24
+ border: none;
25
+ background: none;
26
+ font: inherit;
27
+ font-weight: var(--flx-ui-font-weight-medium);
28
+ color: var(--flx-ui-color-fg);
29
+ text-align: left;
30
+ cursor: pointer;
31
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
32
+ }
33
+ :where([data-accordion-trigger]:hover:not([data-disabled])) {
34
+ background: var(--flx-ui-color-bg-subtle);
35
+ }
36
+ :where([data-accordion-trigger]:focus-visible) {
37
+ outline: 2px solid var(--flx-ui-color-focus-ring);
38
+ outline-offset: -2px;
39
+ }
40
+ :where([data-accordion-trigger][data-disabled]) {
41
+ opacity: var(--flx-ui-opacity-disabled);
42
+ cursor: not-allowed;
43
+ }
44
+ /* Chevron: a crisp masked glyph, centred, that flips 180° when open. */
45
+ :where([data-accordion-trigger])::after {
46
+ content: '';
47
+ width: 1em;
48
+ height: 1em;
49
+ flex: none;
50
+ background: currentColor;
51
+ opacity: 0.55;
52
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
53
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
54
+ transition: transform var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
55
+ }
56
+ :where([data-accordion-trigger][data-state='open'])::after {
57
+ transform: rotate(180deg);
58
+ }
59
+
60
+ :where([data-accordion-content]) {
61
+ padding: 0 var(--flx-ui-space-4) var(--flx-ui-space-3);
5
62
  color: var(--flx-ui-color-fg-muted);
6
63
  }
64
+ :where([data-accordion-content][data-state='open']) {
65
+ animation: flx-ui-accordion-in var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
66
+ }
67
+
68
+ @keyframes flx-ui-accordion-in {
69
+ from {
70
+ opacity: 0;
71
+ transform: translateY(-4px);
72
+ }
73
+ to {
74
+ opacity: 1;
75
+ transform: none;
76
+ }
77
+ }
78
+ @media (prefers-reduced-motion: reduce) {
79
+ :where([data-accordion-trigger])::after,
80
+ :where([data-accordion-content][data-state='open']) {
81
+ transition: none;
82
+ animation: none;
83
+ }
84
+ }
7
85
  }
@@ -1,42 +1,71 @@
1
1
  @layer flx.components {
2
2
  /* ----------------------------------------------------------------------------
3
- * Alert
3
+ * Alert — a tinted callout with an optional leading icon. The icon column spans
4
+ * the title + description rows; without an icon the content sits flush left.
4
5
  * ------------------------------------------------------------------------- */
5
6
  :where([data-alert]) {
6
- display: flex;
7
- flex-direction: column;
8
- align-items: flex-start;
9
- gap: var(--flx-ui-space-1);
10
- text-align: left;
11
- padding-block: var(--flx-ui-space-4);
12
- padding-inline: var(--flx-ui-space-4);
7
+ display: grid;
8
+ grid-template-columns: auto minmax(0, 1fr);
9
+ row-gap: var(--flx-ui-space-1);
10
+ align-items: start;
11
+ padding: var(--flx-ui-space-3) var(--flx-ui-space-4);
13
12
  border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
14
- border-left-width: 3px;
15
13
  border-radius: var(--flx-ui-radius-md);
16
14
  background: var(--flx-ui-color-bg-subtle);
17
15
  color: var(--flx-ui-color-fg);
18
16
  }
19
- :where([data-alert][data-variant='info']) {
20
- border-color: var(--flx-ui-color-primary);
21
- background: var(--flx-ui-color-primary-subtle);
17
+ :where([data-alert]) > * {
18
+ grid-column: 2;
19
+ min-width: 0;
22
20
  }
23
- :where([data-alert][data-variant='success']) {
24
- border-color: var(--flx-ui-color-success);
25
- background: var(--flx-ui-color-success-subtle);
21
+ :where([data-alert-icon]) {
22
+ grid-column: 1;
23
+ grid-row: 1 / -1;
24
+ margin-right: var(--flx-ui-space-3);
25
+ display: inline-flex;
26
+ flex: none;
27
+ color: var(--flx-ui-color-fg-muted);
26
28
  }
27
- :where([data-alert][data-variant='warning']) {
28
- border-color: var(--flx-ui-color-warning);
29
- background: var(--flx-ui-color-warning-subtle);
30
- }
31
- :where([data-alert][data-variant='danger']) {
32
- border-color: var(--flx-ui-color-danger);
33
- background: var(--flx-ui-color-danger-subtle);
29
+ :where([data-alert-icon]) > svg {
30
+ width: 1.15em;
31
+ height: 1.15em;
34
32
  }
35
33
  :where([data-alert-title]) {
36
34
  font-weight: var(--flx-ui-font-weight-semibold);
35
+ line-height: var(--flx-ui-line-height-tight);
37
36
  }
38
37
  :where([data-alert-description]) {
39
38
  font-size: var(--flx-ui-font-size-sm);
40
- opacity: 0.9;
39
+ color: var(--flx-ui-color-fg-muted);
40
+ }
41
+
42
+ /* Variants tint the surface, border and icon with the matching semantic hue. */
43
+ :where([data-alert][data-alert-variant='info']) {
44
+ border-color: color-mix(in oklab, var(--flx-ui-color-primary) 35%, var(--flx-ui-color-border));
45
+ background: var(--flx-ui-color-primary-subtle);
46
+ }
47
+ :where([data-alert][data-alert-variant='info']) :where([data-alert-icon]) {
48
+ color: var(--flx-ui-color-primary);
49
+ }
50
+ :where([data-alert][data-alert-variant='success']) {
51
+ border-color: color-mix(in oklab, var(--flx-ui-color-success) 35%, var(--flx-ui-color-border));
52
+ background: var(--flx-ui-color-success-subtle);
53
+ }
54
+ :where([data-alert][data-alert-variant='success']) :where([data-alert-icon]) {
55
+ color: var(--flx-ui-color-success);
56
+ }
57
+ :where([data-alert][data-alert-variant='warning']) {
58
+ border-color: color-mix(in oklab, var(--flx-ui-color-warning) 35%, var(--flx-ui-color-border));
59
+ background: var(--flx-ui-color-warning-subtle);
60
+ }
61
+ :where([data-alert][data-alert-variant='warning']) :where([data-alert-icon]) {
62
+ color: var(--flx-ui-color-warning);
63
+ }
64
+ :where([data-alert][data-alert-variant='danger']) {
65
+ border-color: color-mix(in oklab, var(--flx-ui-color-danger) 35%, var(--flx-ui-color-border));
66
+ background: var(--flx-ui-color-danger-subtle);
67
+ }
68
+ :where([data-alert][data-alert-variant='danger']) :where([data-alert-icon]) {
69
+ color: var(--flx-ui-color-danger);
41
70
  }
42
71
  }
@@ -2,6 +2,7 @@
2
2
  /* Badge */
3
3
  :where([data-variant].flx-ui-badge, .flx-ui-badge[data-variant]) {
4
4
  height: auto;
5
+ min-width: 0;
5
6
  padding: 2px var(--flx-ui-space-2);
6
7
  border-radius: var(--flx-ui-radius-full);
7
8
  font-size: var(--flx-ui-font-size-xs);
package/dist/css/base.css CHANGED
@@ -6,5 +6,7 @@
6
6
  :where(:root) {
7
7
  --flx-ui-ctl-ring: 0 0 0 2px var(--flx-ui-color-bg), 0 0 0 4px var(--flx-ui-color-focus-ring);
8
8
  --flx-ui-ctl-font: var(--flx-ui-font-weight-medium);
9
+ /* Shared chevron glyph (mask source) — colour comes from the consuming rule. */
10
+ --flx-ui-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
9
11
  }
10
12
  }
@@ -74,13 +74,13 @@
74
74
  align-items: flex-start;
75
75
  justify-content: space-between;
76
76
  gap: var(--flx-ui-space-3);
77
- padding: var(--flx-ui-space-2) var(--flx-ui-space-5) var(--flx-ui-space-4);
77
+ padding: var(--flx-ui-space-2) var(--flx-ui-space-4) var(--flx-ui-space-4);
78
78
  }
79
79
  :where([data-bottom-sheet-title]) {
80
80
  margin: 0;
81
81
  font-size: var(--flx-ui-font-size-lg);
82
82
  font-weight: var(--flx-ui-font-weight-semibold);
83
- line-height: var(--flx-ui-leading-tight);
83
+ line-height: var(--flx-ui-line-height-tight);
84
84
  color: var(--flx-ui-color-fg);
85
85
  }
86
86
  :where([data-bottom-sheet-description]) {
@@ -92,14 +92,14 @@
92
92
  flex: 1;
93
93
  min-height: 0;
94
94
  overflow-y: auto;
95
- padding: 0 var(--flx-ui-space-5) var(--flx-ui-space-5);
95
+ padding: 0 var(--flx-ui-space-4) var(--flx-ui-space-4);
96
96
  }
97
97
  :where([data-bottom-sheet-footer]) {
98
98
  display: flex;
99
99
  align-items: center;
100
100
  justify-content: flex-end;
101
101
  gap: var(--flx-ui-space-2);
102
- padding: var(--flx-ui-space-4) var(--flx-ui-space-5);
102
+ padding: var(--flx-ui-space-4) var(--flx-ui-space-4);
103
103
  border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
104
104
  }
105
105
 
@@ -21,7 +21,7 @@
21
21
  color: var(--flx-ui-color-fg-muted);
22
22
  text-decoration: none;
23
23
  border-radius: var(--flx-ui-radius-sm);
24
- transition: color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
24
+ transition: color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
25
25
  }
26
26
  :where([data-breadcrumb-link]:hover) {
27
27
  color: var(--flx-ui-color-fg);
@@ -20,6 +20,9 @@
20
20
  border: var(--flx-ui-border-width-thin) solid transparent;
21
21
  border-radius: var(--flx-ui-radius-md);
22
22
  height: var(--flx-ui-size-md);
23
+ /* Never narrower than tall, so an icon-only button (a close ×, a chevron)
24
+ stays square instead of collapsing to its glyph width. */
25
+ min-width: var(--flx-ui-size-md);
23
26
  padding: 0 var(--flx-ui-space-4);
24
27
  color: var(--flx-ui-color-fg);
25
28
  transition:
@@ -29,11 +32,13 @@
29
32
  }
30
33
  :where([data-variant][data-size='sm']) {
31
34
  height: var(--flx-ui-size-sm);
35
+ min-width: var(--flx-ui-size-sm);
32
36
  padding: 0 var(--flx-ui-space-3);
33
37
  font-size: var(--flx-ui-font-size-sm);
34
38
  }
35
39
  :where([data-variant][data-size='lg']) {
36
40
  height: var(--flx-ui-size-lg);
41
+ min-width: var(--flx-ui-size-lg);
37
42
  padding: 0 var(--flx-ui-space-5);
38
43
  font-size: var(--flx-ui-font-size-lg);
39
44
  }
@@ -34,11 +34,12 @@
34
34
  background: transparent;
35
35
  color: var(--flx-ui-color-fg-muted);
36
36
  cursor: pointer;
37
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
38
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
37
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
38
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
39
39
  }
40
40
  :where([data-calendar-nav]:hover:not([disabled])) {
41
- background: var(--flx-ui-color-bg-subtle);
41
+ background: var(--flx-ui-color-bg-muted);
42
+ border-color: var(--flx-ui-color-border-strong);
42
43
  color: var(--flx-ui-color-fg);
43
44
  }
44
45
  :where([data-calendar-nav]:focus-visible) {
@@ -77,11 +78,12 @@
77
78
  font-size: var(--flx-ui-font-size-sm);
78
79
  color: var(--flx-ui-color-fg);
79
80
  cursor: pointer;
80
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
81
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
81
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
82
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
82
83
  }
83
- :where([data-calendar-day]:hover:not([data-disabled]):not([data-unavailable])) {
84
- background: var(--flx-ui-color-bg-subtle);
84
+ :where([data-calendar-day]:hover:not([data-disabled]):not([data-unavailable]):not([data-selected])) {
85
+ background: var(--flx-ui-color-primary-subtle);
86
+ color: var(--flx-ui-color-primary-active);
85
87
  }
86
88
  :where([data-calendar-day]:focus-visible) {
87
89
  outline: none;
package/dist/css/card.css CHANGED
@@ -12,6 +12,10 @@
12
12
  box-shadow: var(--flx-ui-shadow-sm);
13
13
  overflow: hidden;
14
14
  }
15
+ /* Tonal elevation in dark — a card lifts by lightening, not by an unseen shadow. */
16
+ [data-theme='dark'] :where([data-card]) {
17
+ background: var(--flx-ui-gray-2);
18
+ }
15
19
  :where([data-card-header]) {
16
20
  display: flex;
17
21
  flex-direction: column;
@@ -22,7 +26,7 @@
22
26
  margin: 0;
23
27
  font-size: var(--flx-ui-font-size-lg);
24
28
  font-weight: var(--flx-ui-font-weight-semibold);
25
- line-height: var(--flx-ui-leading-tight);
29
+ line-height: var(--flx-ui-line-height-tight);
26
30
  color: var(--flx-ui-color-fg);
27
31
  }
28
32
  :where([data-card-description]) {
@@ -23,6 +23,9 @@
23
23
  background: var(--flx-ui-color-primary);
24
24
  border-color: var(--flx-ui-color-primary);
25
25
  }
26
+ :where([role='checkbox']:active:not([data-disabled])) {
27
+ transform: scale(0.92);
28
+ }
26
29
  :where([role='checkbox'][data-state='checked'])::after {
27
30
  content: '';
28
31
  width: 5px;
@@ -31,6 +34,8 @@
31
34
  border: solid currentColor;
32
35
  border-width: 0 2px 2px 0;
33
36
  transform: rotate(45deg);
37
+ transform-origin: center;
38
+ animation: flx-ui-check-pop var(--flx-ui-duration-normal) var(--flx-ui-ease-emphasized);
34
39
  }
35
40
  :where([role='checkbox'][data-state='indeterminate'])::after {
36
41
  content: '';
@@ -38,6 +43,7 @@
38
43
  height: 2px;
39
44
  background: currentColor;
40
45
  border-radius: 1px;
46
+ animation: flx-ui-scale-pop var(--flx-ui-duration-fast) var(--flx-ui-ease-emphasized);
41
47
  }
42
48
 
43
49
  :where([role='radio']) {
@@ -50,7 +56,12 @@
50
56
  border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
51
57
  border-radius: var(--flx-ui-radius-full);
52
58
  background: var(--flx-ui-color-bg);
53
- transition: border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
59
+ transition:
60
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
61
+ border-width var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
62
+ }
63
+ :where([role='radio']:active:not([data-disabled])) {
64
+ transform: scale(0.92);
54
65
  }
55
66
  :where([role='radio'][data-state='checked']) {
56
67
  border-color: var(--flx-ui-color-primary);
@@ -62,5 +73,48 @@
62
73
  inset: 3px;
63
74
  border-radius: var(--flx-ui-radius-full);
64
75
  background: var(--flx-ui-color-primary);
76
+ animation: flx-ui-scale-pop var(--flx-ui-duration-normal) var(--flx-ui-ease-emphasized);
77
+ }
78
+
79
+ :where([role='checkbox']:hover:not([data-disabled])),
80
+ :where([role='radio']:hover:not([data-disabled])) {
81
+ border-color: var(--flx-ui-color-primary);
82
+ }
83
+ :where([role='checkbox']:focus-visible),
84
+ :where([role='radio']:focus-visible) {
85
+ outline: none;
86
+ box-shadow: var(--flx-ui-ctl-ring);
87
+ }
88
+ :where([role='checkbox'][data-disabled]),
89
+ :where([role='radio'][data-disabled]) {
90
+ opacity: var(--flx-ui-opacity-disabled);
91
+ cursor: not-allowed;
92
+ }
93
+
94
+ @keyframes flx-ui-check-pop {
95
+ from {
96
+ transform: rotate(45deg) scale(0.3);
97
+ opacity: 0;
98
+ }
99
+ to {
100
+ transform: rotate(45deg) scale(1);
101
+ opacity: 1;
102
+ }
103
+ }
104
+ @keyframes flx-ui-scale-pop {
105
+ from {
106
+ transform: scale(0);
107
+ opacity: 0;
108
+ }
109
+ to {
110
+ transform: scale(1);
111
+ opacity: 1;
112
+ }
113
+ }
114
+ @media (prefers-reduced-motion: reduce) {
115
+ :where([role='checkbox'][data-state]::after),
116
+ :where([role='radio'][data-state='checked'])::after {
117
+ animation: none;
118
+ }
65
119
  }
66
120
  }
@@ -11,7 +11,7 @@
11
11
  font-size: var(--flx-ui-font-size-sm);
12
12
  font-weight: var(--flx-ui-font-weight-medium);
13
13
  color: var(--flx-ui-color-fg);
14
- line-height: var(--flx-ui-leading-none);
14
+ line-height: var(--flx-ui-line-height-none);
15
15
  }
16
16
  :where([data-date-field-input]) {
17
17
  display: inline-flex;
@@ -11,7 +11,7 @@
11
11
  font-size: var(--flx-ui-font-size-sm);
12
12
  font-weight: var(--flx-ui-font-weight-medium);
13
13
  color: var(--flx-ui-color-fg);
14
- line-height: var(--flx-ui-leading-none);
14
+ line-height: var(--flx-ui-line-height-none);
15
15
  }
16
16
  /* The control box owns the border; the embedded field input goes borderless. */
17
17
  :where([data-date-picker-control]) {
@@ -71,7 +71,7 @@
71
71
  border-radius: var(--flx-ui-radius-lg);
72
72
  background: var(--flx-ui-color-bg);
73
73
  box-shadow: var(--flx-ui-shadow-lg, 0 10px 30px rgba(17, 24, 39, 0.12));
74
- animation: flx-ui-pop-in var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
74
+ animation: flx-ui-pop-in var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
75
75
  }
76
76
  @media (prefers-reduced-motion: reduce) {
77
77
  :where([data-date-picker-content]) {
@@ -70,14 +70,14 @@
70
70
  align-items: flex-start;
71
71
  justify-content: space-between;
72
72
  gap: var(--flx-ui-space-3);
73
- padding: var(--flx-ui-space-5);
73
+ padding: var(--flx-ui-space-4);
74
74
  border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
75
75
  }
76
76
  :where([data-drawer-title]) {
77
77
  margin: 0;
78
78
  font-size: var(--flx-ui-font-size-lg);
79
79
  font-weight: var(--flx-ui-font-weight-semibold);
80
- line-height: var(--flx-ui-leading-tight);
80
+ line-height: var(--flx-ui-line-height-tight);
81
81
  color: var(--flx-ui-color-fg);
82
82
  }
83
83
  :where([data-drawer-description]) {
@@ -89,14 +89,14 @@
89
89
  flex: 1;
90
90
  min-height: 0;
91
91
  overflow-y: auto;
92
- padding: var(--flx-ui-space-5);
92
+ padding: var(--flx-ui-space-4);
93
93
  }
94
94
  :where([data-drawer-footer]) {
95
95
  display: flex;
96
96
  align-items: center;
97
97
  justify-content: flex-end;
98
98
  gap: var(--flx-ui-space-2);
99
- padding: var(--flx-ui-space-5);
99
+ padding: var(--flx-ui-space-4);
100
100
  border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
101
101
  }
102
102
 
@@ -0,0 +1,123 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Glow — an animated gradient border ring, opt-in via `data-glow` on any element
4
+ * (button, surface, card…). The ::after covers the border box and is masked to
5
+ * just the ring, so the element keeps its own fill and centred content. Colours
6
+ * follow the accent and are overridable per-instance with --flx-glow-from / -via
7
+ * / -to (and rest speed via --flx-glow-speed). `data-glow-side` restricts it to
8
+ * one edge that slides to the opposite side on hover.
9
+ * ------------------------------------------------------------------------- */
10
+ :where([data-glow]) {
11
+ position: relative;
12
+ border-color: transparent;
13
+ /* Show the edge ring even when the host would otherwise clip (e.g. a card). */
14
+ overflow: visible;
15
+ }
16
+ :where([data-glow])::after {
17
+ content: '';
18
+ position: absolute;
19
+ inset: 0;
20
+ border-radius: inherit;
21
+ padding: 2px;
22
+ background: conic-gradient(
23
+ from var(--flx-angle, 0deg),
24
+ var(--flx-glow-from, var(--flx-ui-color-primary)),
25
+ var(--flx-glow-via, var(--flx-ui-accent-5)),
26
+ var(--flx-glow-to, var(--flx-glow-from, var(--flx-ui-color-primary)))
27
+ );
28
+ -webkit-mask:
29
+ linear-gradient(#000 0 0) content-box,
30
+ linear-gradient(#000 0 0);
31
+ -webkit-mask-composite: xor;
32
+ mask:
33
+ linear-gradient(#000 0 0) content-box,
34
+ linear-gradient(#000 0 0);
35
+ mask-composite: exclude;
36
+ animation: flx-border-spin var(--flx-glow-speed, 4s) linear infinite;
37
+ transition: filter var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
38
+ pointer-events: none;
39
+ }
40
+ /* Comes alive on hover: the ring spins faster and blooms in its accent colour. */
41
+ :where([data-glow]:hover:not([data-disabled]))::after {
42
+ --flx-glow-speed: 1.2s;
43
+ filter: drop-shadow(0 0 5px var(--flx-glow-via, var(--flx-ui-color-primary)));
44
+ }
45
+
46
+ /* Single-edge glow: reuse the fixed full ring mask, but paint the colour as a
47
+ band positioned on one side, so only that edge (and its corners) lights up.
48
+ Because it's the *background* that's positioned, hover slides it to the opposite
49
+ side smoothly. Positional, so no spin. */
50
+ :where([data-glow][data-glow-side])::after {
51
+ animation: none;
52
+ background-repeat: no-repeat;
53
+ transition:
54
+ background-position 450ms var(--flx-ui-ease-standard),
55
+ filter var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
56
+ }
57
+ :where([data-glow][data-glow-side='top'])::after,
58
+ :where([data-glow][data-glow-side='bottom'])::after {
59
+ background-image: linear-gradient(
60
+ to bottom,
61
+ transparent 28%,
62
+ var(--flx-glow-from, var(--flx-ui-color-primary)) 44%,
63
+ var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
64
+ var(--flx-glow-from, var(--flx-ui-color-primary)) 56%,
65
+ transparent 72%
66
+ );
67
+ background-size: 100% 200%;
68
+ }
69
+ :where([data-glow][data-glow-side='left'])::after,
70
+ :where([data-glow][data-glow-side='right'])::after {
71
+ background-image: linear-gradient(
72
+ to right,
73
+ transparent 28%,
74
+ var(--flx-glow-from, var(--flx-ui-color-primary)) 44%,
75
+ var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
76
+ var(--flx-glow-from, var(--flx-ui-color-primary)) 56%,
77
+ transparent 72%
78
+ );
79
+ background-size: 200% 100%;
80
+ }
81
+ :where([data-glow][data-glow-side='bottom'])::after {
82
+ background-position: 50% 0%;
83
+ }
84
+ :where([data-glow][data-glow-side='bottom']:hover:not([data-disabled]))::after {
85
+ background-position: 50% 100%;
86
+ }
87
+ :where([data-glow][data-glow-side='top'])::after {
88
+ background-position: 50% 100%;
89
+ }
90
+ :where([data-glow][data-glow-side='top']:hover:not([data-disabled]))::after {
91
+ background-position: 50% 0%;
92
+ }
93
+ :where([data-glow][data-glow-side='left'])::after {
94
+ background-position: 100% 50%;
95
+ }
96
+ :where([data-glow][data-glow-side='left']:hover:not([data-disabled]))::after {
97
+ background-position: 0% 50%;
98
+ }
99
+ :where([data-glow][data-glow-side='right'])::after {
100
+ background-position: 0% 50%;
101
+ }
102
+ :where([data-glow][data-glow-side='right']:hover:not([data-disabled]))::after {
103
+ background-position: 100% 50%;
104
+ }
105
+
106
+ @media (prefers-reduced-motion: reduce) {
107
+ :where([data-glow])::after {
108
+ animation: none;
109
+ }
110
+ }
111
+ }
112
+
113
+ /* Registered so the angle can actually animate; unlayered like other keyframes. */
114
+ @property --flx-angle {
115
+ syntax: '<angle>';
116
+ inherits: false;
117
+ initial-value: 0deg;
118
+ }
119
+ @keyframes flx-border-spin {
120
+ to {
121
+ --flx-angle: 360deg;
122
+ }
123
+ }
@@ -29,6 +29,21 @@
29
29
  :where([data-input][data-invalid]) {
30
30
  border-color: var(--flx-ui-color-danger);
31
31
  }
32
+ /* Drop the browser's native number spinners — they can't be styled consistently
33
+ across engines and clash with the field look. Covers both the bare input and
34
+ the one nested inside an adornment group (which carries no [data-input]). Reach
35
+ for @fluixi-ui/number-field when you want proper increment / decrement steppers. */
36
+ :where([data-input][type='number'], [data-input-group] input[type='number']) {
37
+ -moz-appearance: textfield;
38
+ appearance: textfield;
39
+ }
40
+ :where([data-input][type='number']::-webkit-outer-spin-button),
41
+ :where([data-input][type='number']::-webkit-inner-spin-button),
42
+ :where([data-input-group] input[type='number']::-webkit-outer-spin-button),
43
+ :where([data-input-group] input[type='number']::-webkit-inner-spin-button) {
44
+ -webkit-appearance: none;
45
+ margin: 0;
46
+ }
32
47
 
33
48
  /* Input with leading/trailing adornments: the group owns the frame. */
34
49
  :where([data-input-group]) {
@@ -42,20 +42,21 @@
42
42
  :where([data-navigation-menu-trigger]:focus-visible, [data-navigation-menu-link]:focus-visible) {
43
43
  box-shadow: var(--flx-ui-ctl-ring);
44
44
  }
45
- /* The chevron affordance: rotates when the panel opens. */
45
+ /* The chevron affordance: the shared theme chevron, flipping 180° when open. */
46
46
  :where([data-navigation-menu-trigger])::after {
47
47
  content: '';
48
- width: 0.55em;
49
- height: 0.55em;
48
+ width: 1em;
49
+ height: 1em;
50
+ flex: none;
50
51
  margin-inline-start: var(--flx-ui-space-1);
51
- border-right: 1.5px solid currentColor;
52
- border-bottom: 1.5px solid currentColor;
53
- transform: rotate(45deg) translateY(-0.1em);
52
+ background: currentColor;
54
53
  opacity: 0.6;
54
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
55
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
55
56
  transition: transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
56
57
  }
57
58
  :where([data-navigation-menu-trigger][data-state='open'])::after {
58
- transform: rotate(225deg) translateY(-0.1em);
59
+ transform: rotate(180deg);
59
60
  }
60
61
 
61
62
  :where([data-navigation-menu-content]) {
@@ -11,7 +11,7 @@
11
11
  font-size: var(--flx-ui-font-size-sm);
12
12
  font-weight: var(--flx-ui-font-weight-medium);
13
13
  color: var(--flx-ui-color-fg);
14
- line-height: var(--flx-ui-leading-none);
14
+ line-height: var(--flx-ui-line-height-none);
15
15
  }
16
16
  :where([data-number-field-label][data-disabled]) {
17
17
  color: var(--flx-ui-color-fg-subtle);
@@ -61,8 +61,8 @@
61
61
  color: var(--flx-ui-color-fg-muted);
62
62
  cursor: pointer;
63
63
  font-size: var(--flx-ui-font-size-sm);
64
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
65
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
64
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
65
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
66
66
  }
67
67
  :where([data-number-field-decrement]) {
68
68
  border-right: 1px solid var(--flx-ui-color-border);