@fluixi-ui/tokens 0.4.0-alpha.10 → 0.4.0-alpha.12
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/base.css +1 -1
- package/dist/css/carousel.css +120 -0
- package/dist/css/chip.css +175 -0
- package/dist/css/dnd.css +68 -0
- package/dist/css/file-upload.css +37 -0
- package/dist/css/page.css +138 -0
- package/dist/css/splitter.css +81 -0
- package/dist/css/tags-input.css +6 -36
- package/dist/css/text.css +124 -0
- package/dist/css/theme.css +97 -0
- package/dist/lib/css/index.d.ts +4 -2
- package/dist/lib/css/index.d.ts.map +1 -1
- package/dist/lib/css/index.js +4 -2
- package/dist/preset.css +7 -1
- package/dist/tokens.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/css/base.css
CHANGED
package/dist/css/carousel.css
CHANGED
|
@@ -64,13 +64,133 @@
|
|
|
64
64
|
box-shadow: var(--flx-ui-ctl-ring);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/* Controls row. Inset from the carousel's edges on purpose: laid out flush
|
|
68
|
+
(the obvious `justify-content: space-between` a consumer writes by hand) the
|
|
69
|
+
arrows sit hard against the container's left, right and bottom lines. */
|
|
70
|
+
:where([data-carousel-controls]) {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
gap: var(--flx-ui-space-3);
|
|
75
|
+
/* Inset on both axes: without the block padding the arrows sit hard against
|
|
76
|
+
the carousel container's bottom line. */
|
|
77
|
+
padding-inline: var(--flx-ui-space-2);
|
|
78
|
+
padding-block: var(--flx-ui-space-2);
|
|
79
|
+
margin-top: var(--flx-ui-space-3);
|
|
80
|
+
}
|
|
81
|
+
/* The dots take the middle so the arrows stay pinned to either end even when
|
|
82
|
+
there is no third child. */
|
|
83
|
+
:where([data-carousel-controls] > [data-carousel-dots]) {
|
|
84
|
+
flex: 1;
|
|
85
|
+
margin-top: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── overlay mode ──────────────────────────────────────────────────────────
|
|
89
|
+
Controls sit over the slides, so the carousel is exactly as tall as its
|
|
90
|
+
images. The layer itself takes no pointer events — otherwise it would cover
|
|
91
|
+
the viewport and swallow the drag that starts a swipe — and each control
|
|
92
|
+
opts back in. */
|
|
93
|
+
:where([data-carousel][data-overlay] [data-carousel-controls]) {
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset: 0;
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding: 0;
|
|
98
|
+
display: block;
|
|
99
|
+
pointer-events: none;
|
|
100
|
+
}
|
|
101
|
+
:where([data-carousel][data-overlay] [data-carousel-controls] > *) {
|
|
102
|
+
pointer-events: auto;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:where([data-carousel][data-overlay] [data-carousel-prev]),
|
|
106
|
+
:where([data-carousel][data-overlay] [data-carousel-next]) {
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 50%;
|
|
109
|
+
transform: translateY(-50%);
|
|
110
|
+
/* Inset rather than flush: an arrow touching the frame reads as clipped. */
|
|
111
|
+
background: color-mix(in oklab, var(--flx-ui-color-bg) 72%, transparent);
|
|
112
|
+
border-color: color-mix(in oklab, var(--flx-ui-color-fg) 12%, transparent);
|
|
113
|
+
backdrop-filter: blur(6px);
|
|
114
|
+
}
|
|
115
|
+
:where([data-carousel][data-overlay] [data-carousel-prev]) {
|
|
116
|
+
inset-inline-start: var(--flx-ui-space-3);
|
|
117
|
+
}
|
|
118
|
+
:where([data-carousel][data-overlay] [data-carousel-next]) {
|
|
119
|
+
inset-inline-end: var(--flx-ui-space-3);
|
|
120
|
+
}
|
|
121
|
+
:where([data-carousel][data-overlay] [data-carousel-prev]:hover),
|
|
122
|
+
:where([data-carousel][data-overlay] [data-carousel-next]:hover) {
|
|
123
|
+
background: color-mix(in oklab, var(--flx-ui-color-bg) 90%, transparent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
:where([data-carousel][data-overlay] [data-carousel-dots]) {
|
|
127
|
+
position: absolute;
|
|
128
|
+
inset-inline: 0;
|
|
129
|
+
bottom: var(--flx-ui-space-3);
|
|
130
|
+
margin: 0;
|
|
131
|
+
}
|
|
132
|
+
/* Over an arbitrary image a border-coloured dot can vanish, so the inactive
|
|
133
|
+
state gets its own contrast rather than inheriting the page's. */
|
|
134
|
+
:where([data-carousel][data-overlay] [data-carousel-dot]) {
|
|
135
|
+
background: color-mix(in oklab, var(--flx-ui-color-bg) 55%, transparent);
|
|
136
|
+
box-shadow: 0 0 0 1px color-mix(in oklab, var(--flx-ui-color-fg) 18%, transparent);
|
|
137
|
+
}
|
|
138
|
+
:where([data-carousel][data-overlay] [data-carousel-dot][data-active]) {
|
|
139
|
+
background: var(--flx-ui-color-bg);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* The arrows ship a default glyph and sit in normal flow (the consumer places
|
|
143
|
+
them), so they need a real control appearance — without it they fell back to
|
|
144
|
+
the UA button look while the dots beside them were fully styled.
|
|
145
|
+
|
|
146
|
+
A pill rather than a circle: the same control has to hold a bare `‹` and a
|
|
147
|
+
labelled "‹ Prev". Self-contained like the dots, so carousel needs no
|
|
148
|
+
dependency on the button slice. */
|
|
67
149
|
:where([data-carousel-prev], [data-carousel-next]) {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
gap: var(--flx-ui-space-1);
|
|
154
|
+
min-inline-size: var(--flx-ui-size-md);
|
|
155
|
+
block-size: var(--flx-ui-size-md);
|
|
156
|
+
padding-inline: var(--flx-ui-space-2);
|
|
157
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
158
|
+
border-radius: var(--flx-ui-radius-full);
|
|
159
|
+
background: var(--flx-ui-color-bg);
|
|
160
|
+
color: var(--flx-ui-color-fg);
|
|
161
|
+
font: inherit;
|
|
162
|
+
line-height: 1;
|
|
68
163
|
cursor: pointer;
|
|
164
|
+
transition:
|
|
165
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
166
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
167
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
168
|
+
}
|
|
169
|
+
:where([data-carousel-prev]:hover, [data-carousel-next]:hover) {
|
|
170
|
+
background: var(--flx-ui-color-bg-muted);
|
|
171
|
+
border-color: var(--flx-ui-color-border-strong);
|
|
172
|
+
}
|
|
173
|
+
:where([data-carousel-prev]:active, [data-carousel-next]:active) {
|
|
174
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
175
|
+
}
|
|
176
|
+
:where([data-carousel-prev]:focus-visible, [data-carousel-next]:focus-visible) {
|
|
177
|
+
outline: none;
|
|
178
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
179
|
+
border-color: var(--flx-ui-color-focus-ring);
|
|
180
|
+
}
|
|
181
|
+
:where([data-carousel-prev] svg, [data-carousel-next] svg) {
|
|
182
|
+
inline-size: 20px;
|
|
183
|
+
block-size: 20px;
|
|
69
184
|
}
|
|
70
185
|
:where([data-carousel-prev][disabled], [data-carousel-next][disabled]) {
|
|
71
186
|
opacity: var(--flx-ui-opacity-disabled);
|
|
72
187
|
cursor: not-allowed;
|
|
73
188
|
}
|
|
189
|
+
/* Hover must not repaint a disabled control. */
|
|
190
|
+
:where([data-carousel-prev][disabled]:hover, [data-carousel-next][disabled]:hover) {
|
|
191
|
+
background: var(--flx-ui-color-bg);
|
|
192
|
+
border-color: var(--flx-ui-color-border);
|
|
193
|
+
}
|
|
74
194
|
}
|
|
75
195
|
|
|
76
196
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Chip (from @fluixi-ui/chip). An interactive pill: a `data-variant` fill style
|
|
3
|
+
crossed with a semantic `data-tone`, like Badge — but taller, with room for a
|
|
4
|
+
checkbox, media slot and remove button. Tone sets four custom props and the
|
|
5
|
+
variants consume them, so tone × variant needs no per-combo rules. */
|
|
6
|
+
:where([data-chip]) {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: var(--flx-ui-space-1);
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
min-width: 0;
|
|
12
|
+
padding-inline: var(--flx-ui-space-2);
|
|
13
|
+
border-radius: var(--flx-ui-radius-full);
|
|
14
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
15
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
16
|
+
line-height: 1.35;
|
|
17
|
+
border: var(--flx-ui-border-width-thin) solid transparent;
|
|
18
|
+
vertical-align: middle;
|
|
19
|
+
|
|
20
|
+
/* neutral defaults; each tone overrides these four. */
|
|
21
|
+
--flx-ui-chip-color: var(--flx-ui-color-fg-muted);
|
|
22
|
+
--flx-ui-chip-text: var(--flx-ui-color-fg-muted);
|
|
23
|
+
--flx-ui-chip-bg: var(--flx-ui-color-bg-muted);
|
|
24
|
+
--flx-ui-chip-on: var(--flx-ui-color-bg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:where([data-chip][data-size='md']) { block-size: var(--flx-ui-space-8); }
|
|
28
|
+
:where([data-chip][data-size='sm']) {
|
|
29
|
+
block-size: var(--flx-ui-space-6);
|
|
30
|
+
font-size: var(--flx-ui-font-size-xs);
|
|
31
|
+
padding-inline: var(--flx-ui-space-1-5, var(--flx-ui-space-1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where([data-chip][data-tone='primary']),
|
|
35
|
+
:where([data-chip][data-tone='info']) {
|
|
36
|
+
--flx-ui-chip-color: var(--flx-ui-color-primary);
|
|
37
|
+
--flx-ui-chip-text: var(--flx-ui-color-primary-text);
|
|
38
|
+
--flx-ui-chip-bg: var(--flx-ui-color-primary-subtle);
|
|
39
|
+
--flx-ui-chip-on: var(--flx-ui-color-primary-fg);
|
|
40
|
+
}
|
|
41
|
+
:where([data-chip][data-tone='success']) {
|
|
42
|
+
--flx-ui-chip-color: var(--flx-ui-color-success);
|
|
43
|
+
--flx-ui-chip-text: var(--flx-ui-color-success-text);
|
|
44
|
+
--flx-ui-chip-bg: var(--flx-ui-color-success-subtle);
|
|
45
|
+
--flx-ui-chip-on: var(--flx-ui-color-success-fg);
|
|
46
|
+
}
|
|
47
|
+
:where([data-chip][data-tone='warning']) {
|
|
48
|
+
--flx-ui-chip-color: var(--flx-ui-color-warning);
|
|
49
|
+
--flx-ui-chip-text: var(--flx-ui-color-warning-text);
|
|
50
|
+
--flx-ui-chip-bg: var(--flx-ui-color-warning-subtle);
|
|
51
|
+
--flx-ui-chip-on: var(--flx-ui-color-warning-fg);
|
|
52
|
+
}
|
|
53
|
+
:where([data-chip][data-tone='danger']) {
|
|
54
|
+
--flx-ui-chip-color: var(--flx-ui-color-danger);
|
|
55
|
+
--flx-ui-chip-text: var(--flx-ui-color-danger-text);
|
|
56
|
+
--flx-ui-chip-bg: var(--flx-ui-color-danger-subtle);
|
|
57
|
+
--flx-ui-chip-on: var(--flx-ui-color-danger-fg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:where([data-chip][data-variant='soft']) {
|
|
61
|
+
background: var(--flx-ui-chip-bg);
|
|
62
|
+
color: var(--flx-ui-chip-text);
|
|
63
|
+
}
|
|
64
|
+
:where([data-chip][data-variant='solid']) {
|
|
65
|
+
background: var(--flx-ui-chip-color);
|
|
66
|
+
color: var(--flx-ui-chip-on);
|
|
67
|
+
}
|
|
68
|
+
:where([data-chip][data-variant='outline']) {
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: var(--flx-ui-chip-text);
|
|
71
|
+
border-color: var(--flx-ui-chip-color);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* A selected filter chip reads as "on" regardless of variant. */
|
|
75
|
+
:where([data-chip][data-selected]) {
|
|
76
|
+
background: var(--flx-ui-chip-color);
|
|
77
|
+
color: var(--flx-ui-chip-on);
|
|
78
|
+
border-color: var(--flx-ui-chip-color);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:where([data-chip][data-interactive]:not([data-disabled])) { cursor: pointer; }
|
|
82
|
+
|
|
83
|
+
:where([data-chip][data-disabled]) {
|
|
84
|
+
opacity: var(--flx-ui-opacity-disabled, 0.5);
|
|
85
|
+
cursor: not-allowed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* The label truncates rather than pushing the remove button out of the pill —
|
|
89
|
+
a chip sits in a row of chips and must not grow unbounded. */
|
|
90
|
+
:where([data-chip-label]),
|
|
91
|
+
:where([data-chip-action]) {
|
|
92
|
+
min-width: 0;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
text-overflow: ellipsis;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* The action is a real <button>; strip its chrome so it reads as the chip's label. */
|
|
99
|
+
:where([data-chip-action]) {
|
|
100
|
+
appearance: none;
|
|
101
|
+
background: none;
|
|
102
|
+
border: 0;
|
|
103
|
+
padding: 0;
|
|
104
|
+
margin: 0;
|
|
105
|
+
font: inherit;
|
|
106
|
+
color: inherit;
|
|
107
|
+
cursor: inherit;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:where([data-chip-media]) {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
flex: none;
|
|
114
|
+
margin-inline-start: calc(var(--flx-ui-space-1) * -1);
|
|
115
|
+
}
|
|
116
|
+
:where([data-chip-media]) :where(img, [data-avatar]) {
|
|
117
|
+
block-size: 1.5em;
|
|
118
|
+
inline-size: 1.5em;
|
|
119
|
+
border-radius: var(--flx-ui-radius-full);
|
|
120
|
+
object-fit: cover;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:where([data-chip-remove]) {
|
|
124
|
+
appearance: none;
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
flex: none;
|
|
129
|
+
background: none;
|
|
130
|
+
border: 0;
|
|
131
|
+
padding: 0;
|
|
132
|
+
margin-inline-end: calc(var(--flx-ui-space-1) * -1);
|
|
133
|
+
/* A comfortable target that still fits the pill: the glyph is small, the
|
|
134
|
+
hit area is not. */
|
|
135
|
+
block-size: 1.5em;
|
|
136
|
+
inline-size: 1.5em;
|
|
137
|
+
border-radius: var(--flx-ui-radius-full);
|
|
138
|
+
font: inherit;
|
|
139
|
+
line-height: 1;
|
|
140
|
+
color: inherit;
|
|
141
|
+
opacity: 0.7;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
transition: opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
144
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
145
|
+
}
|
|
146
|
+
:where([data-chip-remove]:hover) {
|
|
147
|
+
opacity: 1;
|
|
148
|
+
background: color-mix(in oklab, currentColor 15%, transparent);
|
|
149
|
+
}
|
|
150
|
+
:where([data-chip-remove]:disabled) { cursor: not-allowed; }
|
|
151
|
+
|
|
152
|
+
:where([data-chip-action]:focus-visible),
|
|
153
|
+
:where([data-chip-remove]:focus-visible),
|
|
154
|
+
:where([data-chip-checkbox]:focus-visible) {
|
|
155
|
+
outline: var(--flx-ui-border-width-thick) solid var(--flx-ui-color-focus-ring);
|
|
156
|
+
outline-offset: 2px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Shrink the checkbox to chip scale without touching the checkbox component. */
|
|
160
|
+
:where([data-chip-checkbox]) {
|
|
161
|
+
flex: none;
|
|
162
|
+
block-size: 1.15em;
|
|
163
|
+
inline-size: 1.15em;
|
|
164
|
+
margin-inline-start: calc(var(--flx-ui-space-1) * -1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
:where([data-chip-group]) {
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-wrap: wrap;
|
|
170
|
+
align-items: center;
|
|
171
|
+
gap: var(--flx-ui-space-2);
|
|
172
|
+
min-width: 0;
|
|
173
|
+
}
|
|
174
|
+
:where([data-chip-group][data-size='sm']) { gap: var(--flx-ui-space-1); }
|
|
175
|
+
}
|
package/dist/css/dnd.css
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Sortable (from @fluixi-ui/dnd). A reorderable list: the container owns the
|
|
3
|
+
axis, items carry the drag state as `data-dragging`.
|
|
4
|
+
|
|
5
|
+
The container hook is `data-sortable-list`, not `data-sortable`: Table already
|
|
6
|
+
marks a sortable header cell with `data-sortable`, so the bare attribute
|
|
7
|
+
matched every `<th>` and laid the header row out as a flex column. Component
|
|
8
|
+
hooks have to be specific enough not to collide across packages. */
|
|
9
|
+
:where([data-sortable-list]) {
|
|
10
|
+
display: flex;
|
|
11
|
+
min-width: 0;
|
|
12
|
+
gap: var(--flx-ui-space-2);
|
|
13
|
+
}
|
|
14
|
+
:where([data-sortable-list][data-orientation='vertical']) { flex-direction: column; }
|
|
15
|
+
:where([data-sortable-list][data-orientation='horizontal']) {
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:where([data-sortable-item]) {
|
|
21
|
+
min-width: 0;
|
|
22
|
+
/* The pointer owns the gesture once a drag starts: no text selection, and no
|
|
23
|
+
browser scroll/pan stealing the move on touch. */
|
|
24
|
+
touch-action: none;
|
|
25
|
+
border-radius: var(--flx-ui-radius-md);
|
|
26
|
+
transition:
|
|
27
|
+
box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
28
|
+
opacity var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* The item under the pointer/keyboard lifts off the list. Deliberately not a
|
|
32
|
+
transform: the list reorders live, so the element is already where it belongs
|
|
33
|
+
and moving it again would double the offset. */
|
|
34
|
+
:where([data-sortable-item][data-dragging]) {
|
|
35
|
+
position: relative;
|
|
36
|
+
z-index: 1;
|
|
37
|
+
box-shadow: var(--flx-ui-shadow-lg, 0 8px 24px rgb(0 0 0 / 0.18));
|
|
38
|
+
cursor: grabbing;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:where([data-sortable-list][data-dragging]) { user-select: none; }
|
|
42
|
+
|
|
43
|
+
:where([data-sortable-item]:focus-visible) {
|
|
44
|
+
outline: var(--flx-ui-border-width-thick) solid var(--flx-ui-color-focus-ring);
|
|
45
|
+
outline-offset: 2px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where([data-sortable-item][data-disabled]) {
|
|
49
|
+
opacity: var(--flx-ui-opacity-disabled, 0.5);
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:where([data-sortable-handle]) {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
flex: none;
|
|
58
|
+
/* A grip needs a real target, not just a glyph. */
|
|
59
|
+
min-inline-size: var(--flx-ui-space-6);
|
|
60
|
+
min-block-size: var(--flx-ui-space-6);
|
|
61
|
+
color: var(--flx-ui-color-fg-muted);
|
|
62
|
+
cursor: grab;
|
|
63
|
+
touch-action: none;
|
|
64
|
+
user-select: none;
|
|
65
|
+
}
|
|
66
|
+
:where([data-sortable-item][data-dragging]) :where([data-sortable-handle]) { cursor: grabbing; }
|
|
67
|
+
:where([data-sortable-item][data-disabled]) :where([data-sortable-handle]) { cursor: not-allowed; }
|
|
68
|
+
}
|
package/dist/css/file-upload.css
CHANGED
|
@@ -37,4 +37,41 @@
|
|
|
37
37
|
opacity: 0.6;
|
|
38
38
|
cursor: not-allowed;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/* The trigger renders a plain `<button>`; without an appearance here it fell back
|
|
42
|
+
to the browser's default button next to a fully styled dropzone. */
|
|
43
|
+
:where([data-file-upload-trigger]) {
|
|
44
|
+
appearance: none;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
gap: var(--flx-ui-space-2);
|
|
49
|
+
min-inline-size: var(--flx-ui-size-md);
|
|
50
|
+
block-size: var(--flx-ui-size-md);
|
|
51
|
+
padding-inline: var(--flx-ui-space-3);
|
|
52
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
53
|
+
border-radius: var(--flx-ui-radius-md);
|
|
54
|
+
background: var(--flx-ui-color-bg);
|
|
55
|
+
color: var(--flx-ui-color-fg);
|
|
56
|
+
font: inherit;
|
|
57
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
58
|
+
line-height: 1;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition:
|
|
61
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
62
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
63
|
+
}
|
|
64
|
+
:where([data-file-upload-trigger]:hover:not([disabled])) {
|
|
65
|
+
background: var(--flx-ui-color-bg-muted);
|
|
66
|
+
border-color: var(--flx-ui-color-border-strong);
|
|
67
|
+
}
|
|
68
|
+
:where([data-file-upload-trigger]:focus-visible) {
|
|
69
|
+
outline: none;
|
|
70
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
71
|
+
border-color: var(--flx-ui-color-focus-ring);
|
|
72
|
+
}
|
|
73
|
+
:where([data-file-upload-trigger][disabled]) {
|
|
74
|
+
opacity: var(--flx-ui-opacity-disabled);
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
}
|
|
40
77
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/*
|
|
3
|
+
* The page shell. Structure only: a bounded column whose middle scrolls.
|
|
4
|
+
*
|
|
5
|
+
* `min-height: 0` on the body is the load-bearing rule — without it a flex item
|
|
6
|
+
* refuses to shrink below its content, so a long page pushes the footer out of
|
|
7
|
+
* view instead of scrolling.
|
|
8
|
+
*/
|
|
9
|
+
:where([data-page]) {
|
|
10
|
+
--flx-ui-page-gutter: var(--flx-ui-space-6);
|
|
11
|
+
--flx-ui-page-header-gap: var(--flx-ui-space-3);
|
|
12
|
+
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
min-height: 0;
|
|
16
|
+
height: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:where([data-page-header]) {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--flx-ui-page-header-gap);
|
|
23
|
+
padding: var(--flx-ui-space-4) var(--flx-ui-page-gutter);
|
|
24
|
+
border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
25
|
+
background: var(--flx-ui-color-bg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:where([data-page-header][data-sticky]) {
|
|
29
|
+
position: sticky;
|
|
30
|
+
top: 0;
|
|
31
|
+
z-index: 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where([data-page-heading-group]) {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: var(--flx-ui-space-1);
|
|
38
|
+
min-width: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:where([data-page-heading]) {
|
|
42
|
+
margin: 0;
|
|
43
|
+
font-size: var(--flx-ui-font-size-xl);
|
|
44
|
+
font-weight: var(--flx-ui-font-weight-semibold);
|
|
45
|
+
line-height: var(--flx-ui-line-height-tight);
|
|
46
|
+
color: var(--flx-ui-color-fg);
|
|
47
|
+
/* Long titles truncate rather than pushing the actions off the row. */
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
text-overflow: ellipsis;
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:where([data-page-subtitle]) {
|
|
54
|
+
margin: 0;
|
|
55
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
56
|
+
line-height: var(--flx-ui-line-height-normal);
|
|
57
|
+
color: var(--flx-ui-color-fg-muted);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Actions sit at the end of the row whatever precedes them. */
|
|
61
|
+
:where([data-page-actions]) {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--flx-ui-space-2);
|
|
65
|
+
margin-inline-start: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:where([data-page-body]) {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: var(--flx-ui-space-4);
|
|
72
|
+
padding: var(--flx-ui-space-5) var(--flx-ui-page-gutter);
|
|
73
|
+
flex: 1;
|
|
74
|
+
min-height: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Only the body scrolls; the header and footer stay put. */
|
|
78
|
+
:where([data-page-body][data-scroll='body']) {
|
|
79
|
+
overflow-y: auto;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* The whole page scrolls, so the body must not create a second scroll context. */
|
|
83
|
+
:where([data-page-body][data-scroll='page']) {
|
|
84
|
+
overflow: visible;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:where([data-page][data-scroll='page']) {
|
|
88
|
+
overflow-y: auto;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:where([data-page-footer]) {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: flex-end;
|
|
95
|
+
gap: var(--flx-ui-space-3);
|
|
96
|
+
padding: var(--flx-ui-space-4) var(--flx-ui-page-gutter);
|
|
97
|
+
border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
|
|
98
|
+
background: var(--flx-ui-color-bg);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* The back affordance carries `data-variant`, so the button rules and each
|
|
103
|
+
* skin's state layer/ripple already apply. Only the square geometry is left —
|
|
104
|
+
* and the anchor form needs the button's own resets, which it doesn't inherit.
|
|
105
|
+
*/
|
|
106
|
+
:where([data-page-back]) {
|
|
107
|
+
display: inline-flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
flex: none;
|
|
111
|
+
width: var(--flx-ui-size-sm);
|
|
112
|
+
padding: 0;
|
|
113
|
+
text-decoration: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* A CSS chevron, so the back button needs no icon library. */
|
|
117
|
+
:where([data-page-back])::before {
|
|
118
|
+
content: '';
|
|
119
|
+
width: 0.5rem;
|
|
120
|
+
height: 0.5rem;
|
|
121
|
+
border-inline-start: 2px solid currentColor;
|
|
122
|
+
border-block-start: 2px solid currentColor;
|
|
123
|
+
transform: rotate(-45deg);
|
|
124
|
+
/* Optical centring: the glyph's mass sits right of its box. */
|
|
125
|
+
margin-inline-start: 0.15em;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* A supplied icon replaces the drawn chevron. */
|
|
129
|
+
:where([data-page-back]:has(> *))::before {
|
|
130
|
+
content: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:where([dir='rtl']) :where([data-page-back])::before {
|
|
134
|
+
transform: rotate(135deg);
|
|
135
|
+
margin-inline-start: 0;
|
|
136
|
+
margin-inline-end: 0.15em;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/css/tags-input.css
CHANGED
|
@@ -48,43 +48,13 @@
|
|
|
48
48
|
color: var(--flx-ui-color-fg-muted);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/* A committed tag chip.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
51
|
+
/* A committed tag is a Chip (see chip.css); only the field-specific bits live
|
|
52
|
+
here — it must not stretch the row, and the field owns the focus ring. */
|
|
53
|
+
:where([data-tags-input] [data-tags-input-tag]) {
|
|
54
|
+
flex: none;
|
|
55
|
+
max-width: 100%;
|
|
86
56
|
}
|
|
87
|
-
:where([data-tags-input-tag-remove]:focus-visible) {
|
|
57
|
+
:where([data-tags-input] [data-tags-input-tag] [data-chip-remove]:focus-visible) {
|
|
88
58
|
outline: none;
|
|
89
59
|
box-shadow: var(--flx-ui-ctl-ring);
|
|
90
60
|
}
|