@xenosystem/elements-react 0.0.4 → 0.0.6
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/LICENSE +18 -18
- package/README.md +41 -41
- package/dist/containers/Chip.js +1 -1
- package/dist/containers/Panel.js +1 -1
- package/package.json +58 -58
- package/src/chrome-separated.css +132 -132
- package/src/chrome-unified.css +72 -72
- package/src/containers/Badges.css +116 -116
- package/src/containers/avatar.css +70 -70
- package/src/containers/card.css +75 -75
- package/src/containers/chip.css +103 -103
- package/src/containers/list-row.css +132 -132
- package/src/containers/message-bubble.css +112 -112
- package/src/containers/panel.css +125 -120
- package/src/containers/table.css +102 -102
- package/src/containers/tile.css +17 -17
- package/src/content/callout.css +77 -77
- package/src/content/caret.css +30 -30
- package/src/content/code-block.css +159 -159
- package/src/content/collapsible.css +80 -80
- package/src/content/inline-code.css +14 -14
- package/src/content/model-picker.css +219 -219
- package/src/content/source-card.css +99 -99
- package/src/content/sources-disclosure.css +118 -118
- package/src/controls.css +453 -453
- package/src/fonts.css +34 -34
- package/src/forms/Checkbox.css +114 -114
- package/src/forms/RadioGroup.css +115 -115
- package/src/goo.css +97 -97
- package/src/icon-motion.css +1909 -1909
- package/src/layout/ResizablePanel.css +79 -79
- package/src/nav/sidebar.css +296 -296
- package/src/overlays/date-time-picker.css +238 -238
- package/src/overlays/menu.css +195 -195
- package/src/overlays/modal.css +167 -167
- package/src/overlays/picker-field.css +81 -81
- package/src/overlays/pill-filter.css +129 -129
- package/src/overlays/reveal.css +56 -56
- package/src/overlays/segmented-control.css +185 -185
- package/src/overlays/tabs.css +143 -143
- package/src/overlays/tooltip.css +126 -126
- package/src/scrollbar.css +184 -184
- package/src/size.css +179 -179
- package/src/status/progress-bar.css +88 -88
- package/src/status/spinner.css +33 -33
- package/src/status/statusindicator.css +65 -65
- package/src/status/steptimeline.css +137 -137
- package/src/status/thinkingcube.css +75 -75
- package/src/xeno-element.css +60 -60
- package/src/xeno-elements.css +71 -71
- package/src/xeno-theme.css +112 -112
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* <SegmentedControl> — connected options in one bordered track (rounded square). CSS-first: the chosen
|
|
3
|
-
* option carries `data-selection="on"`; a disabled option carries `data-availability="disabled"`. Size
|
|
4
|
-
* metrics arrive as inline vars. Monochrome only.
|
|
5
|
-
*
|
|
6
|
-
* The fill is ONE thumb that slides between options, not a background each option paints for itself —
|
|
7
|
-
* the same shared-indicator idea as the sidebar's goo pill, laid on its side. A fill that simply
|
|
8
|
-
* appears on the new option and vanishes from the old one tells you nothing about where it came from;
|
|
9
|
-
* one that travels does.
|
|
10
|
-
*
|
|
11
|
-
* How it can slide with no measuring. The track is a GRID of `1fr` columns, so every segment is exactly
|
|
12
|
-
* as wide as the widest label — which makes a segment's width pure arithmetic (track minus padding
|
|
13
|
-
* minus gaps, over the count) instead of something only layout knows. React supplies the two numbers it
|
|
14
|
-
* already has, `--xeno-seg-count` and `--xeno-seg-index`, as plain data; CSS does the rest. No
|
|
15
|
-
* measuring, no animation runtime, and the thumb lands on the segment to the pixel.
|
|
16
|
-
*
|
|
17
|
-
* It must be grid, not `flex: 1 1 0`. A flex item's `min-width` defaults to `auto`, i.e. its own
|
|
18
|
-
* min-content, so each option gets clamped to the width of its own label and the segments come out
|
|
19
|
-
* UNEQUAL — the thumb then computes an average width and sits beside the option instead of on it.
|
|
20
|
-
* Grid's `1fr` has no such floor: every track resolves to the largest contribution, so they are equal
|
|
21
|
-
* by construction and the arithmetic below is exact.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/* Registering the index as a real `<number>` is what makes the slide smooth. An unregistered custom
|
|
25
|
-
property is a string as far as the engine is concerned: it flips from "0" to "2" in one frame, and
|
|
26
|
-
anything computed from it flips with it. Typed, it interpolates — so the transition below runs on the
|
|
27
|
-
NUMBER, and the thumb's offset is simply recomputed every frame from a value that is genuinely
|
|
28
|
-
travelling. That is also why the thumb itself does not transition `translate`: two easings stacked on
|
|
29
|
-
the same movement smear it. */
|
|
30
|
-
@property --xeno-seg-index {
|
|
31
|
-
syntax: '<number>';
|
|
32
|
-
inherits: true;
|
|
33
|
-
initial-value: 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.xeno-segmented {
|
|
37
|
-
/* The goo. Mid-flight the thumb ELONGATES along the direction it is travelling and thins across it —
|
|
38
|
-
a body of liquid reaching for where it is going — then rebounds a hair the other way as it lands.
|
|
39
|
-
Both amounts are multiplied by how far it is going (`--xeno-seg-dist`, from React), so a jump
|
|
40
|
-
across the whole control stretches visibly more than a step to the neighbour. That is the part
|
|
41
|
-
that sells it as matter rather than as a rectangle with an easing curve. */
|
|
42
|
-
--xeno-seg-stretch: 0.16;
|
|
43
|
-
--xeno-seg-squeeze: 0.07;
|
|
44
|
-
--xeno-seg-goo-x: calc(1 + var(--xeno-seg-stretch) * var(--xeno-seg-dist, 1));
|
|
45
|
-
--xeno-seg-goo-y: calc(1 - var(--xeno-seg-squeeze) * var(--xeno-seg-dist, 1));
|
|
46
|
-
/* The rebound: a small counter-squash on the way out of the stretch. */
|
|
47
|
-
--xeno-seg-goo-x2: calc(1 - var(--xeno-seg-squeeze) * 0.45);
|
|
48
|
-
--xeno-seg-goo-y2: calc(1 + var(--xeno-seg-squeeze) * 0.45);
|
|
49
|
-
/* ONE duration for the travel and the deformation. If they drift apart the stretch peaks somewhere
|
|
50
|
-
other than mid-flight and the whole thing reads as a glitch. */
|
|
51
|
-
--xeno-seg-dur: 300ms;
|
|
52
|
-
--xeno-seg-gap: 2px;
|
|
53
|
-
/* The track's own inset, named once: the thumb's width subtracts exactly this, twice. Hard-coding it
|
|
54
|
-
in two places is how a thumb drifts a couple of pixels off its segment. */
|
|
55
|
-
--xeno-seg-pad: 2px;
|
|
56
|
-
--xeno-seg-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
|
|
57
|
-
position: relative;
|
|
58
|
-
isolation: isolate;
|
|
59
|
-
display: grid;
|
|
60
|
-
grid-auto-flow: column;
|
|
61
|
-
grid-auto-columns: 1fr; /* equal segments, by construction */
|
|
62
|
-
/* Explicit content-width so a column parent (align-items:stretch) cannot stretch the track to full
|
|
63
|
-
width — a segmented control always hugs its options. */
|
|
64
|
-
width: fit-content;
|
|
65
|
-
align-items: stretch;
|
|
66
|
-
gap: var(--xeno-seg-gap);
|
|
67
|
-
padding: var(--xeno-seg-pad);
|
|
68
|
-
background: var(--xeno-surface);
|
|
69
|
-
border: 1px solid var(--xeno-border);
|
|
70
|
-
border-radius: var(--xeno-radius-md);
|
|
71
|
-
/* The travel itself: the index slides, and everything computed from it slides with it. */
|
|
72
|
-
transition: --xeno-seg-index var(--xeno-seg-dur) var(--xeno-seg-ease);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/* The travelling fill. `100%` in `translate` is the thumb's OWN width, so one segment plus one gap is
|
|
76
|
-
exactly `100% + var(--xeno-seg-gap)` — the stride, without ever naming a pixel width. */
|
|
77
|
-
.xeno-segmented-thumb {
|
|
78
|
-
position: absolute;
|
|
79
|
-
z-index: -1;
|
|
80
|
-
top: var(--xeno-seg-pad);
|
|
81
|
-
bottom: var(--xeno-seg-pad);
|
|
82
|
-
left: var(--xeno-seg-pad);
|
|
83
|
-
/* `100%` here is the track's PADDING box, so the options' row is that minus the padding on both
|
|
84
|
-
sides; take out the gaps and divide by the count and you have one segment, exactly. */
|
|
85
|
-
width: calc(
|
|
86
|
-
(100% - var(--xeno-seg-pad) * 2 - var(--xeno-seg-gap) * (var(--xeno-seg-count, 1) - 1)) /
|
|
87
|
-
var(--xeno-seg-count, 1)
|
|
88
|
-
);
|
|
89
|
-
translate: calc(var(--xeno-seg-index, 0) * (100% + var(--xeno-seg-gap))) 0;
|
|
90
|
-
background: var(--xeno-control);
|
|
91
|
-
border-radius: var(--xeno-radius-xs);
|
|
92
|
-
pointer-events: none;
|
|
93
|
-
}
|
|
94
|
-
/* Nothing selected (a `value` matching no option) — no thumb rather than a thumb parked on the first. */
|
|
95
|
-
.xeno-segmented[data-selection='off'] .xeno-segmented-thumb {
|
|
96
|
-
opacity: 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.xeno-segmented-option {
|
|
100
|
-
position: relative;
|
|
101
|
-
z-index: 1; /* over the thumb */
|
|
102
|
-
display: inline-flex;
|
|
103
|
-
min-width: 0; /* the grid track owns the width; do not let the label put a floor under it */
|
|
104
|
-
align-items: center;
|
|
105
|
-
justify-content: center;
|
|
106
|
-
gap: var(--xeno-gap, 6px);
|
|
107
|
-
height: calc(var(--xeno-h, 32px) - 4px);
|
|
108
|
-
padding: 0 var(--xeno-padx, 12px);
|
|
109
|
-
font-family: inherit;
|
|
110
|
-
font-size: var(--xeno-font, 14px);
|
|
111
|
-
font-weight: 500;
|
|
112
|
-
line-height: 1;
|
|
113
|
-
white-space: nowrap;
|
|
114
|
-
color: var(--xeno-muted);
|
|
115
|
-
background: transparent;
|
|
116
|
-
border: none;
|
|
117
|
-
border-radius: var(--xeno-radius-xs);
|
|
118
|
-
cursor: pointer;
|
|
119
|
-
user-select: none;
|
|
120
|
-
transition:
|
|
121
|
-
color var(--xeno-dur) var(--xeno-ease),
|
|
122
|
-
background var(--xeno-dur) var(--xeno-ease);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* Hover brightens the LABEL and nothing else. A tint behind an unselected option is a second filled
|
|
126
|
-
surface sliding under the thumb — which is exactly what reads as the background being wrong mid-swap.
|
|
127
|
-
One control, one fill: the thumb. */
|
|
128
|
-
.xeno-segmented-option:hover:not([data-availability='disabled']):not([data-selection='on']) {
|
|
129
|
-
color: var(--xeno-text);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/* The selected option no longer paints a fill of its own — the thumb under it is the fill. */
|
|
133
|
-
.xeno-segmented-option[data-selection='on'] {
|
|
134
|
-
color: var(--xeno-text);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.xeno-segmented-option:focus-visible {
|
|
138
|
-
outline: none;
|
|
139
|
-
box-shadow:
|
|
140
|
-
0 0 0 2px var(--xeno-canvas),
|
|
141
|
-
0 0 0 4px var(--xeno-border);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.xeno-segmented-option[data-availability='disabled'] {
|
|
145
|
-
opacity: 0.4;
|
|
146
|
-
cursor: not-allowed;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/* The squash, keyed off the track's `data-index`. One keyframe track per position, all identical, for
|
|
150
|
-
the same reason as the sidebar's: moving to another segment only changes which rule matches, and a
|
|
151
|
-
browser will not restart an animation whose name did not change. Eight positions — a segmented
|
|
152
|
-
control with more options than that wants to be a Select. Past eight the thumb still travels; it
|
|
153
|
-
just stops squashing. */
|
|
154
|
-
.xeno-segmented[data-index='0'] .xeno-segmented-thumb { animation: xeno-seg-goo-0 var(--xeno-seg-dur) 1; }
|
|
155
|
-
.xeno-segmented[data-index='1'] .xeno-segmented-thumb { animation: xeno-seg-goo-1 var(--xeno-seg-dur) 1; }
|
|
156
|
-
.xeno-segmented[data-index='2'] .xeno-segmented-thumb { animation: xeno-seg-goo-2 var(--xeno-seg-dur) 1; }
|
|
157
|
-
.xeno-segmented[data-index='3'] .xeno-segmented-thumb { animation: xeno-seg-goo-3 var(--xeno-seg-dur) 1; }
|
|
158
|
-
.xeno-segmented[data-index='4'] .xeno-segmented-thumb { animation: xeno-seg-goo-4 var(--xeno-seg-dur) 1; }
|
|
159
|
-
.xeno-segmented[data-index='5'] .xeno-segmented-thumb { animation: xeno-seg-goo-5 var(--xeno-seg-dur) 1; }
|
|
160
|
-
.xeno-segmented[data-index='6'] .xeno-segmented-thumb { animation: xeno-seg-goo-6 var(--xeno-seg-dur) 1; }
|
|
161
|
-
.xeno-segmented[data-index='7'] .xeno-segmented-thumb { animation: xeno-seg-goo-7 var(--xeno-seg-dur) 1; }
|
|
162
|
-
/* Armed only once the value has actually changed — otherwise every segmented control on the page
|
|
163
|
-
squashes itself on load. */
|
|
164
|
-
.xeno-segmented:not([data-motion='ready']) .xeno-segmented-thumb { animation: none; }
|
|
165
|
-
|
|
166
|
-
@keyframes xeno-seg-goo-0 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
167
|
-
@keyframes xeno-seg-goo-1 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
168
|
-
@keyframes xeno-seg-goo-2 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
169
|
-
@keyframes xeno-seg-goo-3 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
170
|
-
@keyframes xeno-seg-goo-4 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
171
|
-
@keyframes xeno-seg-goo-5 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
172
|
-
@keyframes xeno-seg-goo-6 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
173
|
-
@keyframes xeno-seg-goo-7 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
174
|
-
|
|
175
|
-
@media (prefers-reduced-motion: reduce) {
|
|
176
|
-
/* The travel lives on the track (the index), so that is where it has to be switched off. */
|
|
177
|
-
.xeno-segmented,
|
|
178
|
-
.xeno-segmented-option {
|
|
179
|
-
transition: none;
|
|
180
|
-
}
|
|
181
|
-
/* The thumb still marks the segment — it just gets there without travelling or squashing. */
|
|
182
|
-
.xeno-segmented-thumb {
|
|
183
|
-
animation: none !important;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* <SegmentedControl> — connected options in one bordered track (rounded square). CSS-first: the chosen
|
|
3
|
+
* option carries `data-selection="on"`; a disabled option carries `data-availability="disabled"`. Size
|
|
4
|
+
* metrics arrive as inline vars. Monochrome only.
|
|
5
|
+
*
|
|
6
|
+
* The fill is ONE thumb that slides between options, not a background each option paints for itself —
|
|
7
|
+
* the same shared-indicator idea as the sidebar's goo pill, laid on its side. A fill that simply
|
|
8
|
+
* appears on the new option and vanishes from the old one tells you nothing about where it came from;
|
|
9
|
+
* one that travels does.
|
|
10
|
+
*
|
|
11
|
+
* How it can slide with no measuring. The track is a GRID of `1fr` columns, so every segment is exactly
|
|
12
|
+
* as wide as the widest label — which makes a segment's width pure arithmetic (track minus padding
|
|
13
|
+
* minus gaps, over the count) instead of something only layout knows. React supplies the two numbers it
|
|
14
|
+
* already has, `--xeno-seg-count` and `--xeno-seg-index`, as plain data; CSS does the rest. No
|
|
15
|
+
* measuring, no animation runtime, and the thumb lands on the segment to the pixel.
|
|
16
|
+
*
|
|
17
|
+
* It must be grid, not `flex: 1 1 0`. A flex item's `min-width` defaults to `auto`, i.e. its own
|
|
18
|
+
* min-content, so each option gets clamped to the width of its own label and the segments come out
|
|
19
|
+
* UNEQUAL — the thumb then computes an average width and sits beside the option instead of on it.
|
|
20
|
+
* Grid's `1fr` has no such floor: every track resolves to the largest contribution, so they are equal
|
|
21
|
+
* by construction and the arithmetic below is exact.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* Registering the index as a real `<number>` is what makes the slide smooth. An unregistered custom
|
|
25
|
+
property is a string as far as the engine is concerned: it flips from "0" to "2" in one frame, and
|
|
26
|
+
anything computed from it flips with it. Typed, it interpolates — so the transition below runs on the
|
|
27
|
+
NUMBER, and the thumb's offset is simply recomputed every frame from a value that is genuinely
|
|
28
|
+
travelling. That is also why the thumb itself does not transition `translate`: two easings stacked on
|
|
29
|
+
the same movement smear it. */
|
|
30
|
+
@property --xeno-seg-index {
|
|
31
|
+
syntax: '<number>';
|
|
32
|
+
inherits: true;
|
|
33
|
+
initial-value: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.xeno-segmented {
|
|
37
|
+
/* The goo. Mid-flight the thumb ELONGATES along the direction it is travelling and thins across it —
|
|
38
|
+
a body of liquid reaching for where it is going — then rebounds a hair the other way as it lands.
|
|
39
|
+
Both amounts are multiplied by how far it is going (`--xeno-seg-dist`, from React), so a jump
|
|
40
|
+
across the whole control stretches visibly more than a step to the neighbour. That is the part
|
|
41
|
+
that sells it as matter rather than as a rectangle with an easing curve. */
|
|
42
|
+
--xeno-seg-stretch: 0.16;
|
|
43
|
+
--xeno-seg-squeeze: 0.07;
|
|
44
|
+
--xeno-seg-goo-x: calc(1 + var(--xeno-seg-stretch) * var(--xeno-seg-dist, 1));
|
|
45
|
+
--xeno-seg-goo-y: calc(1 - var(--xeno-seg-squeeze) * var(--xeno-seg-dist, 1));
|
|
46
|
+
/* The rebound: a small counter-squash on the way out of the stretch. */
|
|
47
|
+
--xeno-seg-goo-x2: calc(1 - var(--xeno-seg-squeeze) * 0.45);
|
|
48
|
+
--xeno-seg-goo-y2: calc(1 + var(--xeno-seg-squeeze) * 0.45);
|
|
49
|
+
/* ONE duration for the travel and the deformation. If they drift apart the stretch peaks somewhere
|
|
50
|
+
other than mid-flight and the whole thing reads as a glitch. */
|
|
51
|
+
--xeno-seg-dur: 300ms;
|
|
52
|
+
--xeno-seg-gap: 2px;
|
|
53
|
+
/* The track's own inset, named once: the thumb's width subtracts exactly this, twice. Hard-coding it
|
|
54
|
+
in two places is how a thumb drifts a couple of pixels off its segment. */
|
|
55
|
+
--xeno-seg-pad: 2px;
|
|
56
|
+
--xeno-seg-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
|
|
57
|
+
position: relative;
|
|
58
|
+
isolation: isolate;
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-auto-flow: column;
|
|
61
|
+
grid-auto-columns: 1fr; /* equal segments, by construction */
|
|
62
|
+
/* Explicit content-width so a column parent (align-items:stretch) cannot stretch the track to full
|
|
63
|
+
width — a segmented control always hugs its options. */
|
|
64
|
+
width: fit-content;
|
|
65
|
+
align-items: stretch;
|
|
66
|
+
gap: var(--xeno-seg-gap);
|
|
67
|
+
padding: var(--xeno-seg-pad);
|
|
68
|
+
background: var(--xeno-surface);
|
|
69
|
+
border: 1px solid var(--xeno-border);
|
|
70
|
+
border-radius: var(--xeno-radius-md);
|
|
71
|
+
/* The travel itself: the index slides, and everything computed from it slides with it. */
|
|
72
|
+
transition: --xeno-seg-index var(--xeno-seg-dur) var(--xeno-seg-ease);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The travelling fill. `100%` in `translate` is the thumb's OWN width, so one segment plus one gap is
|
|
76
|
+
exactly `100% + var(--xeno-seg-gap)` — the stride, without ever naming a pixel width. */
|
|
77
|
+
.xeno-segmented-thumb {
|
|
78
|
+
position: absolute;
|
|
79
|
+
z-index: -1;
|
|
80
|
+
top: var(--xeno-seg-pad);
|
|
81
|
+
bottom: var(--xeno-seg-pad);
|
|
82
|
+
left: var(--xeno-seg-pad);
|
|
83
|
+
/* `100%` here is the track's PADDING box, so the options' row is that minus the padding on both
|
|
84
|
+
sides; take out the gaps and divide by the count and you have one segment, exactly. */
|
|
85
|
+
width: calc(
|
|
86
|
+
(100% - var(--xeno-seg-pad) * 2 - var(--xeno-seg-gap) * (var(--xeno-seg-count, 1) - 1)) /
|
|
87
|
+
var(--xeno-seg-count, 1)
|
|
88
|
+
);
|
|
89
|
+
translate: calc(var(--xeno-seg-index, 0) * (100% + var(--xeno-seg-gap))) 0;
|
|
90
|
+
background: var(--xeno-control);
|
|
91
|
+
border-radius: var(--xeno-radius-xs);
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
/* Nothing selected (a `value` matching no option) — no thumb rather than a thumb parked on the first. */
|
|
95
|
+
.xeno-segmented[data-selection='off'] .xeno-segmented-thumb {
|
|
96
|
+
opacity: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.xeno-segmented-option {
|
|
100
|
+
position: relative;
|
|
101
|
+
z-index: 1; /* over the thumb */
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
min-width: 0; /* the grid track owns the width; do not let the label put a floor under it */
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
gap: var(--xeno-gap, 6px);
|
|
107
|
+
height: calc(var(--xeno-h, 32px) - 4px);
|
|
108
|
+
padding: 0 var(--xeno-padx, 12px);
|
|
109
|
+
font-family: inherit;
|
|
110
|
+
font-size: var(--xeno-font, 14px);
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
line-height: 1;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
color: var(--xeno-muted);
|
|
115
|
+
background: transparent;
|
|
116
|
+
border: none;
|
|
117
|
+
border-radius: var(--xeno-radius-xs);
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
user-select: none;
|
|
120
|
+
transition:
|
|
121
|
+
color var(--xeno-dur) var(--xeno-ease),
|
|
122
|
+
background var(--xeno-dur) var(--xeno-ease);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Hover brightens the LABEL and nothing else. A tint behind an unselected option is a second filled
|
|
126
|
+
surface sliding under the thumb — which is exactly what reads as the background being wrong mid-swap.
|
|
127
|
+
One control, one fill: the thumb. */
|
|
128
|
+
.xeno-segmented-option:hover:not([data-availability='disabled']):not([data-selection='on']) {
|
|
129
|
+
color: var(--xeno-text);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* The selected option no longer paints a fill of its own — the thumb under it is the fill. */
|
|
133
|
+
.xeno-segmented-option[data-selection='on'] {
|
|
134
|
+
color: var(--xeno-text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.xeno-segmented-option:focus-visible {
|
|
138
|
+
outline: none;
|
|
139
|
+
box-shadow:
|
|
140
|
+
0 0 0 2px var(--xeno-canvas),
|
|
141
|
+
0 0 0 4px var(--xeno-border);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.xeno-segmented-option[data-availability='disabled'] {
|
|
145
|
+
opacity: 0.4;
|
|
146
|
+
cursor: not-allowed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* The squash, keyed off the track's `data-index`. One keyframe track per position, all identical, for
|
|
150
|
+
the same reason as the sidebar's: moving to another segment only changes which rule matches, and a
|
|
151
|
+
browser will not restart an animation whose name did not change. Eight positions — a segmented
|
|
152
|
+
control with more options than that wants to be a Select. Past eight the thumb still travels; it
|
|
153
|
+
just stops squashing. */
|
|
154
|
+
.xeno-segmented[data-index='0'] .xeno-segmented-thumb { animation: xeno-seg-goo-0 var(--xeno-seg-dur) 1; }
|
|
155
|
+
.xeno-segmented[data-index='1'] .xeno-segmented-thumb { animation: xeno-seg-goo-1 var(--xeno-seg-dur) 1; }
|
|
156
|
+
.xeno-segmented[data-index='2'] .xeno-segmented-thumb { animation: xeno-seg-goo-2 var(--xeno-seg-dur) 1; }
|
|
157
|
+
.xeno-segmented[data-index='3'] .xeno-segmented-thumb { animation: xeno-seg-goo-3 var(--xeno-seg-dur) 1; }
|
|
158
|
+
.xeno-segmented[data-index='4'] .xeno-segmented-thumb { animation: xeno-seg-goo-4 var(--xeno-seg-dur) 1; }
|
|
159
|
+
.xeno-segmented[data-index='5'] .xeno-segmented-thumb { animation: xeno-seg-goo-5 var(--xeno-seg-dur) 1; }
|
|
160
|
+
.xeno-segmented[data-index='6'] .xeno-segmented-thumb { animation: xeno-seg-goo-6 var(--xeno-seg-dur) 1; }
|
|
161
|
+
.xeno-segmented[data-index='7'] .xeno-segmented-thumb { animation: xeno-seg-goo-7 var(--xeno-seg-dur) 1; }
|
|
162
|
+
/* Armed only once the value has actually changed — otherwise every segmented control on the page
|
|
163
|
+
squashes itself on load. */
|
|
164
|
+
.xeno-segmented:not([data-motion='ready']) .xeno-segmented-thumb { animation: none; }
|
|
165
|
+
|
|
166
|
+
@keyframes xeno-seg-goo-0 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
167
|
+
@keyframes xeno-seg-goo-1 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
168
|
+
@keyframes xeno-seg-goo-2 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
169
|
+
@keyframes xeno-seg-goo-3 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
170
|
+
@keyframes xeno-seg-goo-4 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
171
|
+
@keyframes xeno-seg-goo-5 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
172
|
+
@keyframes xeno-seg-goo-6 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
173
|
+
@keyframes xeno-seg-goo-7 { 0% { scale: 1 1; } 34% { scale: var(--xeno-seg-goo-x) var(--xeno-seg-goo-y); } 72% { scale: var(--xeno-seg-goo-x2) var(--xeno-seg-goo-y2); } 100% { scale: 1 1; } }
|
|
174
|
+
|
|
175
|
+
@media (prefers-reduced-motion: reduce) {
|
|
176
|
+
/* The travel lives on the track (the index), so that is where it has to be switched off. */
|
|
177
|
+
.xeno-segmented,
|
|
178
|
+
.xeno-segmented-option {
|
|
179
|
+
transition: none;
|
|
180
|
+
}
|
|
181
|
+
/* The thumb still marks the segment — it just gets there without travelling or squashing. */
|
|
182
|
+
.xeno-segmented-thumb {
|
|
183
|
+
animation: none !important;
|
|
184
|
+
}
|
|
185
|
+
}
|