@fluixi-ui/tokens 0.4.0-alpha.0 → 0.4.0-alpha.10

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 (57) 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 +19 -9
  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/date-field.css +1 -1
  15. package/dist/css/date-picker.css +2 -2
  16. package/dist/css/descriptions.css +29 -0
  17. package/dist/css/drawer.css +6 -4
  18. package/dist/css/empty.css +40 -0
  19. package/dist/css/fab.css +48 -0
  20. package/dist/css/field-variants.css +139 -0
  21. package/dist/css/file-upload.css +40 -0
  22. package/dist/css/glow.css +127 -0
  23. package/dist/css/icon-button.css +42 -0
  24. package/dist/css/image.css +67 -0
  25. package/dist/css/input.css +15 -0
  26. package/dist/css/layout.css +10 -0
  27. package/dist/css/navigation-menu.css +8 -7
  28. package/dist/css/number-field.css +3 -3
  29. package/dist/css/pagination.css +3 -3
  30. package/dist/css/parts/listbox.css +9 -4
  31. package/dist/css/pin-input.css +40 -0
  32. package/dist/css/progress.css +56 -0
  33. package/dist/css/rating.css +59 -0
  34. package/dist/css/scroll-area.css +44 -0
  35. package/dist/css/select-combobox.css +19 -4
  36. package/dist/css/sidenav.css +180 -10
  37. package/dist/css/slider.css +4 -4
  38. package/dist/css/speed-dial.css +85 -0
  39. package/dist/css/statistic.css +34 -0
  40. package/dist/css/stepper.css +3 -3
  41. package/dist/css/surface.css +71 -0
  42. package/dist/css/switch.css +12 -0
  43. package/dist/css/table.css +11 -0
  44. package/dist/css/tabs.css +10 -1
  45. package/dist/css/tags-input.css +91 -0
  46. package/dist/css/timeline.css +59 -0
  47. package/dist/css/toggle.css +6 -0
  48. package/dist/css/tree-view.css +70 -0
  49. package/dist/lib/css/index.d.ts +8 -12
  50. package/dist/lib/css/index.d.ts.map +1 -1
  51. package/dist/lib/css/index.js +19 -7
  52. package/dist/lib/reference/colors.d.ts.map +1 -1
  53. package/dist/lib/reference/colors.js +15 -1
  54. package/dist/preset.css +20 -1
  55. package/dist/tokens.css +627 -0
  56. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  57. package/package.json +6 -3
@@ -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,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);
@@ -0,0 +1,71 @@
1
+ @layer flx.components {
2
+ /* ----------------------------------------------------------------------------
3
+ * Surface — an elevated container primitive. Background + radius are always set;
4
+ * elevation (shadow) and border are opt-in via data attributes, so it backs
5
+ * everything from a flat panel to a raised card or floating sheet.
6
+ * ------------------------------------------------------------------------- */
7
+ :where([data-surface]) {
8
+ background: var(--flx-ui-color-bg);
9
+ color: var(--flx-ui-color-fg);
10
+ border-radius: var(--flx-ui-radius-lg);
11
+ transition:
12
+ box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
13
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
14
+ }
15
+ :where([data-surface][data-bordered]) {
16
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
17
+ }
18
+
19
+ :where([data-surface][data-radius='none']) {
20
+ border-radius: 0;
21
+ }
22
+ :where([data-surface][data-radius='sm']) {
23
+ border-radius: var(--flx-ui-radius-sm);
24
+ }
25
+ :where([data-surface][data-radius='md']) {
26
+ border-radius: var(--flx-ui-radius-md);
27
+ }
28
+ :where([data-surface][data-radius='lg']) {
29
+ border-radius: var(--flx-ui-radius-lg);
30
+ }
31
+ :where([data-surface][data-radius='xl']) {
32
+ border-radius: var(--flx-ui-radius-xl);
33
+ }
34
+ :where([data-surface][data-radius='full']) {
35
+ border-radius: var(--flx-ui-radius-full);
36
+ }
37
+
38
+ :where([data-surface][data-elevation='0']) {
39
+ box-shadow: none;
40
+ }
41
+ :where([data-surface][data-elevation='1']) {
42
+ box-shadow: var(--flx-ui-shadow-sm);
43
+ }
44
+ :where([data-surface][data-elevation='2']) {
45
+ box-shadow: var(--flx-ui-shadow-md);
46
+ }
47
+ :where([data-surface][data-elevation='3']) {
48
+ box-shadow: var(--flx-ui-shadow-lg);
49
+ }
50
+ :where([data-surface][data-elevation='4']) {
51
+ box-shadow: var(--flx-ui-shadow-xl);
52
+ }
53
+
54
+ /* In dark, shadows barely read on a near-black page, so elevation is tonal — the
55
+ surface lightens as it rises — with a hairline edge for definition. */
56
+ [data-theme='dark'] :where([data-surface][data-elevation='1']) {
57
+ background: var(--flx-ui-gray-2);
58
+ }
59
+ [data-theme='dark'] :where([data-surface][data-elevation='2']) {
60
+ background: var(--flx-ui-gray-3);
61
+ }
62
+ [data-theme='dark'] :where([data-surface][data-elevation='3']) {
63
+ background: var(--flx-ui-gray-4);
64
+ }
65
+ [data-theme='dark'] :where([data-surface][data-elevation='4']) {
66
+ background: var(--flx-ui-gray-5);
67
+ }
68
+ [data-theme='dark'] :where([data-surface]:not([data-elevation='0']):not([data-bordered])) {
69
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
70
+ }
71
+ }
@@ -30,4 +30,16 @@
30
30
  :where([role='switch'][data-state='checked'])::after {
31
31
  transform: translateX(16px);
32
32
  }
33
+ :where([role='switch']:hover:not([data-disabled])) {
34
+ background: var(--flx-ui-color-border-strong);
35
+ filter: brightness(0.97);
36
+ }
37
+ :where([role='switch']:focus-visible) {
38
+ outline: none;
39
+ box-shadow: var(--flx-ui-ctl-ring);
40
+ }
41
+ :where([role='switch'][data-disabled]) {
42
+ opacity: var(--flx-ui-opacity-disabled);
43
+ cursor: not-allowed;
44
+ }
33
45
  }
@@ -170,4 +170,15 @@
170
170
  :where([data-pinned='right']) {
171
171
  box-shadow: inset 1px 0 0 var(--flx-ui-color-border);
172
172
  }
173
+
174
+ /* Compact density — tighter padding + smaller type for dense data tables.
175
+ * Opt in with `data-density="compact"` on the table. */
176
+ [data-table][data-density='compact'] [data-table-head],
177
+ [data-table][data-density='compact'] [data-table-cell],
178
+ [data-table][data-density='compact'] [data-table-footer] [data-table-cell] {
179
+ padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
180
+ }
181
+ [data-table][data-density='compact'] [data-table-cell] {
182
+ font-size: var(--flx-ui-font-size-xs);
183
+ }
173
184
  }
package/dist/css/tabs.css CHANGED
@@ -19,13 +19,22 @@
19
19
  margin-bottom: -1px;
20
20
  transition: color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
21
21
  }
22
- :where([role='tab']:hover) {
22
+ :where([role='tab']:hover:not([data-disabled])) {
23
23
  color: var(--flx-ui-color-fg);
24
24
  }
25
+ :where([role='tab']:focus-visible) {
26
+ outline: none;
27
+ border-radius: var(--flx-ui-radius-sm);
28
+ box-shadow: var(--flx-ui-ctl-ring);
29
+ }
25
30
  :where([role='tab'][data-state='active']) {
26
31
  color: var(--flx-ui-color-primary-active);
27
32
  border-bottom-color: var(--flx-ui-color-primary);
28
33
  }
34
+ :where([role='tab'][data-disabled]) {
35
+ opacity: var(--flx-ui-opacity-disabled);
36
+ cursor: not-allowed;
37
+ }
29
38
  :where([role='tabpanel']) {
30
39
  padding: var(--flx-ui-space-4) 0;
31
40
  }
@@ -0,0 +1,91 @@
1
+ @layer flx.components {
2
+ /* Tags input (from @fluixi-ui/tags-input) — a chip field. The container owns the
3
+ frame + focus ring; the inner input is borderless and grows to fill the row. */
4
+ :where([data-tags-input]) {
5
+ box-sizing: border-box;
6
+ display: flex;
7
+ flex-wrap: wrap;
8
+ align-items: center;
9
+ gap: var(--flx-ui-space-1);
10
+ width: 100%;
11
+ min-height: var(--flx-ui-size-md);
12
+ padding: var(--flx-ui-space-1) var(--flx-ui-space-2);
13
+ color: var(--flx-ui-color-fg);
14
+ background: var(--flx-ui-color-bg);
15
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
16
+ border-radius: var(--flx-ui-radius-md);
17
+ cursor: text;
18
+ transition:
19
+ border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
20
+ box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
21
+ }
22
+ :where([data-tags-input]:focus-within) {
23
+ border-color: var(--flx-ui-color-focus-ring);
24
+ box-shadow: var(--flx-ui-ctl-ring);
25
+ }
26
+ :where([data-tags-input][data-invalid]) {
27
+ border-color: var(--flx-ui-color-danger);
28
+ }
29
+ :where([data-tags-input][data-disabled]) {
30
+ opacity: 0.6;
31
+ cursor: not-allowed;
32
+ }
33
+
34
+ /* The editable input; borderless, inherits the field's type + colour. */
35
+ :where([data-tags-input-field]) {
36
+ flex: 1 1 6ch;
37
+ min-width: 6ch;
38
+ min-height: 0;
39
+ border: none;
40
+ outline: none;
41
+ box-shadow: none;
42
+ background: none;
43
+ font: inherit;
44
+ color: inherit;
45
+ padding: var(--flx-ui-space-1);
46
+ }
47
+ :where([data-tags-input-field]::placeholder) {
48
+ color: var(--flx-ui-color-fg-muted);
49
+ }
50
+
51
+ /* A committed tag chip. */
52
+ :where([data-tags-input-tag]) {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: var(--flx-ui-space-1);
56
+ padding: 0 var(--flx-ui-space-1) 0 var(--flx-ui-space-2);
57
+ height: calc(var(--flx-ui-size-md) - 2 * var(--flx-ui-space-2));
58
+ font-size: var(--flx-ui-font-size-sm);
59
+ color: var(--flx-ui-color-fg);
60
+ background: var(--flx-ui-color-bg-subtle);
61
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
62
+ border-radius: var(--flx-ui-radius-sm);
63
+ white-space: nowrap;
64
+ }
65
+ :where([data-tags-input-tag-remove]) {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 1.3em;
70
+ height: 1.3em;
71
+ padding: 0;
72
+ font: inherit;
73
+ line-height: 1;
74
+ color: var(--flx-ui-color-fg-muted);
75
+ background: none;
76
+ border: none;
77
+ border-radius: var(--flx-ui-radius-sm);
78
+ cursor: pointer;
79
+ transition:
80
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
81
+ background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
82
+ }
83
+ :where([data-tags-input-tag-remove]:hover) {
84
+ color: var(--flx-ui-color-fg);
85
+ background: color-mix(in oklab, var(--flx-ui-color-fg) 10%, transparent);
86
+ }
87
+ :where([data-tags-input-tag-remove]:focus-visible) {
88
+ outline: none;
89
+ box-shadow: var(--flx-ui-ctl-ring);
90
+ }
91
+ }
@@ -0,0 +1,59 @@
1
+ @layer flx.components {
2
+ /* Timeline (from @fluixi-ui/timeline). A vertical feed: each item is an indicator
3
+ column (dot) beside its content; a connector line threads between the dots. */
4
+ :where([data-timeline]) {
5
+ display: flex;
6
+ flex-direction: column;
7
+ }
8
+ :where([data-timeline-item]) {
9
+ display: grid;
10
+ grid-template-columns: auto 1fr;
11
+ column-gap: var(--flx-ui-space-3);
12
+ --flx-timeline-dot: var(--flx-ui-color-border-strong);
13
+ }
14
+ :where([data-timeline-item][data-tone='primary']),
15
+ :where([data-timeline-item][data-tone='info']) { --flx-timeline-dot: var(--flx-ui-color-primary); }
16
+ :where([data-timeline-item][data-tone='success']) { --flx-timeline-dot: var(--flx-ui-color-success); }
17
+ :where([data-timeline-item][data-tone='warning']) { --flx-timeline-dot: var(--flx-ui-color-warning); }
18
+ :where([data-timeline-item][data-tone='danger']) { --flx-timeline-dot: var(--flx-ui-color-danger); }
19
+
20
+ /* Indicator column: the dot, with the connector line drawn below it. */
21
+ :where([data-timeline-indicator]) {
22
+ position: relative;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ width: 18px;
27
+ padding-top: 2px;
28
+ color: var(--flx-timeline-dot);
29
+ }
30
+ /* Default dot (when no custom `dot` content). */
31
+ :where([data-timeline-indicator]:empty)::before {
32
+ content: "";
33
+ width: 9px;
34
+ height: 9px;
35
+ border-radius: var(--flx-ui-radius-full);
36
+ background: var(--flx-timeline-dot);
37
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--flx-timeline-dot) 18%, transparent);
38
+ }
39
+ /* Connector line — hidden on the last item. */
40
+ :where([data-timeline-item]:not(:last-child) [data-timeline-indicator])::after {
41
+ content: "";
42
+ position: absolute;
43
+ top: 16px;
44
+ bottom: -6px;
45
+ left: 50%;
46
+ width: var(--flx-ui-border-width-thin);
47
+ transform: translateX(-50%);
48
+ background: var(--flx-ui-color-border);
49
+ }
50
+ :where([data-timeline-content]) {
51
+ min-width: 0;
52
+ padding-bottom: var(--flx-ui-space-4);
53
+ font-size: var(--flx-ui-font-size-sm);
54
+ color: var(--flx-ui-color-fg);
55
+ }
56
+ :where([data-timeline-item]:last-child [data-timeline-content]) {
57
+ padding-bottom: 0;
58
+ }
59
+ }