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

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.
Files changed (61) hide show
  1. package/dist/css/accordion.css +81 -3
  2. package/dist/css/alert.css +52 -23
  3. package/dist/css/avatar.css +19 -0
  4. package/dist/css/badge.css +129 -5
  5. package/dist/css/base.css +3 -1
  6. package/dist/css/bottom-sheet.css +4 -4
  7. package/dist/css/breadcrumb.css +1 -1
  8. package/dist/css/bulk-editor.css +2 -17
  9. package/dist/css/button.css +76 -16
  10. package/dist/css/calendar.css +9 -7
  11. package/dist/css/card.css +5 -1
  12. package/dist/css/carousel.css +80 -0
  13. package/dist/css/checkbox-radio.css +55 -1
  14. package/dist/css/chip.css +175 -0
  15. package/dist/css/date-field.css +1 -1
  16. package/dist/css/date-picker.css +2 -2
  17. package/dist/css/descriptions.css +29 -0
  18. package/dist/css/drawer.css +4 -4
  19. package/dist/css/empty.css +40 -0
  20. package/dist/css/fab.css +48 -0
  21. package/dist/css/field-variants.css +139 -0
  22. package/dist/css/file-upload.css +40 -0
  23. package/dist/css/glow.css +127 -0
  24. package/dist/css/icon-button.css +42 -0
  25. package/dist/css/image.css +67 -0
  26. package/dist/css/input.css +15 -0
  27. package/dist/css/layout.css +10 -0
  28. package/dist/css/navigation-menu.css +8 -7
  29. package/dist/css/number-field.css +3 -3
  30. package/dist/css/page.css +138 -0
  31. package/dist/css/pagination.css +3 -3
  32. package/dist/css/parts/listbox.css +9 -4
  33. package/dist/css/pin-input.css +40 -0
  34. package/dist/css/progress.css +56 -0
  35. package/dist/css/rating.css +59 -0
  36. package/dist/css/scroll-area.css +44 -0
  37. package/dist/css/select-combobox.css +19 -4
  38. package/dist/css/sidenav.css +180 -10
  39. package/dist/css/slider.css +4 -4
  40. package/dist/css/speed-dial.css +85 -0
  41. package/dist/css/splitter.css +81 -0
  42. package/dist/css/statistic.css +34 -0
  43. package/dist/css/stepper.css +3 -3
  44. package/dist/css/surface.css +71 -0
  45. package/dist/css/switch.css +12 -0
  46. package/dist/css/table.css +11 -0
  47. package/dist/css/tabs.css +10 -1
  48. package/dist/css/tags-input.css +91 -0
  49. package/dist/css/text.css +124 -0
  50. package/dist/css/timeline.css +59 -0
  51. package/dist/css/toggle.css +6 -0
  52. package/dist/css/tree-view.css +70 -0
  53. package/dist/lib/css/index.d.ts +10 -12
  54. package/dist/lib/css/index.d.ts.map +1 -1
  55. package/dist/lib/css/index.js +21 -7
  56. package/dist/lib/reference/colors.d.ts.map +1 -1
  57. package/dist/lib/reference/colors.js +15 -1
  58. package/dist/preset.css +23 -1
  59. package/dist/tokens.css +52 -4
  60. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  61. package/package.json +2 -2
@@ -0,0 +1,59 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Rating
4
+ *
5
+ * Each item is a star silhouette (mask) painted in the "empty" colour; a
6
+ * ::before overlay paints the "filled" colour, revealed left-to-right via
7
+ * clip-path by the item's data-state (empty / half / full).
8
+ * ------------------------------------------------------------------------- */
9
+ :where([data-rating]) {
10
+ --flx-ui-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.2l2.9 6.5 7 .6-5.3 4.6 1.6 6.9L12 17.7 5.8 21.4l1.6-6.9L2 9.9l7-.6z'/%3E%3C/svg%3E");
11
+ display: inline-flex;
12
+ align-items: center;
13
+ gap: var(--flx-ui-rating-gap, 2px);
14
+ outline: none;
15
+ }
16
+ :where([data-rating]:not([data-disabled]):not([data-readonly])) {
17
+ cursor: pointer;
18
+ }
19
+ :where([data-rating]:focus-visible) {
20
+ border-radius: var(--flx-ui-radius-sm);
21
+ box-shadow: var(--flx-ui-ctl-ring);
22
+ }
23
+ :where([data-rating][data-disabled]) {
24
+ opacity: var(--flx-ui-opacity-disabled, 0.5);
25
+ cursor: not-allowed;
26
+ }
27
+
28
+ :where([data-rating-item]) {
29
+ position: relative;
30
+ display: inline-block;
31
+ width: var(--flx-ui-rating-size, 20px);
32
+ height: var(--flx-ui-rating-size, 20px);
33
+ flex: none;
34
+ background: var(--flx-ui-color-border-strong);
35
+ -webkit-mask: var(--flx-ui-star) center / contain no-repeat;
36
+ mask: var(--flx-ui-star) center / contain no-repeat;
37
+ }
38
+ :where([data-rating-item])::before {
39
+ content: '';
40
+ position: absolute;
41
+ inset: 0;
42
+ background: var(--flx-ui-rating-color, var(--flx-ui-color-primary));
43
+ -webkit-mask: var(--flx-ui-star) center / contain no-repeat;
44
+ mask: var(--flx-ui-star) center / contain no-repeat;
45
+ clip-path: inset(0 100% 0 0);
46
+ transition: clip-path var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
47
+ }
48
+ :where([data-rating-item][data-state='full'])::before {
49
+ clip-path: inset(0 0 0 0);
50
+ }
51
+ :where([data-rating-item][data-state='half'])::before {
52
+ clip-path: inset(0 50% 0 0);
53
+ }
54
+ @media (prefers-reduced-motion: reduce) {
55
+ :where([data-rating-item])::before {
56
+ transition: none;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,44 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Scroll area — a scroll container with slim, theme-aware scrollbars styled over
4
+ * the native scrollbar (Firefox `scrollbar-*` + WebKit `::-webkit-scrollbar`).
5
+ * ------------------------------------------------------------------------- */
6
+ :where([data-scroll-area]) {
7
+ position: relative;
8
+ scrollbar-width: thin;
9
+ scrollbar-color: var(--flx-ui-color-border-strong) transparent;
10
+ overscroll-behavior: contain;
11
+ }
12
+ :where([data-scroll-area][data-orientation='vertical']) {
13
+ overflow-y: auto;
14
+ overflow-x: hidden;
15
+ }
16
+ :where([data-scroll-area][data-orientation='horizontal']) {
17
+ overflow-x: auto;
18
+ overflow-y: hidden;
19
+ }
20
+ :where([data-scroll-area][data-orientation='both']) {
21
+ overflow: auto;
22
+ }
23
+ :where([data-scroll-area])::-webkit-scrollbar {
24
+ width: 10px;
25
+ height: 10px;
26
+ }
27
+ :where([data-scroll-area])::-webkit-scrollbar-track {
28
+ background: transparent;
29
+ }
30
+ :where([data-scroll-area])::-webkit-scrollbar-thumb {
31
+ background: var(--flx-ui-color-border-strong);
32
+ border-radius: var(--flx-ui-radius-full);
33
+ /* Inset the thumb with a transparent border so it reads as a slim pill. */
34
+ border: 2px solid transparent;
35
+ background-clip: content-box;
36
+ }
37
+ :where([data-scroll-area])::-webkit-scrollbar-thumb:hover {
38
+ background: var(--flx-ui-color-fg-muted);
39
+ background-clip: content-box;
40
+ }
41
+ :where([data-scroll-area])::-webkit-scrollbar-corner {
42
+ background: transparent;
43
+ }
44
+ }
@@ -33,9 +33,13 @@
33
33
  box-shadow: var(--flx-ui-ctl-ring);
34
34
  }
35
35
  :where(button[role='combobox'])::after {
36
- content: '▾';
37
- color: var(--flx-ui-color-fg-muted);
38
- font-size: 0.8em;
36
+ content: '';
37
+ width: 1.15em;
38
+ height: 1.15em;
39
+ flex: none;
40
+ background: var(--flx-ui-color-fg-muted);
41
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
42
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
39
43
  }
40
44
  :where([data-select-value][data-placeholder]) {
41
45
  color: var(--flx-ui-color-fg-muted);
@@ -65,12 +69,23 @@
65
69
  color: var(--flx-ui-color-fg-muted);
66
70
  }
67
71
  :where([data-combobox-control] > button) {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ justify-content: center;
68
75
  border: none;
69
76
  background: none;
70
77
  cursor: pointer;
71
78
  color: var(--flx-ui-color-fg-muted);
72
79
  padding: 0 var(--flx-ui-space-1);
73
- font-size: 0.8em;
74
80
  border-radius: var(--flx-ui-radius-sm);
75
81
  }
82
+ /* When the trigger has no custom content, show the shared theme chevron. */
83
+ :where([data-combobox-control] > button:empty)::after {
84
+ content: '';
85
+ width: 1.15em;
86
+ height: 1.15em;
87
+ background: currentColor;
88
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
89
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
90
+ }
76
91
  }
@@ -3,9 +3,13 @@
3
3
  * Sidenav (persistent navigation rail)
4
4
  * ------------------------------------------------------------------------- */
5
5
  :where([data-sidenav]) {
6
+ /* Exposed so a consumer's shell can read the live rail width (it also drives the
7
+ rail's own width, so an in-flow flex/grid sidebar makes the main area adapt
8
+ automatically as this transitions). */
9
+ --flx-ui-sidenav-width: 16rem;
6
10
  display: flex;
7
11
  flex-direction: column;
8
- width: 16rem;
12
+ width: var(--flx-ui-sidenav-width);
9
13
  height: 100%;
10
14
  background: var(--flx-ui-color-bg);
11
15
  color: var(--flx-ui-color-fg);
@@ -17,8 +21,14 @@
17
21
  :where([data-sidenav][data-side='right']) {
18
22
  border-left: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
19
23
  }
20
- :where([data-sidenav][data-collapsed]) {
21
- width: 3.75rem;
24
+ /* Compact = icon rail. Hidden = fully off-canvas (zero width, content clipped). */
25
+ :where([data-sidenav][data-state='compact']) {
26
+ --flx-ui-sidenav-width: 3.75rem;
27
+ }
28
+ :where([data-sidenav][data-state='hidden']) {
29
+ --flx-ui-sidenav-width: 0px;
30
+ overflow: hidden;
31
+ border-inline-width: 0;
22
32
  }
23
33
 
24
34
  :where([data-sidenav-header]) {
@@ -65,8 +75,11 @@
65
75
  transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
66
76
  color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
67
77
  }
68
- :where([data-sidenav-link]:hover) {
69
- background: var(--flx-ui-color-bg-subtle);
78
+ /* Hover uses an fg-relative tint (not gray-2, which is invisible on the gray-1
79
+ rail) so it stays legible on any surface, light or dark. `:not([data-active])`
80
+ keeps the active pill from being flattened by hover. */
81
+ :where([data-sidenav-link]:hover:not([data-active])) {
82
+ background: color-mix(in oklab, var(--flx-ui-color-fg) 6%, transparent);
70
83
  color: var(--flx-ui-color-fg);
71
84
  }
72
85
  :where([data-sidenav-link][data-active]) {
@@ -99,6 +112,7 @@
99
112
  justify-content: center;
100
113
  width: 2rem;
101
114
  height: 2rem;
115
+ flex-shrink: 0;
102
116
  padding: 0;
103
117
  background: transparent;
104
118
  border: none;
@@ -106,22 +120,178 @@
106
120
  color: var(--flx-ui-color-fg-muted);
107
121
  cursor: pointer;
108
122
  }
123
+ /* A trigger in the header sits at the trailing edge on its own — the consumer
124
+ drops in <SidenavTrigger/> without wiring any layout. On the compact icon rail
125
+ the header centres its contents, so the auto-margin is dropped. */
126
+ :where([data-sidenav-header] [data-sidenav-trigger]) {
127
+ margin-inline-start: auto;
128
+ }
129
+ :where([data-sidenav][data-state='compact']:not([data-mobile]) [data-sidenav-header] [data-sidenav-trigger]) {
130
+ margin-inline-start: 0;
131
+ }
109
132
  :where([data-sidenav-trigger]:hover) {
110
- background: var(--flx-ui-color-bg-subtle);
133
+ background: color-mix(in oklab, var(--flx-ui-color-fg) 6%, transparent);
111
134
  color: var(--flx-ui-color-fg);
112
135
  }
113
136
  :where([data-sidenav-trigger]:focus-visible) {
114
137
  outline: var(--flx-ui-ctl-ring);
115
138
  outline-offset: 2px;
116
139
  }
140
+ /* Built-in trigger icon: a chevron pointing in the collapse direction, flipping
141
+ between expanded and compact (and mirrored for a right-side rail). */
142
+ :where([data-sidenav-trigger-icon]) {
143
+ display: block;
144
+ width: 1.15em;
145
+ height: 1.15em;
146
+ background: currentColor;
147
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
148
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
149
+ transform: rotate(90deg);
150
+ transition: transform var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
151
+ }
152
+ :where([data-sidenav][data-collapsed] [data-sidenav-trigger-icon]),
153
+ :where([data-sidenav][data-side='right'] [data-sidenav-trigger-icon]) {
154
+ transform: rotate(-90deg);
155
+ }
156
+ :where([data-sidenav][data-side='right'][data-collapsed] [data-sidenav-trigger-icon]) {
157
+ transform: rotate(90deg);
158
+ }
117
159
 
118
- /* Collapsed rail: hide text labels, center the icons. */
119
- :where([data-sidenav][data-collapsed] [data-sidenav-label]),
120
- :where([data-sidenav][data-collapsed] [data-sidenav-group-label]) {
160
+ /* Compact icon rail: collapse each row to its leading icon. Scoped to `compact`
161
+ (not the mobile drawer, which is full-width; `hidden` is clipped anyway).
162
+ Applies to DIRECT rail rows ONLY — a sub's flyout content is portalled OUTSIDE
163
+ [data-sidenav], so these descendant rules never reach it (subitems keep their
164
+ labels in the popup). Three ways an element hides in compact:
165
+ 1. [data-sidenav-label] / [data-sidenav-group-label] — the recommended wrapper.
166
+ 2. [data-sidenav-hide-compact] — an explicit opt-in marker for ANY element.
167
+ 3. auto — any non-leading child of a link / sub-trigger (the first child is the
168
+ icon), so unmarked label text collapses with no extra markup. */
169
+ :where([data-sidenav][data-state='compact']:not([data-mobile]))
170
+ :where([data-sidenav-label], [data-sidenav-group-label], [data-sidenav-hide-compact]) {
121
171
  display: none;
122
172
  }
123
- :where([data-sidenav][data-collapsed] [data-sidenav-link]) {
173
+ :where([data-sidenav][data-state='compact']:not([data-mobile]))
174
+ :where([data-sidenav-link], [data-sidenav-sub-trigger]) > :not(:first-child) {
175
+ display: none;
176
+ }
177
+ :where([data-sidenav][data-state='compact']:not([data-mobile]))
178
+ :where([data-sidenav-link], [data-sidenav-sub-trigger]) {
124
179
  justify-content: center;
180
+ gap: 0;
125
181
  padding-inline: 0;
126
182
  }
183
+
184
+ /* ── Nested items (submenus) ──────────────────────────────────────────────── */
185
+ /* The parent row reuses [data-sidenav-link] styling; add the disclosure chevron. */
186
+ :where([data-sidenav-sub-trigger]) {
187
+ width: 100%;
188
+ border: 0;
189
+ background: transparent;
190
+ font: inherit;
191
+ text-align: start;
192
+ cursor: pointer;
193
+ }
194
+ :where([data-sidenav-sub-trigger])::after {
195
+ content: '';
196
+ margin-inline-start: auto;
197
+ width: 1em;
198
+ height: 1em;
199
+ flex-shrink: 0;
200
+ background: currentColor;
201
+ mask: var(--flx-ui-chevron-down, none) center / contain no-repeat;
202
+ -webkit-mask: var(--flx-ui-chevron-down, none) center / contain no-repeat;
203
+ transition: transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
204
+ }
205
+ :where([data-sidenav-sub-trigger][data-open])::after {
206
+ transform: rotate(180deg);
207
+ }
208
+ /* Hide the chevron on the compact rail (the flyout replaces it). */
209
+ :where([data-sidenav][data-state='compact']:not([data-mobile]) [data-sidenav-sub-trigger])::after {
210
+ display: none;
211
+ }
212
+
213
+ /* Inline nested list (expanded / mobile). Collapsed via max-height so every child
214
+ hides (a grid-rows 0fr trick only collapses a single grid child — the rest fall
215
+ into implicit auto rows and stay visible). The cap is generous; nav lists are short. */
216
+ :where([data-sidenav-sub-content]:not([data-flyout])) {
217
+ margin: 0;
218
+ padding: 0 0 0 var(--flx-ui-space-5);
219
+ list-style: none;
220
+ max-height: 0;
221
+ overflow: hidden;
222
+ opacity: 0;
223
+ transition:
224
+ max-height var(--flx-ui-duration-normal) var(--flx-ui-ease-standard),
225
+ opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
226
+ }
227
+ :where([data-sidenav-sub-content]:not([data-flyout])[data-open]) {
228
+ max-height: 40rem;
229
+ opacity: 1;
230
+ }
231
+
232
+ /* Flyout panel (compact rail): a floating anchored menu of the nested links. */
233
+ :where([data-sidenav-sub-content][data-flyout]) {
234
+ position: absolute;
235
+ z-index: var(--flx-ui-z-popover, 40);
236
+ min-width: 12rem;
237
+ margin: 0;
238
+ padding: var(--flx-ui-space-1);
239
+ list-style: none;
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: var(--flx-ui-space-1);
243
+ background: var(--flx-ui-color-bg);
244
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
245
+ border-radius: var(--flx-ui-radius-lg);
246
+ box-shadow: var(--flx-ui-shadow-lg);
247
+ }
248
+
249
+ /* ── Mobile overlay drawer ────────────────────────────────────────────────── */
250
+ :where([data-sidenav-overlay]) {
251
+ position: fixed;
252
+ inset: 0;
253
+ z-index: var(--flx-ui-z-modal, 50);
254
+ }
255
+ :where([data-sidenav-overlay][data-state='closed']) {
256
+ pointer-events: none;
257
+ }
258
+ :where([data-sidenav-backdrop]) {
259
+ position: fixed;
260
+ inset: 0;
261
+ background: var(--flx-ui-color-overlay);
262
+ opacity: 0;
263
+ transition: opacity var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
264
+ }
265
+ :where([data-sidenav-backdrop][data-state='open']) {
266
+ opacity: 1;
267
+ }
268
+ /* The overlay rail is full-height, positioned against its side, always expanded. */
269
+ :where([data-sidenav][data-mobile]) {
270
+ position: fixed;
271
+ top: 0;
272
+ bottom: 0;
273
+ --flx-ui-sidenav-width: 16rem;
274
+ max-width: 85vw;
275
+ box-shadow: var(--flx-ui-shadow-xl);
276
+ animation: flx-sidenav-in var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
277
+ }
278
+ :where([data-sidenav][data-mobile][data-side='left']) {
279
+ left: 0;
280
+ }
281
+ :where([data-sidenav][data-mobile][data-side='right']) {
282
+ right: 0;
283
+ }
284
+ @keyframes flx-sidenav-in {
285
+ from {
286
+ transform: translateX(var(--flx-ui-sidenav-enter-x, -100%));
287
+ }
288
+ }
289
+ :where([data-sidenav][data-mobile][data-side='right']) {
290
+ --flx-ui-sidenav-enter-x: 100%;
291
+ }
292
+ @media (prefers-reduced-motion: reduce) {
293
+ :where([data-sidenav][data-mobile]) {
294
+ animation: none;
295
+ }
296
+ }
127
297
  }
@@ -42,7 +42,7 @@
42
42
  left: 0;
43
43
  width: 100%;
44
44
  }
45
- :where([role='slider']) {
45
+ :where([data-slider] [role='slider']) {
46
46
  position: absolute;
47
47
  width: 16px;
48
48
  height: 16px;
@@ -52,15 +52,15 @@
52
52
  box-shadow: var(--flx-ui-shadow-sm);
53
53
  cursor: grab;
54
54
  }
55
- :where([role='slider'][data-orientation='horizontal']) {
55
+ :where([data-slider] [role='slider'][data-orientation='horizontal']) {
56
56
  top: 50%;
57
57
  transform: translate(-50%, -50%);
58
58
  }
59
- :where([role='slider'][data-orientation='vertical']) {
59
+ :where([data-slider] [role='slider'][data-orientation='vertical']) {
60
60
  left: 50%;
61
61
  transform: translate(-50%, 50%);
62
62
  }
63
- :where([role='slider']:focus-visible) {
63
+ :where([data-slider] [role='slider']:focus-visible) {
64
64
  outline: none;
65
65
  box-shadow: var(--flx-ui-ctl-ring);
66
66
  }
@@ -0,0 +1,85 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Speed dial — a FAB trigger with actions that fan out in a direction.
4
+ * ------------------------------------------------------------------------- */
5
+ :where([data-speed-dial]) {
6
+ position: relative;
7
+ display: inline-flex;
8
+ width: max-content;
9
+ }
10
+ :where([data-speed-dial-actions]) {
11
+ position: absolute;
12
+ display: flex;
13
+ gap: var(--flx-ui-space-3);
14
+ align-items: center;
15
+ opacity: 1;
16
+ transition: opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
17
+ }
18
+ /* Off by default — the actions float bare. `filled` opts into a surface. */
19
+ :where([data-speed-dial-actions][data-filled]) {
20
+ gap: var(--flx-ui-space-2);
21
+ padding: var(--flx-ui-space-2);
22
+ background: var(--flx-ui-color-bg);
23
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
24
+ border-radius: var(--flx-ui-radius-lg);
25
+ box-shadow: var(--flx-ui-shadow-lg);
26
+ }
27
+ /* Hidden (and unfocusable) until the dial opens. */
28
+ :where([data-speed-dial][data-state='closed']) [data-speed-dial-actions] {
29
+ visibility: hidden;
30
+ opacity: 0;
31
+ pointer-events: none;
32
+ }
33
+
34
+ /* Fan-out direction, anchored to the trigger. */
35
+ :where([data-speed-dial][data-direction='up']) [data-speed-dial-actions] {
36
+ flex-direction: column-reverse;
37
+ bottom: 100%;
38
+ left: 50%;
39
+ transform: translateX(-50%);
40
+ margin-bottom: var(--flx-ui-space-3);
41
+ }
42
+ :where([data-speed-dial][data-direction='down']) [data-speed-dial-actions] {
43
+ flex-direction: column;
44
+ top: 100%;
45
+ left: 50%;
46
+ transform: translateX(-50%);
47
+ margin-top: var(--flx-ui-space-3);
48
+ }
49
+ :where([data-speed-dial][data-direction='left']) [data-speed-dial-actions] {
50
+ flex-direction: row-reverse;
51
+ right: 100%;
52
+ top: 50%;
53
+ transform: translateY(-50%);
54
+ margin-right: var(--flx-ui-space-3);
55
+ }
56
+ :where([data-speed-dial][data-direction='right']) [data-speed-dial-actions] {
57
+ flex-direction: row;
58
+ left: 100%;
59
+ top: 50%;
60
+ transform: translateY(-50%);
61
+ margin-left: var(--flx-ui-space-3);
62
+ }
63
+
64
+ /* An action + its optional label. */
65
+ :where([data-speed-dial-action-wrap]) {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: var(--flx-ui-space-2);
69
+ }
70
+ /* For vertical directions the label reads to the left of the button. */
71
+ :where([data-speed-dial][data-direction='down']) [data-speed-dial-action-wrap],
72
+ :where([data-speed-dial][data-direction='up']) [data-speed-dial-action-wrap] {
73
+ flex-direction: row-reverse;
74
+ }
75
+ :where([data-speed-dial-action-label]) {
76
+ padding: 2px var(--flx-ui-space-2);
77
+ border-radius: var(--flx-ui-radius-sm);
78
+ background: var(--flx-ui-color-fg);
79
+ color: var(--flx-ui-color-bg);
80
+ font-size: var(--flx-ui-font-size-xs);
81
+ font-weight: var(--flx-ui-font-weight-medium);
82
+ white-space: nowrap;
83
+ box-shadow: var(--flx-ui-shadow-sm);
84
+ }
85
+ }
@@ -0,0 +1,81 @@
1
+ @layer flx.components {
2
+ :where([data-splitter]) {
3
+ --flx-ui-splitter-thickness: 1px;
4
+ --flx-ui-splitter-hit-area: var(--flx-ui-space-2);
5
+
6
+ display: flex;
7
+ min-width: 0;
8
+ min-height: 0;
9
+ }
10
+
11
+ :where([data-splitter][data-orientation='horizontal']) { flex-direction: row; }
12
+ :where([data-splitter][data-orientation='vertical']) { flex-direction: column; }
13
+
14
+ /* `min-*: 0` lets a panel shrink below its content instead of forcing the
15
+ splitter wider than its container — the same rule the page body needs. */
16
+ :where([data-splitter-panel]) {
17
+ min-width: 0;
18
+ min-height: 0;
19
+ overflow: auto;
20
+ }
21
+
22
+ /*
23
+ * The divider is a hairline, but the grab area is wider — a 1px target is
24
+ * unusable with a mouse and impossible with a finger. The line is drawn by a
25
+ * pseudo-element so the hit area can be generous without looking heavy.
26
+ */
27
+ :where([data-splitter-handle]) {
28
+ position: relative;
29
+ flex: none;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ background: transparent;
34
+ border: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ :where([data-splitter-handle])::before {
39
+ content: '';
40
+ position: absolute;
41
+ background: var(--flx-ui-color-border);
42
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
43
+ }
44
+
45
+ :where([data-splitter-handle][data-orientation='horizontal']) {
46
+ width: var(--flx-ui-splitter-hit-area);
47
+ cursor: col-resize;
48
+ }
49
+ :where([data-splitter-handle][data-orientation='horizontal'])::before {
50
+ inset-block: 0;
51
+ width: var(--flx-ui-splitter-thickness);
52
+ }
53
+
54
+ :where([data-splitter-handle][data-orientation='vertical']) {
55
+ height: var(--flx-ui-splitter-hit-area);
56
+ cursor: row-resize;
57
+ }
58
+ :where([data-splitter-handle][data-orientation='vertical'])::before {
59
+ inset-inline: 0;
60
+ height: var(--flx-ui-splitter-thickness);
61
+ }
62
+
63
+ :where([data-splitter-handle]:hover)::before,
64
+ :where([data-splitter-handle][data-dragging])::before {
65
+ background: var(--flx-ui-color-primary);
66
+ }
67
+
68
+ :where([data-splitter-handle]:focus-visible) {
69
+ outline: var(--flx-ui-border-width-thick) solid var(--flx-ui-color-focus-ring);
70
+ outline-offset: -2px;
71
+ }
72
+
73
+ /* While dragging, the pointer owns the whole splitter: no text selection, and
74
+ no panel swallowing the pointer as the cursor crosses it. */
75
+ :where([data-splitter][data-dragging]) {
76
+ user-select: none;
77
+ }
78
+ :where([data-splitter][data-dragging]) :where([data-splitter-panel]) {
79
+ pointer-events: none;
80
+ }
81
+ }
@@ -0,0 +1,34 @@
1
+ @layer flx.components {
2
+ /* Statistic (from @fluixi-ui/statistic). A KPI: caption, big value, and an
3
+ optional delta line coloured by the container's data-trend. */
4
+ :where([data-statistic]) {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--flx-ui-space-1);
8
+ min-width: 0;
9
+ }
10
+ :where([data-statistic-label]) {
11
+ font-size: var(--flx-ui-font-size-xs);
12
+ font-weight: var(--flx-ui-font-weight-medium);
13
+ color: var(--flx-ui-color-fg-subtle);
14
+ letter-spacing: var(--flx-ui-letter-spacing-wide);
15
+ text-transform: uppercase;
16
+ }
17
+ :where([data-statistic-value]) {
18
+ font-size: var(--flx-ui-font-size-2xl);
19
+ font-weight: var(--flx-ui-font-weight-bold);
20
+ color: var(--flx-ui-color-fg);
21
+ line-height: 1.1;
22
+ font-variant-numeric: tabular-nums;
23
+ }
24
+ :where([data-statistic-delta]) {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: var(--flx-ui-space-1);
28
+ font-size: var(--flx-ui-font-size-xs);
29
+ font-weight: var(--flx-ui-font-weight-semibold);
30
+ color: var(--flx-ui-color-fg-subtle);
31
+ }
32
+ :where([data-statistic][data-trend='up'] [data-statistic-delta]) { color: var(--flx-ui-color-success); }
33
+ :where([data-statistic][data-trend='down'] [data-statistic-delta]) { color: var(--flx-ui-color-danger); }
34
+ }
@@ -66,9 +66,9 @@
66
66
  font-size: var(--flx-ui-font-size-sm);
67
67
  font-weight: var(--flx-ui-font-weight-medium);
68
68
  line-height: 1;
69
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
70
- border-color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
71
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
69
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
70
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
71
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
72
72
  }
73
73
  :where([data-stepper-indicator][data-state='active']) {
74
74
  border-color: var(--flx-ui-color-primary);