@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.
- package/dist/css/accordion.css +81 -3
- package/dist/css/alert.css +52 -23
- package/dist/css/avatar.css +19 -0
- package/dist/css/badge.css +129 -5
- package/dist/css/base.css +3 -1
- package/dist/css/bottom-sheet.css +4 -4
- package/dist/css/breadcrumb.css +1 -1
- package/dist/css/bulk-editor.css +2 -17
- package/dist/css/button.css +76 -16
- package/dist/css/calendar.css +9 -7
- package/dist/css/card.css +5 -1
- package/dist/css/carousel.css +80 -0
- package/dist/css/checkbox-radio.css +55 -1
- package/dist/css/chip.css +175 -0
- package/dist/css/date-field.css +1 -1
- package/dist/css/date-picker.css +2 -2
- package/dist/css/descriptions.css +29 -0
- package/dist/css/drawer.css +4 -4
- 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/file-upload.css +40 -0
- package/dist/css/glow.css +127 -0
- package/dist/css/icon-button.css +42 -0
- package/dist/css/image.css +67 -0
- package/dist/css/input.css +15 -0
- package/dist/css/layout.css +10 -0
- package/dist/css/navigation-menu.css +8 -7
- package/dist/css/number-field.css +3 -3
- package/dist/css/page.css +138 -0
- package/dist/css/pagination.css +3 -3
- 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/select-combobox.css +19 -4
- package/dist/css/sidenav.css +180 -10
- package/dist/css/slider.css +4 -4
- package/dist/css/speed-dial.css +85 -0
- package/dist/css/splitter.css +81 -0
- package/dist/css/statistic.css +34 -0
- package/dist/css/stepper.css +3 -3
- package/dist/css/surface.css +71 -0
- package/dist/css/switch.css +12 -0
- package/dist/css/table.css +11 -0
- package/dist/css/tabs.css +10 -1
- package/dist/css/tags-input.css +91 -0
- package/dist/css/text.css +124 -0
- package/dist/css/timeline.css +59 -0
- package/dist/css/toggle.css +6 -0
- package/dist/css/tree-view.css +70 -0
- package/dist/lib/css/index.d.ts +10 -12
- package/dist/lib/css/index.d.ts.map +1 -1
- package/dist/lib/css/index.js +21 -7
- package/dist/lib/reference/colors.d.ts.map +1 -1
- package/dist/lib/reference/colors.js +15 -1
- package/dist/preset.css +23 -1
- package/dist/tokens.css +52 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* File upload (from @fluixi-ui/file-upload). The dropzone is a dashed drop area
|
|
3
|
+
that also opens the file dialog on click; it lifts while a drag is over it. */
|
|
4
|
+
:where([data-file-upload-dropzone]) {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
gap: var(--flx-ui-space-2);
|
|
10
|
+
padding: var(--flx-ui-space-6) var(--flx-ui-space-4);
|
|
11
|
+
text-align: center;
|
|
12
|
+
font-size: var(--flx-ui-font-size-sm);
|
|
13
|
+
color: var(--flx-ui-color-fg-muted);
|
|
14
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
15
|
+
border: var(--flx-ui-border-width-thin) dashed var(--flx-ui-color-border-strong);
|
|
16
|
+
border-radius: var(--flx-ui-radius-md);
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
transition:
|
|
19
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
20
|
+
background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
21
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
22
|
+
}
|
|
23
|
+
:where([data-file-upload-dropzone]:hover),
|
|
24
|
+
:where([data-file-upload-dropzone][data-dragging]) {
|
|
25
|
+
border-color: var(--flx-ui-color-focus-ring);
|
|
26
|
+
background: var(--flx-ui-color-primary-subtle);
|
|
27
|
+
color: var(--flx-ui-color-fg);
|
|
28
|
+
}
|
|
29
|
+
:where([data-file-upload-dropzone][data-dragging]) {
|
|
30
|
+
border-style: solid;
|
|
31
|
+
}
|
|
32
|
+
:where([data-file-upload-dropzone]:focus-visible) {
|
|
33
|
+
outline: none;
|
|
34
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
35
|
+
}
|
|
36
|
+
:where([data-file-upload-dropzone][data-disabled]) {
|
|
37
|
+
opacity: 0.6;
|
|
38
|
+
cursor: not-allowed;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Glow — an animated gradient border ring, opt-in via `data-glow` on any element
|
|
4
|
+
* (button, surface, card…). The ::after covers the border box and is masked to
|
|
5
|
+
* just the ring, so the element keeps its own fill and centred content. Colours
|
|
6
|
+
* follow the accent and are overridable per-instance with --flx-glow-from / -via
|
|
7
|
+
* / -to (and rest speed via --flx-glow-speed). `data-glow-side` restricts it to
|
|
8
|
+
* one edge that slides to the opposite side on hover.
|
|
9
|
+
* ------------------------------------------------------------------------- */
|
|
10
|
+
:where([data-glow]) {
|
|
11
|
+
position: relative;
|
|
12
|
+
border-color: transparent;
|
|
13
|
+
/* Show the edge ring even when the host would otherwise clip (e.g. a card). */
|
|
14
|
+
overflow: visible;
|
|
15
|
+
}
|
|
16
|
+
:where([data-glow])::after {
|
|
17
|
+
content: '';
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset: 0;
|
|
20
|
+
border-radius: inherit;
|
|
21
|
+
padding: 2px;
|
|
22
|
+
background: conic-gradient(
|
|
23
|
+
from var(--flx-angle, 0deg),
|
|
24
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)),
|
|
25
|
+
var(--flx-glow-via, var(--flx-ui-accent-5)),
|
|
26
|
+
var(--flx-glow-to, var(--flx-glow-from, var(--flx-ui-color-primary)))
|
|
27
|
+
);
|
|
28
|
+
-webkit-mask:
|
|
29
|
+
linear-gradient(#000 0 0) content-box,
|
|
30
|
+
linear-gradient(#000 0 0);
|
|
31
|
+
-webkit-mask-composite: xor;
|
|
32
|
+
mask:
|
|
33
|
+
linear-gradient(#000 0 0) content-box,
|
|
34
|
+
linear-gradient(#000 0 0);
|
|
35
|
+
mask-composite: exclude;
|
|
36
|
+
animation: flx-border-spin var(--flx-glow-speed, 4s) linear infinite;
|
|
37
|
+
transition: filter var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
}
|
|
40
|
+
/* Comes alive on hover: the ring spins faster and blooms in its accent colour. */
|
|
41
|
+
:where([data-glow]:hover:not([data-disabled]))::after {
|
|
42
|
+
--flx-glow-speed: 1.2s;
|
|
43
|
+
filter: drop-shadow(0 0 5px var(--flx-glow-via, var(--flx-ui-color-primary)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Single-edge glow: reuse the fixed full ring mask, but paint the colour as a
|
|
47
|
+
band positioned on one side, so only that edge (and its corners) lights up.
|
|
48
|
+
Because it's the *background* that's positioned, hover slides it to the opposite
|
|
49
|
+
side smoothly. Positional, so no spin. */
|
|
50
|
+
:where([data-glow][data-glow-side])::after {
|
|
51
|
+
animation: none;
|
|
52
|
+
background-repeat: no-repeat;
|
|
53
|
+
/* 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)));
|
|
55
|
+
transition:
|
|
56
|
+
background-position 450ms var(--flx-ui-ease-standard),
|
|
57
|
+
filter var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
|
|
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. */
|
|
61
|
+
:where([data-glow][data-glow-side='top'])::after,
|
|
62
|
+
:where([data-glow][data-glow-side='bottom'])::after {
|
|
63
|
+
background-image: linear-gradient(
|
|
64
|
+
to bottom,
|
|
65
|
+
transparent 12%,
|
|
66
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 38%,
|
|
67
|
+
var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
|
|
68
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 62%,
|
|
69
|
+
transparent 88%
|
|
70
|
+
);
|
|
71
|
+
background-size: 100% 200%;
|
|
72
|
+
}
|
|
73
|
+
:where([data-glow][data-glow-side='left'])::after,
|
|
74
|
+
:where([data-glow][data-glow-side='right'])::after {
|
|
75
|
+
background-image: linear-gradient(
|
|
76
|
+
to right,
|
|
77
|
+
transparent 12%,
|
|
78
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 38%,
|
|
79
|
+
var(--flx-glow-via, var(--flx-ui-accent-5)) 50%,
|
|
80
|
+
var(--flx-glow-from, var(--flx-ui-color-primary)) 62%,
|
|
81
|
+
transparent 88%
|
|
82
|
+
);
|
|
83
|
+
background-size: 200% 100%;
|
|
84
|
+
}
|
|
85
|
+
:where([data-glow][data-glow-side='bottom'])::after {
|
|
86
|
+
background-position: 50% 0%;
|
|
87
|
+
}
|
|
88
|
+
:where([data-glow][data-glow-side='bottom']:hover:not([data-disabled]))::after {
|
|
89
|
+
background-position: 50% 100%;
|
|
90
|
+
}
|
|
91
|
+
:where([data-glow][data-glow-side='top'])::after {
|
|
92
|
+
background-position: 50% 100%;
|
|
93
|
+
}
|
|
94
|
+
:where([data-glow][data-glow-side='top']:hover:not([data-disabled]))::after {
|
|
95
|
+
background-position: 50% 0%;
|
|
96
|
+
}
|
|
97
|
+
:where([data-glow][data-glow-side='left'])::after {
|
|
98
|
+
background-position: 100% 50%;
|
|
99
|
+
}
|
|
100
|
+
:where([data-glow][data-glow-side='left']:hover:not([data-disabled]))::after {
|
|
101
|
+
background-position: 0% 50%;
|
|
102
|
+
}
|
|
103
|
+
:where([data-glow][data-glow-side='right'])::after {
|
|
104
|
+
background-position: 0% 50%;
|
|
105
|
+
}
|
|
106
|
+
:where([data-glow][data-glow-side='right']:hover:not([data-disabled]))::after {
|
|
107
|
+
background-position: 100% 50%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (prefers-reduced-motion: reduce) {
|
|
111
|
+
:where([data-glow])::after {
|
|
112
|
+
animation: none;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Registered so the angle can actually animate; unlayered like other keyframes. */
|
|
118
|
+
@property --flx-angle {
|
|
119
|
+
syntax: '<angle>';
|
|
120
|
+
inherits: false;
|
|
121
|
+
initial-value: 0deg;
|
|
122
|
+
}
|
|
123
|
+
@keyframes flx-border-spin {
|
|
124
|
+
to {
|
|
125
|
+
--flx-angle: 360deg;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* Image (from @fluixi-ui/image). A box that reserves the image's space and layers
|
|
3
|
+
a loading placeholder / blur-up and an error fallback over the <img>. Size it via
|
|
4
|
+
a width/height, an aspect-ratio, or a sized parent — the <img> fills it. */
|
|
5
|
+
:where([data-image]) {
|
|
6
|
+
position: relative;
|
|
7
|
+
display: inline-block;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
line-height: 0;
|
|
10
|
+
vertical-align: middle;
|
|
11
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
12
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
13
|
+
--flx-ui-image-fallback-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M21 3H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2Zm0 16H3V5h18v14ZM8.5 13.5l2 2.41L13.5 12l4 5.5H6.5l2-4Z'/%3E%3C/svg%3E");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* The image stays in normal flow so the box reserves its footprint (and keeps its
|
|
17
|
+
height when the source errors); object-fit crops it to a sized box. */
|
|
18
|
+
:where([data-image-img]) {
|
|
19
|
+
display: block;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transition: opacity var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
|
|
25
|
+
}
|
|
26
|
+
:where([data-image][data-status='loaded'] [data-image-img]) {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
}
|
|
29
|
+
:where([data-image][data-status='error'] [data-image-img]) {
|
|
30
|
+
opacity: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Overlays — blur-up, custom placeholder, and the error fallback — fill the box. */
|
|
34
|
+
:where([data-image-blur], [data-image-placeholder], [data-image-fallback]) {
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 0;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
}
|
|
40
|
+
:where([data-image-blur]) {
|
|
41
|
+
background-size: cover;
|
|
42
|
+
background-position: center;
|
|
43
|
+
filter: blur(12px);
|
|
44
|
+
transform: scale(1.1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Default error fallback: a centered picture glyph on the subtle surface. Passing a
|
|
48
|
+
custom `fallback` element replaces it entirely. */
|
|
49
|
+
:where([data-image-fallback]) {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
background: var(--flx-ui-color-bg-subtle);
|
|
54
|
+
color: var(--flx-ui-color-fg-subtle);
|
|
55
|
+
}
|
|
56
|
+
:where([data-image-fallback])::before {
|
|
57
|
+
content: "";
|
|
58
|
+
width: 45%;
|
|
59
|
+
height: 45%;
|
|
60
|
+
max-width: 28px;
|
|
61
|
+
max-height: 28px;
|
|
62
|
+
background: currentColor;
|
|
63
|
+
opacity: 0.55;
|
|
64
|
+
-webkit-mask: center / contain no-repeat var(--flx-ui-image-fallback-glyph);
|
|
65
|
+
mask: center / contain no-repeat var(--flx-ui-image-fallback-glyph);
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/css/input.css
CHANGED
|
@@ -29,6 +29,21 @@
|
|
|
29
29
|
:where([data-input][data-invalid]) {
|
|
30
30
|
border-color: var(--flx-ui-color-danger);
|
|
31
31
|
}
|
|
32
|
+
/* Drop the browser's native number spinners — they can't be styled consistently
|
|
33
|
+
across engines and clash with the field look. Covers both the bare input and
|
|
34
|
+
the one nested inside an adornment group (which carries no [data-input]). Reach
|
|
35
|
+
for @fluixi-ui/number-field when you want proper increment / decrement steppers. */
|
|
36
|
+
:where([data-input][type='number'], [data-input-group] input[type='number']) {
|
|
37
|
+
-moz-appearance: textfield;
|
|
38
|
+
appearance: textfield;
|
|
39
|
+
}
|
|
40
|
+
:where([data-input][type='number']::-webkit-outer-spin-button),
|
|
41
|
+
:where([data-input][type='number']::-webkit-inner-spin-button),
|
|
42
|
+
:where([data-input-group] input[type='number']::-webkit-outer-spin-button),
|
|
43
|
+
:where([data-input-group] input[type='number']::-webkit-inner-spin-button) {
|
|
44
|
+
-webkit-appearance: none;
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
32
47
|
|
|
33
48
|
/* Input with leading/trailing adornments: the group owns the frame. */
|
|
34
49
|
:where([data-input-group]) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* Layout primitives (Stack / Grid / Container) — the geometry is set inline per
|
|
4
|
+
* instance; this only guarantees a predictable box model.
|
|
5
|
+
* ------------------------------------------------------------------------- */
|
|
6
|
+
:where([data-stack], [data-grid], [data-container]) {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -42,20 +42,21 @@
|
|
|
42
42
|
:where([data-navigation-menu-trigger]:focus-visible, [data-navigation-menu-link]:focus-visible) {
|
|
43
43
|
box-shadow: var(--flx-ui-ctl-ring);
|
|
44
44
|
}
|
|
45
|
-
/* The chevron affordance:
|
|
45
|
+
/* The chevron affordance: the shared theme chevron, flipping 180° when open. */
|
|
46
46
|
:where([data-navigation-menu-trigger])::after {
|
|
47
47
|
content: '';
|
|
48
|
-
width:
|
|
49
|
-
height:
|
|
48
|
+
width: 1em;
|
|
49
|
+
height: 1em;
|
|
50
|
+
flex: none;
|
|
50
51
|
margin-inline-start: var(--flx-ui-space-1);
|
|
51
|
-
|
|
52
|
-
border-bottom: 1.5px solid currentColor;
|
|
53
|
-
transform: rotate(45deg) translateY(-0.1em);
|
|
52
|
+
background: currentColor;
|
|
54
53
|
opacity: 0.6;
|
|
54
|
+
-webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
|
|
55
|
+
mask: var(--flx-ui-chevron-down) center / contain no-repeat;
|
|
55
56
|
transition: transform var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
56
57
|
}
|
|
57
58
|
:where([data-navigation-menu-trigger][data-state='open'])::after {
|
|
58
|
-
transform: rotate(
|
|
59
|
+
transform: rotate(180deg);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
:where([data-navigation-menu-content]) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
font-size: var(--flx-ui-font-size-sm);
|
|
12
12
|
font-weight: var(--flx-ui-font-weight-medium);
|
|
13
13
|
color: var(--flx-ui-color-fg);
|
|
14
|
-
line-height: var(--flx-ui-
|
|
14
|
+
line-height: var(--flx-ui-line-height-none);
|
|
15
15
|
}
|
|
16
16
|
:where([data-number-field-label][data-disabled]) {
|
|
17
17
|
color: var(--flx-ui-color-fg-subtle);
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
color: var(--flx-ui-color-fg-muted);
|
|
62
62
|
cursor: pointer;
|
|
63
63
|
font-size: var(--flx-ui-font-size-sm);
|
|
64
|
-
transition: background var(--flx-ui-duration-fast) var(--flx-ui-
|
|
65
|
-
color var(--flx-ui-duration-fast) var(--flx-ui-
|
|
64
|
+
transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
65
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
66
66
|
}
|
|
67
67
|
:where([data-number-field-decrement]) {
|
|
68
68
|
border-right: 1px solid var(--flx-ui-color-border);
|
|
@@ -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
|
+
}
|
package/dist/css/pagination.css
CHANGED
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
border-radius: var(--flx-ui-radius-md);
|
|
32
32
|
cursor: pointer;
|
|
33
33
|
user-select: none;
|
|
34
|
-
transition: background var(--flx-ui-duration-fast) var(--flx-ui-
|
|
35
|
-
color var(--flx-ui-duration-fast) var(--flx-ui-
|
|
36
|
-
border-color var(--flx-ui-duration-fast) var(--flx-ui-
|
|
34
|
+
transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
35
|
+
color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
36
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
37
37
|
}
|
|
38
38
|
:where([data-pagination-page]:hover:not([disabled])),
|
|
39
39
|
:where([data-pagination-prev]:hover:not([disabled])),
|
|
@@ -89,10 +89,15 @@
|
|
|
89
89
|
border-color: var(--flx-ui-color-primary);
|
|
90
90
|
}
|
|
91
91
|
:where([data-mode='multiple'] [role='option'][data-selected] > [data-select-item-indicator])::after {
|
|
92
|
-
content: '
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
content: '';
|
|
93
|
+
box-sizing: content-box;
|
|
94
|
+
flex: none;
|
|
95
|
+
width: 4px;
|
|
96
|
+
height: 8px;
|
|
97
|
+
margin-top: -2px;
|
|
98
|
+
border: solid var(--flx-ui-color-primary-fg);
|
|
99
|
+
border-width: 0 2px 2px 0;
|
|
100
|
+
transform: rotate(45deg);
|
|
96
101
|
}
|
|
97
102
|
:where([data-mode='multiple'] [role='option'][data-selected])::after {
|
|
98
103
|
content: none;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@layer flx.components {
|
|
2
|
+
/* ----------------------------------------------------------------------------
|
|
3
|
+
* PIN / OTP input — a row of single-character cells
|
|
4
|
+
* ------------------------------------------------------------------------- */
|
|
5
|
+
:where([data-pin-input]) {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: var(--flx-ui-pin-gap, var(--flx-ui-space-2));
|
|
9
|
+
}
|
|
10
|
+
:where([data-pin-input-field]) {
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
width: var(--flx-ui-pin-size, 2.75rem);
|
|
13
|
+
height: var(--flx-ui-pin-size, 2.75rem);
|
|
14
|
+
padding: 0;
|
|
15
|
+
text-align: center;
|
|
16
|
+
font: inherit;
|
|
17
|
+
font-size: var(--flx-ui-font-size-lg);
|
|
18
|
+
font-weight: var(--flx-ui-font-weight-medium);
|
|
19
|
+
font-variant-numeric: tabular-nums;
|
|
20
|
+
color: var(--flx-ui-color-fg);
|
|
21
|
+
background: var(--flx-ui-color-bg);
|
|
22
|
+
border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border-strong);
|
|
23
|
+
border-radius: var(--flx-ui-radius-md);
|
|
24
|
+
transition:
|
|
25
|
+
border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
|
|
26
|
+
box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
|
|
27
|
+
}
|
|
28
|
+
:where([data-pin-input-field]::placeholder) {
|
|
29
|
+
color: var(--flx-ui-color-fg-muted);
|
|
30
|
+
}
|
|
31
|
+
:where([data-pin-input-field]:focus-visible) {
|
|
32
|
+
outline: none;
|
|
33
|
+
border-color: var(--flx-ui-color-focus-ring);
|
|
34
|
+
box-shadow: var(--flx-ui-ctl-ring);
|
|
35
|
+
}
|
|
36
|
+
:where([data-pin-input-field]:disabled) {
|
|
37
|
+
opacity: var(--flx-ui-opacity-disabled, 0.5);
|
|
38
|
+
cursor: not-allowed;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/css/progress.css
CHANGED
|
@@ -37,4 +37,60 @@
|
|
|
37
37
|
0% { transform: translateX(-100%); }
|
|
38
38
|
100% { transform: translateX(250%); }
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/* ----------------------------------------------------------------------------
|
|
42
|
+
* Circular progress
|
|
43
|
+
* ------------------------------------------------------------------------- */
|
|
44
|
+
:where([data-circular]) {
|
|
45
|
+
position: relative;
|
|
46
|
+
/* Stack the ring and any centred label in a single grid cell. */
|
|
47
|
+
display: inline-grid;
|
|
48
|
+
place-items: center;
|
|
49
|
+
vertical-align: middle;
|
|
50
|
+
/* Undo the linear bar's box styling that keys off [role='progressbar']
|
|
51
|
+
(fixed 6px height / 240px width / clipped overflow / filled track). */
|
|
52
|
+
width: auto;
|
|
53
|
+
height: auto;
|
|
54
|
+
overflow: visible;
|
|
55
|
+
background: none;
|
|
56
|
+
border-radius: 0;
|
|
57
|
+
}
|
|
58
|
+
:where([data-circular]) > * {
|
|
59
|
+
grid-area: 1 / 1;
|
|
60
|
+
}
|
|
61
|
+
:where([data-circular] [data-progress-svg]) {
|
|
62
|
+
display: block;
|
|
63
|
+
/* Start the arc at 12 o'clock rather than 3 o'clock. */
|
|
64
|
+
transform: rotate(-90deg);
|
|
65
|
+
}
|
|
66
|
+
:where([data-progress-track]) {
|
|
67
|
+
fill: none;
|
|
68
|
+
stroke: var(--flx-ui-color-bg-muted);
|
|
69
|
+
}
|
|
70
|
+
:where([data-progress-indicator]) {
|
|
71
|
+
fill: none;
|
|
72
|
+
stroke: var(--flx-ui-color-primary);
|
|
73
|
+
stroke-linecap: round;
|
|
74
|
+
transition: stroke-dashoffset var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
|
|
75
|
+
}
|
|
76
|
+
/* Indeterminate: a short arc that sweeps around the ring. */
|
|
77
|
+
:where([data-circular][data-state='indeterminate'] [data-progress-svg]) {
|
|
78
|
+
animation: flx-ui-circular-spin 0.9s linear infinite;
|
|
79
|
+
}
|
|
80
|
+
:where([data-circular][data-state='indeterminate'] [data-progress-indicator]) {
|
|
81
|
+
stroke-dasharray: 25 200;
|
|
82
|
+
transition: none;
|
|
83
|
+
}
|
|
84
|
+
@media (prefers-reduced-motion: reduce) {
|
|
85
|
+
:where([data-circular][data-state='indeterminate'] [data-progress-svg]) {
|
|
86
|
+
animation: none;
|
|
87
|
+
}
|
|
88
|
+
:where([data-circular][data-state='indeterminate'] [data-progress-indicator]) {
|
|
89
|
+
stroke-dasharray: none;
|
|
90
|
+
opacity: 0.5;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
@keyframes flx-ui-circular-spin {
|
|
94
|
+
to { transform: rotate(270deg); }
|
|
95
|
+
}
|
|
40
96
|
}
|