@fluixi-ui/tokens 0.4.0-alpha.8 → 0.4.0-alpha.9
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/dist/css/avatar.css +19 -0
- package/dist/css/badge.css +128 -5
- package/dist/css/bulk-editor.css +2 -17
- package/dist/css/button.css +38 -16
- package/dist/css/descriptions.css +29 -0
- package/dist/css/empty.css +40 -0
- package/dist/css/fab.css +48 -0
- package/dist/css/field-variants.css +139 -0
- package/dist/css/glow.css +12 -8
- package/dist/css/icon-button.css +42 -0
- package/dist/css/image.css +42 -10
- package/dist/css/layout.css +10 -0
- package/dist/css/parts/listbox.css +9 -4
- package/dist/css/pin-input.css +40 -0
- package/dist/css/progress.css +56 -0
- package/dist/css/rating.css +59 -0
- package/dist/css/scroll-area.css +44 -0
- package/dist/css/slider.css +4 -4
- package/dist/css/speed-dial.css +85 -0
- package/dist/css/statistic.css +34 -0
- package/dist/css/timeline.css +59 -0
- package/dist/lib/reference/colors.d.ts.map +1 -1
- package/dist/lib/reference/colors.js +14 -0
- package/dist/preset.css +12 -0
- package/dist/tokens.css +40 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/css/avatar.css
CHANGED
|
@@ -33,4 +33,23 @@
|
|
|
33
33
|
width: 100%;
|
|
34
34
|
height: 100%;
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/* ----------------------------------------------------------------------------
|
|
38
|
+
* Avatar group — overlapping cluster
|
|
39
|
+
* ------------------------------------------------------------------------- */
|
|
40
|
+
:where([data-avatar-group]) {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
vertical-align: middle;
|
|
44
|
+
}
|
|
45
|
+
:where([data-avatar-group]) > * {
|
|
46
|
+
/* A ring in the page background so the overlap reads as separate discs. */
|
|
47
|
+
box-shadow: 0 0 0 2px var(--flx-ui-color-bg);
|
|
48
|
+
}
|
|
49
|
+
:where([data-avatar-group]) > * + * {
|
|
50
|
+
margin-inline-start: calc(-1 * var(--flx-ui-avatar-group-spacing, 8px));
|
|
51
|
+
}
|
|
52
|
+
:where([data-avatar-group-overflow]) {
|
|
53
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
54
|
+
}
|
|
36
55
|
}
|
package/dist/css/badge.css
CHANGED
|
@@ -1,13 +1,136 @@
|
|
|
1
1
|
@layer flx.components {
|
|
2
|
-
/* Badge
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/* Badge (from @fluixi-ui/badge). A small status/label pill: a `data-variant` fill
|
|
3
|
+
style (soft/solid/outline) crossed with a semantic `data-tone`. Each tone sets
|
|
4
|
+
three custom props — the strong colour, the soft background, and the on-solid
|
|
5
|
+
foreground — and the variants consume them, so tone × variant needs no per-combo
|
|
6
|
+
rules. */
|
|
7
|
+
:where([data-badge]) {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: var(--flx-ui-space-1);
|
|
5
11
|
min-width: 0;
|
|
6
|
-
|
|
12
|
+
/* A compact chip, not a button: tight vertical padding + line-height so the
|
|
13
|
+
height tracks the text, no wide tracking. */
|
|
14
|
+
padding: 1px var(--flx-ui-space-2);
|
|
7
15
|
border-radius: var(--flx-ui-radius-full);
|
|
8
16
|
font-size: var(--flx-ui-font-size-xs);
|
|
9
17
|
font-weight: var(--flx-ui-font-weight-semibold);
|
|
10
|
-
|
|
18
|
+
line-height: 1.35;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
border: var(--flx-ui-border-width-thin) solid transparent;
|
|
11
21
|
box-shadow: none;
|
|
22
|
+
/* Each tone sets four props: `color` (strong — solid fill / outline border),
|
|
23
|
+
`text` (a legible-on-tint shade — soft/outline label), `bg` (soft fill) and
|
|
24
|
+
`on` (label on the solid fill). neutral defaults: */
|
|
25
|
+
--flx-ui-badge-color: var(--flx-ui-color-fg-muted);
|
|
26
|
+
--flx-ui-badge-text: var(--flx-ui-color-fg-muted);
|
|
27
|
+
--flx-ui-badge-bg: var(--flx-ui-color-bg-muted);
|
|
28
|
+
--flx-ui-badge-on: var(--flx-ui-color-bg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
:where([data-badge][data-tone='primary']),
|
|
32
|
+
:where([data-badge][data-tone='info']) {
|
|
33
|
+
--flx-ui-badge-color: var(--flx-ui-color-primary);
|
|
34
|
+
--flx-ui-badge-text: var(--flx-ui-color-primary-text);
|
|
35
|
+
--flx-ui-badge-bg: var(--flx-ui-color-primary-subtle);
|
|
36
|
+
--flx-ui-badge-on: var(--flx-ui-color-primary-fg);
|
|
37
|
+
}
|
|
38
|
+
:where([data-badge][data-tone='success']) {
|
|
39
|
+
--flx-ui-badge-color: var(--flx-ui-color-success);
|
|
40
|
+
--flx-ui-badge-text: var(--flx-ui-color-success-text);
|
|
41
|
+
--flx-ui-badge-bg: var(--flx-ui-color-success-subtle);
|
|
42
|
+
--flx-ui-badge-on: var(--flx-ui-color-success-fg);
|
|
43
|
+
}
|
|
44
|
+
:where([data-badge][data-tone='warning']) {
|
|
45
|
+
--flx-ui-badge-color: var(--flx-ui-color-warning);
|
|
46
|
+
--flx-ui-badge-text: var(--flx-ui-color-warning-text);
|
|
47
|
+
--flx-ui-badge-bg: var(--flx-ui-color-warning-subtle);
|
|
48
|
+
--flx-ui-badge-on: var(--flx-ui-color-warning-fg);
|
|
49
|
+
}
|
|
50
|
+
:where([data-badge][data-tone='danger']) {
|
|
51
|
+
--flx-ui-badge-color: var(--flx-ui-color-danger);
|
|
52
|
+
--flx-ui-badge-text: var(--flx-ui-color-danger-text);
|
|
53
|
+
--flx-ui-badge-bg: var(--flx-ui-color-danger-subtle);
|
|
54
|
+
--flx-ui-badge-on: var(--flx-ui-color-danger-fg);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:where([data-badge][data-variant='soft']) {
|
|
58
|
+
background: var(--flx-ui-badge-bg);
|
|
59
|
+
color: var(--flx-ui-badge-text);
|
|
60
|
+
}
|
|
61
|
+
:where([data-badge][data-variant='solid']) {
|
|
62
|
+
background: var(--flx-ui-badge-color);
|
|
63
|
+
color: var(--flx-ui-badge-on);
|
|
64
|
+
}
|
|
65
|
+
:where([data-badge][data-variant='outline']) {
|
|
66
|
+
color: var(--flx-ui-badge-text);
|
|
67
|
+
border-color: var(--flx-ui-badge-color);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ----------------------------------------------------------------------------
|
|
71
|
+
* Overlay badge (BadgeAnchor) — a count / dot pinned to a corner of a wrapped
|
|
72
|
+
* element (a Material-style notification badge). Reuses the [data-badge] fill.
|
|
73
|
+
* ------------------------------------------------------------------------- */
|
|
74
|
+
:where([data-badge-anchor]) {
|
|
75
|
+
position: relative;
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
vertical-align: middle;
|
|
78
|
+
}
|
|
79
|
+
:where([data-badge][data-overlay]) {
|
|
80
|
+
position: absolute;
|
|
81
|
+
z-index: 1;
|
|
82
|
+
min-width: 1.25rem;
|
|
83
|
+
height: 1.25rem;
|
|
84
|
+
padding: 0 var(--flx-ui-space-1);
|
|
85
|
+
justify-content: center;
|
|
86
|
+
font-size: var(--flx-ui-font-size-2xs, 0.6875rem);
|
|
87
|
+
font-variant-numeric: tabular-nums;
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
/* A surface-coloured ring so the badge reads as floating above the element. */
|
|
90
|
+
box-shadow: 0 0 0 2px var(--flx-ui-color-bg);
|
|
91
|
+
}
|
|
92
|
+
:where([data-badge][data-overlay][data-dot]) {
|
|
93
|
+
min-width: 0;
|
|
94
|
+
width: 0.5rem;
|
|
95
|
+
height: 0.5rem;
|
|
96
|
+
padding: 0;
|
|
97
|
+
}
|
|
98
|
+
:where([data-badge-anchor][data-placement='top-end']) > [data-overlay] {
|
|
99
|
+
top: 0;
|
|
100
|
+
right: 0;
|
|
101
|
+
transform: translate(50%, -50%);
|
|
102
|
+
}
|
|
103
|
+
:where([data-badge-anchor][data-placement='top-start']) > [data-overlay] {
|
|
104
|
+
top: 0;
|
|
105
|
+
left: 0;
|
|
106
|
+
transform: translate(-50%, -50%);
|
|
107
|
+
}
|
|
108
|
+
:where([data-badge-anchor][data-placement='bottom-end']) > [data-overlay] {
|
|
109
|
+
bottom: 0;
|
|
110
|
+
right: 0;
|
|
111
|
+
transform: translate(50%, 50%);
|
|
112
|
+
}
|
|
113
|
+
:where([data-badge-anchor][data-placement='bottom-start']) > [data-overlay] {
|
|
114
|
+
bottom: 0;
|
|
115
|
+
left: 0;
|
|
116
|
+
transform: translate(-50%, 50%);
|
|
117
|
+
}
|
|
118
|
+
/* Circular anchor (avatar / circle): nudge the badge in along the diagonal so it
|
|
119
|
+
sits on the round edge instead of the bounding-box corner. */
|
|
120
|
+
:where([data-badge-anchor][data-overlap='circular'][data-placement='top-end']) > [data-overlay] {
|
|
121
|
+
top: 14%;
|
|
122
|
+
right: 14%;
|
|
123
|
+
}
|
|
124
|
+
:where([data-badge-anchor][data-overlap='circular'][data-placement='top-start']) > [data-overlay] {
|
|
125
|
+
top: 14%;
|
|
126
|
+
left: 14%;
|
|
127
|
+
}
|
|
128
|
+
:where([data-badge-anchor][data-overlap='circular'][data-placement='bottom-end']) > [data-overlay] {
|
|
129
|
+
bottom: 14%;
|
|
130
|
+
right: 14%;
|
|
131
|
+
}
|
|
132
|
+
:where([data-badge-anchor][data-overlap='circular'][data-placement='bottom-start']) > [data-overlay] {
|
|
133
|
+
bottom: 14%;
|
|
134
|
+
left: 14%;
|
|
12
135
|
}
|
|
13
136
|
}
|
package/dist/css/bulk-editor.css
CHANGED
|
@@ -183,12 +183,8 @@
|
|
|
183
183
|
align-items: center;
|
|
184
184
|
justify-content: center;
|
|
185
185
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
height: 15px;
|
|
189
|
-
cursor: pointer;
|
|
190
|
-
accent-color: var(--flx-ui-color-primary);
|
|
191
|
-
}
|
|
186
|
+
/* Selection checkboxes render the <Checkbox> component ([role='checkbox']),
|
|
187
|
+
which styles itself; the wrap just centres it in the cell. */
|
|
192
188
|
|
|
193
189
|
/* Fill handle on the active cell */
|
|
194
190
|
:where(.flx-ui-be-fill-handle) {
|
|
@@ -406,17 +402,6 @@
|
|
|
406
402
|
text-align: left;
|
|
407
403
|
}
|
|
408
404
|
|
|
409
|
-
/* Bulk editor — column header select-all checkbox */
|
|
410
|
-
:where(.flx-ui-be-head-check) {
|
|
411
|
-
width: 14px;
|
|
412
|
-
height: 14px;
|
|
413
|
-
cursor: pointer;
|
|
414
|
-
accent-color: var(--flx-ui-color-primary);
|
|
415
|
-
flex-shrink: 0;
|
|
416
|
-
}
|
|
417
|
-
:where(.flx-ui-be-check:disabled) {
|
|
418
|
-
cursor: not-allowed;
|
|
419
|
-
}
|
|
420
405
|
|
|
421
406
|
/* Bulk editor — column resize grip */
|
|
422
407
|
:where(.flx-ui-be-col-resize) {
|
package/dist/css/button.css
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
@layer flx.components {
|
|
2
2
|
/* ----------------------------------------------------------------------------
|
|
3
|
-
* Button (and any [data-variant] control, e.g. dialog triggers)
|
|
3
|
+
* Button (and any [data-variant] control, e.g. dialog triggers). Badges also
|
|
4
|
+
* carry [data-variant], so every rule here excludes [data-badge] — a badge must
|
|
5
|
+
* never pick up button sizing/colour.
|
|
4
6
|
* ------------------------------------------------------------------------- */
|
|
5
7
|
|
|
6
8
|
/* Tone (data-tone) is orthogonal to variant: it remaps the button's own
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
--flx-ui-color-primary-fg: var(--flx-ui-color-bg);
|
|
36
38
|
--flx-ui-color-primary-subtle: var(--flx-ui-color-bg-muted);
|
|
37
39
|
}
|
|
38
|
-
:where([data-variant]) {
|
|
40
|
+
:where([data-variant]:not([data-badge])) {
|
|
39
41
|
box-sizing: border-box;
|
|
40
42
|
display: inline-flex;
|
|
41
43
|
align-items: center;
|
|
@@ -63,13 +65,13 @@
|
|
|
63
65
|
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
64
66
|
box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
65
67
|
}
|
|
66
|
-
:where([data-variant][data-size='sm']) {
|
|
68
|
+
:where([data-variant]:not([data-badge])[data-size='sm']) {
|
|
67
69
|
height: var(--flx-ui-size-sm);
|
|
68
70
|
min-width: var(--flx-ui-size-sm);
|
|
69
71
|
padding: 0 var(--flx-ui-space-3);
|
|
70
72
|
font-size: var(--flx-ui-font-size-sm);
|
|
71
73
|
}
|
|
72
|
-
:where([data-variant][data-size='lg']) {
|
|
74
|
+
:where([data-variant]:not([data-badge])[data-size='lg']) {
|
|
73
75
|
height: var(--flx-ui-size-lg);
|
|
74
76
|
min-width: var(--flx-ui-size-lg);
|
|
75
77
|
padding: 0 var(--flx-ui-space-5);
|
|
@@ -78,41 +80,42 @@
|
|
|
78
80
|
|
|
79
81
|
/* Solid: the primary action — a flat, confident accent. Depth comes from the
|
|
80
82
|
color step change on interaction, not from shadow or sheen. */
|
|
81
|
-
:where([data-variant='solid']) {
|
|
83
|
+
:where([data-variant='solid']:not([data-badge])) {
|
|
82
84
|
background: var(--flx-ui-color-primary);
|
|
83
85
|
color: var(--flx-ui-color-primary-fg);
|
|
84
86
|
}
|
|
85
|
-
:where([data-variant='solid']:hover:not([data-disabled])) {
|
|
87
|
+
:where([data-variant='solid']:not([data-badge]):hover:not([data-disabled])) {
|
|
86
88
|
background: var(--flx-ui-color-primary-hover);
|
|
87
89
|
}
|
|
88
|
-
:where([data-variant='solid']:active:not([data-disabled])) {
|
|
90
|
+
:where([data-variant='solid']:not([data-badge]):active:not([data-disabled])) {
|
|
89
91
|
background: var(--flx-ui-color-primary-active);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
/* Soft: a tinted, low-key primary.
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
/* Soft: a tinted, low-key primary. Deepened a touch over the bare subtle tint so
|
|
95
|
+
it carries enough weight to read as a control, then steps up on hover. */
|
|
96
|
+
:where([data-variant='soft']:not([data-badge])) {
|
|
97
|
+
background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary) 10%);
|
|
95
98
|
color: var(--flx-ui-color-primary-active);
|
|
96
99
|
}
|
|
97
|
-
:where([data-variant='soft']:hover:not([data-disabled])) {
|
|
98
|
-
background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary)
|
|
100
|
+
:where([data-variant='soft']:not([data-badge]):hover:not([data-disabled])) {
|
|
101
|
+
background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary) 20%);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/* Outline: a flat hairline-bordered control. */
|
|
102
|
-
:where([data-variant='outline']) {
|
|
105
|
+
:where([data-variant='outline']:not([data-badge])) {
|
|
103
106
|
border-color: var(--flx-ui-color-border-strong);
|
|
104
107
|
background: var(--flx-ui-color-bg);
|
|
105
108
|
}
|
|
106
|
-
:where([data-variant='outline']:hover:not([data-disabled])) {
|
|
109
|
+
:where([data-variant='outline']:not([data-badge]):hover:not([data-disabled])) {
|
|
107
110
|
background: var(--flx-ui-color-bg-subtle);
|
|
108
111
|
border-color: var(--flx-ui-color-fg-muted);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/* Ghost: text-only until hovered. */
|
|
112
|
-
:where([data-variant='ghost']) {
|
|
115
|
+
:where([data-variant='ghost']:not([data-badge])) {
|
|
113
116
|
color: var(--flx-ui-color-fg);
|
|
114
117
|
}
|
|
115
|
-
:where([data-variant='ghost']:hover:not([data-disabled])) {
|
|
118
|
+
:where([data-variant='ghost']:not([data-badge]):hover:not([data-disabled])) {
|
|
116
119
|
background: var(--flx-ui-color-bg-muted);
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -121,6 +124,25 @@
|
|
|
121
124
|
cursor: not-allowed;
|
|
122
125
|
pointer-events: none;
|
|
123
126
|
}
|
|
127
|
+
/* Buttons get a flat, neutral disabled instead of a dimmed accent (which reads
|
|
128
|
+
muddy). Filled variants drop to a grey fill; outline/ghost stay flat. */
|
|
129
|
+
:where([data-variant]:not([data-badge])[data-disabled]) {
|
|
130
|
+
opacity: 1;
|
|
131
|
+
box-shadow: none;
|
|
132
|
+
color: var(--flx-ui-color-fg-muted);
|
|
133
|
+
}
|
|
134
|
+
:where([data-variant='solid']:not([data-badge])[data-disabled]),
|
|
135
|
+
:where([data-variant='soft']:not([data-badge])[data-disabled]) {
|
|
136
|
+
background: var(--flx-ui-color-bg-muted);
|
|
137
|
+
border-color: transparent;
|
|
138
|
+
}
|
|
139
|
+
:where([data-variant='outline']:not([data-badge])[data-disabled]) {
|
|
140
|
+
background: transparent;
|
|
141
|
+
border-color: var(--flx-ui-color-border);
|
|
142
|
+
}
|
|
143
|
+
:where([data-variant='ghost']:not([data-badge])[data-disabled]) {
|
|
144
|
+
background: transparent;
|
|
145
|
+
}
|
|
124
146
|
|
|
125
147
|
/* The signature focus ring: a surface-colored gap then an accent halo. */
|
|
126
148
|
:where(:focus-visible) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Descriptions (from @fluixi-ui/descriptions). A key/value grid; `columns` drives
|
|
3
|
+
the track count and an item's `span` stretches it. */
|
|
4
|
+
:where([data-descriptions]) {
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: repeat(var(--flx-descriptions-columns, 1), minmax(0, 1fr));
|
|
7
|
+
gap: var(--flx-ui-space-4) var(--flx-ui-space-6);
|
|
8
|
+
margin: 0;
|
|
9
|
+
}
|
|
10
|
+
:where([data-descriptions-item]) {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: var(--flx-ui-space-1);
|
|
14
|
+
min-width: 0;
|
|
15
|
+
}
|
|
16
|
+
:where([data-descriptions-term]) {
|
|
17
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
18
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
19
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
20
|
+
letter-spacing: var(--flx-ui-letter-spacing-wide);
|
|
21
|
+
}
|
|
22
|
+
:where([data-descriptions-detail]) {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
25
|
+
color: var(--flx-ui-color-fg);
|
|
26
|
+
min-width: 0;
|
|
27
|
+
overflow-wrap: anywhere;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Empty (from @fluixi-ui/empty). A centred empty-state: icon tile, title,
|
|
3
|
+
description, and an actions row. */
|
|
4
|
+
:where([data-empty]) {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
text-align: center;
|
|
10
|
+
gap: var(--flx-ui-space-2);
|
|
11
|
+
padding: var(--flx-ui-space-8) var(--flx-ui-space-4);
|
|
12
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
13
|
+
}
|
|
14
|
+
:where([data-empty-icon]) {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
width: 44px;
|
|
19
|
+
height: 44px;
|
|
20
|
+
margin-bottom: var(--flx-ui-space-1);
|
|
21
|
+
border-radius: var(--flx-ui-radius-lg);
|
|
22
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
23
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
24
|
+
}
|
|
25
|
+
:where([data-empty-title]) {
|
|
26
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
27
|
+
font-weight: var(--flx-ui-font-weight-semibold);
|
|
28
|
+
color: var(--flx-ui-color-fg);
|
|
29
|
+
}
|
|
30
|
+
:where([data-empty-description]) {
|
|
31
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
32
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
33
|
+
max-width: 42ch;
|
|
34
|
+
}
|
|
35
|
+
:where([data-empty-actions]) {
|
|
36
|
+
margin-top: var(--flx-ui-space-3);
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: var(--flx-ui-space-2);
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/css/fab.css
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* FAB — a circular, elevated [data-variant] button. Colours / states / ripple
|
|
4
|
+
* come from the button slice + skin; this owns size, shape and elevation.
|
|
5
|
+
* ------------------------------------------------------------------------- */
|
|
6
|
+
:where([data-fab]) {
|
|
7
|
+
width: 56px;
|
|
8
|
+
height: 56px;
|
|
9
|
+
padding: 0;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
flex: none;
|
|
12
|
+
border-radius: var(--flx-ui-radius-full);
|
|
13
|
+
box-shadow: var(--flx-ui-shadow-lg);
|
|
14
|
+
}
|
|
15
|
+
:where([data-fab]:hover:not([data-disabled])) {
|
|
16
|
+
box-shadow: var(--flx-ui-shadow-xl);
|
|
17
|
+
}
|
|
18
|
+
:where([data-fab][data-fab-size='sm']) {
|
|
19
|
+
width: 40px;
|
|
20
|
+
height: 40px;
|
|
21
|
+
}
|
|
22
|
+
:where([data-fab][data-fab-size='lg']) {
|
|
23
|
+
width: 64px;
|
|
24
|
+
height: 64px;
|
|
25
|
+
}
|
|
26
|
+
:where([data-fab] svg) {
|
|
27
|
+
width: 24px;
|
|
28
|
+
height: 24px;
|
|
29
|
+
}
|
|
30
|
+
:where([data-fab][data-fab-size='sm'] svg) {
|
|
31
|
+
width: 20px;
|
|
32
|
+
height: 20px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Extended: a pill holding an icon plus a label. */
|
|
36
|
+
:where([data-fab][data-fab-extended]) {
|
|
37
|
+
width: auto;
|
|
38
|
+
min-width: 56px;
|
|
39
|
+
padding: 0 var(--flx-ui-space-5);
|
|
40
|
+
gap: var(--flx-ui-space-2);
|
|
41
|
+
border-radius: var(--flx-ui-radius-full);
|
|
42
|
+
font-weight: var(--flx-ui-font-weight-semibold);
|
|
43
|
+
}
|
|
44
|
+
:where([data-fab][data-fab-extended][data-fab-size='sm']) {
|
|
45
|
+
min-width: 40px;
|
|
46
|
+
padding: 0 var(--flx-ui-space-4);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Field variants — an appearance axis shared by every field control that opts in
|
|
4
|
+
* via `data-field-variant` (Input, the Select trigger `button[role=combobox]`,
|
|
5
|
+
* the Combobox control, a field-styled dropdown trigger). `default` (no attr)
|
|
6
|
+
* keeps each skin's baseline; `outlined` and `filled` are explicit Material-style
|
|
7
|
+
* modes that every skin renders.
|
|
8
|
+
*
|
|
9
|
+
* These deliberately DON'T use `:where()` — the `[data-field-variant]` attribute
|
|
10
|
+
* carries a little specificity so the variant wins over a control's own baseline
|
|
11
|
+
* slice regardless of import order (per-component self-imports dedupe to the
|
|
12
|
+
* first position). Consumer CSS is unlayered and still wins over this whole
|
|
13
|
+
* layer; the skins (a higher @layer) still override per skin.
|
|
14
|
+
* ------------------------------------------------------------------------- */
|
|
15
|
+
|
|
16
|
+
/* Give a plain trigger button (not the select's role=combobox, which already has
|
|
17
|
+
a field box) the field box model when it opts into a variant. */
|
|
18
|
+
button[data-field-variant]:not([role='combobox']) {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--flx-ui-space-2);
|
|
23
|
+
min-height: var(--flx-ui-size-md);
|
|
24
|
+
padding: 0 var(--flx-ui-space-3);
|
|
25
|
+
font: inherit;
|
|
26
|
+
color: var(--flx-ui-color-fg);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition:
|
|
29
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
30
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
31
|
+
box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Outlined — a bordered box on a transparent ground, with a more defined border
|
|
35
|
+
than the baseline so the two read as distinct. */
|
|
36
|
+
[data-field-variant='outlined'] {
|
|
37
|
+
background: transparent;
|
|
38
|
+
border: var(--flx-ui-border-width-thin) solid
|
|
39
|
+
color-mix(in oklab, var(--flx-ui-color-border-strong), var(--flx-ui-color-fg) 22%);
|
|
40
|
+
border-radius: var(--flx-ui-radius-md);
|
|
41
|
+
}
|
|
42
|
+
[data-field-variant='outlined']:hover:not(:focus):not(:focus-within):not([data-invalid]) {
|
|
43
|
+
border-color: var(--flx-ui-color-fg-muted);
|
|
44
|
+
}
|
|
45
|
+
[data-field-variant='outlined']:focus,
|
|
46
|
+
[data-field-variant='outlined']:focus-within {
|
|
47
|
+
outline: none;
|
|
48
|
+
border-color: var(--flx-ui-color-focus-ring);
|
|
49
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
50
|
+
}
|
|
51
|
+
[data-field-variant='outlined'][data-invalid] {
|
|
52
|
+
border-color: var(--flx-ui-color-danger);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Filled — a tinted fill with a bottom rule that thickens/colours on focus. */
|
|
56
|
+
[data-field-variant='filled'] {
|
|
57
|
+
background: var(--flx-ui-color-bg-muted);
|
|
58
|
+
border: var(--flx-ui-border-width-thin) solid transparent;
|
|
59
|
+
border-bottom-color: var(--flx-ui-color-border-strong);
|
|
60
|
+
border-radius: var(--flx-ui-radius-md) var(--flx-ui-radius-md) 0 0;
|
|
61
|
+
}
|
|
62
|
+
[data-field-variant='filled']:hover:not(:focus):not(:focus-within):not([data-invalid]) {
|
|
63
|
+
background: color-mix(in oklab, var(--flx-ui-color-bg-muted), var(--flx-ui-color-fg) 4%);
|
|
64
|
+
}
|
|
65
|
+
[data-field-variant='filled']:focus,
|
|
66
|
+
[data-field-variant='filled']:focus-within {
|
|
67
|
+
outline: none;
|
|
68
|
+
border-bottom-width: 2px;
|
|
69
|
+
border-bottom-color: var(--flx-ui-color-focus-ring);
|
|
70
|
+
box-shadow: none;
|
|
71
|
+
}
|
|
72
|
+
[data-field-variant='filled'][data-invalid] {
|
|
73
|
+
border-bottom-color: var(--flx-ui-color-danger);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ----------------------------------------------------------------------------
|
|
77
|
+
* Floating label — a frameless positioning wrapper. The INPUT keeps its own
|
|
78
|
+
* frame (base + variant + skin), so there's exactly one border and it always
|
|
79
|
+
* matches the active skin. The label overlays and lifts into the top strip on
|
|
80
|
+
* focus / when filled (driven by the input's :placeholder-shown). The input is
|
|
81
|
+
* taller with bottom-weighted text so the lifted label never overlaps the value.
|
|
82
|
+
* ------------------------------------------------------------------------- */
|
|
83
|
+
[data-field-float] {
|
|
84
|
+
position: relative;
|
|
85
|
+
display: block;
|
|
86
|
+
width: 100%;
|
|
87
|
+
}
|
|
88
|
+
[data-field-float] > [data-input] {
|
|
89
|
+
width: 100%;
|
|
90
|
+
min-height: calc(var(--flx-ui-size-md) + 14px);
|
|
91
|
+
padding-top: 20px;
|
|
92
|
+
padding-bottom: 4px;
|
|
93
|
+
}
|
|
94
|
+
/* Outlined notches the top border (label rides on the edge), so the value text
|
|
95
|
+
stays vertically centred rather than pushed down for an inside label. */
|
|
96
|
+
[data-field-float] > [data-input][data-field-variant='outlined'] {
|
|
97
|
+
padding-top: var(--flx-ui-space-2);
|
|
98
|
+
padding-bottom: var(--flx-ui-space-2);
|
|
99
|
+
}
|
|
100
|
+
[data-field-float] > [data-field-float-label] {
|
|
101
|
+
position: absolute;
|
|
102
|
+
left: var(--flx-ui-space-3);
|
|
103
|
+
top: 50%;
|
|
104
|
+
transform: translateY(-50%);
|
|
105
|
+
transform-origin: left top;
|
|
106
|
+
max-width: calc(100% - 2 * var(--flx-ui-space-3));
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
text-overflow: ellipsis;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
color: var(--flx-ui-color-fg-muted);
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
transition:
|
|
113
|
+
top var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
114
|
+
transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
115
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
116
|
+
}
|
|
117
|
+
/* Lift to the top strip when focused or filled (default + filled: inside). */
|
|
118
|
+
[data-field-float] > [data-input]:focus ~ [data-field-float-label],
|
|
119
|
+
[data-field-float] > [data-input]:not(:placeholder-shown) ~ [data-field-float-label] {
|
|
120
|
+
top: 8px;
|
|
121
|
+
transform: translateY(0) scale(0.8);
|
|
122
|
+
}
|
|
123
|
+
/* Outlined lifts ONTO the top border and notches it (paints the surface behind
|
|
124
|
+
the label). Only outlined is reliably a transparent bordered box across skins. */
|
|
125
|
+
[data-field-float] > [data-input][data-field-variant='outlined']:focus ~ [data-field-float-label],
|
|
126
|
+
[data-field-float] > [data-input][data-field-variant='outlined']:not(:placeholder-shown) ~ [data-field-float-label] {
|
|
127
|
+
top: 0;
|
|
128
|
+
transform: translateY(-50%) scale(0.8);
|
|
129
|
+
padding: 0 var(--flx-ui-space-1);
|
|
130
|
+
background: var(--flx-ui-color-bg);
|
|
131
|
+
}
|
|
132
|
+
/* Accent the label only while the field is focused. */
|
|
133
|
+
[data-field-float] > [data-input]:focus ~ [data-field-float-label] {
|
|
134
|
+
color: var(--flx-ui-color-focus-ring);
|
|
135
|
+
}
|
|
136
|
+
[data-field-float] > [data-input][data-invalid] ~ [data-field-float-label] {
|
|
137
|
+
color: var(--flx-ui-color-danger);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/dist/css/glow.css
CHANGED
|
@@ -50,19 +50,23 @@
|
|
|
50
50
|
:where([data-glow][data-glow-side])::after {
|
|
51
51
|
animation: none;
|
|
52
52
|
background-repeat: no-repeat;
|
|
53
|
+
/* A soft resting glow so the lit edge reads as intentional even before hover. */
|
|
54
|
+
filter: drop-shadow(0 0 2px var(--flx-glow-via, var(--flx-ui-color-primary)));
|
|
53
55
|
transition:
|
|
54
56
|
background-position 450ms var(--flx-ui-ease-standard),
|
|
55
57
|
filter var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
|
|
56
58
|
}
|
|
59
|
+
/* Wider colour plateau (12%→88%) so the whole edge lights evenly and only the
|
|
60
|
+
far edge fades, rather than a thin central sliver. */
|
|
57
61
|
:where([data-glow][data-glow-side='top'])::after,
|
|
58
62
|
:where([data-glow][data-glow-side='bottom'])::after {
|
|
59
63
|
background-image: linear-gradient(
|
|
60
64
|
to bottom,
|
|
61
|
-
transparent
|
|
62
|
-
var(--flx-glow-from, var(--flx-ui-color-primary))
|
|
65
|
+
transparent 12%,
|
|
66
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 38%,
|
|
63
67
|
var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
|
|
64
|
-
var(--flx-glow-from, var(--flx-ui-color-primary))
|
|
65
|
-
transparent
|
|
68
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 62%,
|
|
69
|
+
transparent 88%
|
|
66
70
|
);
|
|
67
71
|
background-size: 100% 200%;
|
|
68
72
|
}
|
|
@@ -70,11 +74,11 @@
|
|
|
70
74
|
:where([data-glow][data-glow-side='right'])::after {
|
|
71
75
|
background-image: linear-gradient(
|
|
72
76
|
to right,
|
|
73
|
-
transparent
|
|
74
|
-
var(--flx-glow-from, var(--flx-ui-color-primary))
|
|
77
|
+
transparent 12%,
|
|
78
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 38%,
|
|
75
79
|
var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
|
|
76
|
-
var(--flx-glow-from, var(--flx-ui-color-primary))
|
|
77
|
-
transparent
|
|
80
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 62%,
|
|
81
|
+
transparent 88%
|
|
78
82
|
);
|
|
79
83
|
background-size: 200% 100%;
|
|
80
84
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Icon button — a square [data-variant] button. Colours / states / ripple come
|
|
4
|
+
* from the button slice + the active skin; this only makes it square and shapes
|
|
5
|
+
* the corners.
|
|
6
|
+
* ------------------------------------------------------------------------- */
|
|
7
|
+
:where([data-icon-button]) {
|
|
8
|
+
width: var(--flx-ui-size-md);
|
|
9
|
+
padding: 0;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
flex: none;
|
|
12
|
+
}
|
|
13
|
+
:where([data-icon-button][data-size='sm']) {
|
|
14
|
+
width: var(--flx-ui-size-sm);
|
|
15
|
+
}
|
|
16
|
+
:where([data-icon-button][data-size='lg']) {
|
|
17
|
+
width: var(--flx-ui-size-lg);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Shape */
|
|
21
|
+
:where([data-icon-button][data-shape='circle']) {
|
|
22
|
+
border-radius: var(--flx-ui-radius-full);
|
|
23
|
+
}
|
|
24
|
+
:where([data-icon-button][data-shape='square']) {
|
|
25
|
+
border-radius: var(--flx-ui-radius-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* A sensible default icon size that tracks the button size (consumers can still
|
|
29
|
+
size their own glyphs). */
|
|
30
|
+
:where([data-icon-button] svg) {
|
|
31
|
+
width: 20px;
|
|
32
|
+
height: 20px;
|
|
33
|
+
}
|
|
34
|
+
:where([data-icon-button][data-size='sm'] svg) {
|
|
35
|
+
width: 16px;
|
|
36
|
+
height: 16px;
|
|
37
|
+
}
|
|
38
|
+
:where([data-icon-button][data-size='lg'] svg) {
|
|
39
|
+
width: 24px;
|
|
40
|
+
height: 24px;
|
|
41
|
+
}
|
|
42
|
+
}
|