@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
package/src/size.css
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Control size metrics, as CSS.
|
|
3
|
-
*
|
|
4
|
-
* These four numbers used to be written onto every control as an INLINE style, computed in JS from
|
|
5
|
-
* `controlSize` at render time. That worked, but it put the box metrics in the one place nothing can
|
|
6
|
-
* override: an inline style beats every stylesheet rule there is. Which means a surface — a product
|
|
7
|
-
* rendering the same elements for touch instead of a mouse — had no seam to reach them through.
|
|
8
|
-
*
|
|
9
|
-
* Here they are ambient instead. A control emits `data-xeno-size="md"` and reads the four variables;
|
|
10
|
-
* a surface redefines the variables for the sizes it wants bigger, once, at the root:
|
|
11
|
-
*
|
|
12
|
-
* .xeno[data-surface='mobile'] [data-xeno-size='md'] { --xeno-h: 44px; }
|
|
13
|
-
*
|
|
14
|
-
* Nothing at the call site changes, no context is threaded through the tree, and a product that sets
|
|
15
|
-
* no surface keeps exactly what it had.
|
|
16
|
-
*
|
|
17
|
-
* The attribute is namespaced (`data-xeno-size`, not `data-size`) for two reasons: it cannot collide
|
|
18
|
-
* with a host application's own `data-size`, and it therefore does not need to be scoped under
|
|
19
|
-
* `.xeno` — a control rendered outside the theme root still gets its metrics.
|
|
20
|
-
*
|
|
21
|
-
* DRIFT: the numbers below mirror `controlSize` in `@xenosystem/elements/tokens`, which stays the
|
|
22
|
-
* source of truth. `size.test.ts` fails if the two ever disagree.
|
|
23
|
-
*
|
|
24
|
-
* NOT here: glyph px. A `<XenoElement>` takes its size as a number prop, so an icon's scale is still
|
|
25
|
-
* resolved in JS (`iconPx`). Making icons follow a surface too is a separate change.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
[data-xeno-size='xs'] {
|
|
29
|
-
--xeno-h: 24px;
|
|
30
|
-
--xeno-padx: 8px;
|
|
31
|
-
--xeno-gap: 5px;
|
|
32
|
-
--xeno-font: 12px;
|
|
33
|
-
--xeno-icon: 15px;
|
|
34
|
-
}
|
|
35
|
-
[data-xeno-size='sm'] {
|
|
36
|
-
--xeno-h: 28px;
|
|
37
|
-
--xeno-padx: 10px;
|
|
38
|
-
--xeno-gap: 6px;
|
|
39
|
-
--xeno-font: 13px;
|
|
40
|
-
--xeno-icon: 16px;
|
|
41
|
-
}
|
|
42
|
-
[data-xeno-size='md'] {
|
|
43
|
-
--xeno-h: 32px;
|
|
44
|
-
--xeno-padx: 12px;
|
|
45
|
-
--xeno-gap: 6px;
|
|
46
|
-
--xeno-font: 14px;
|
|
47
|
-
--xeno-icon: 16px;
|
|
48
|
-
}
|
|
49
|
-
[data-xeno-size='lg'] {
|
|
50
|
-
--xeno-h: 36px;
|
|
51
|
-
--xeno-padx: 14px;
|
|
52
|
-
--xeno-gap: 7px;
|
|
53
|
-
--xeno-font: 14px;
|
|
54
|
-
--xeno-icon: 18px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* ── The touch surface ────────────────────────────────────────────────────────────────
|
|
58
|
-
* One block, declared by the product at its root:
|
|
59
|
-
*
|
|
60
|
-
* <div className="xeno" data-surface="mobile">
|
|
61
|
-
*
|
|
62
|
-
* `font` at md is 16px and that is not a taste call — iOS Safari ZOOMS the page when a focused input
|
|
63
|
-
* has a font under 16px, so a 14px text field makes the whole layout jump the instant it is tapped. */
|
|
64
|
-
.xeno[data-surface='mobile'] [data-xeno-size='xs'] {
|
|
65
|
-
--xeno-h: 28px;
|
|
66
|
-
--xeno-padx: 10px;
|
|
67
|
-
--xeno-gap: 6px;
|
|
68
|
-
--xeno-font: 13px;
|
|
69
|
-
--xeno-icon: 16px;
|
|
70
|
-
}
|
|
71
|
-
.xeno[data-surface='mobile'] [data-xeno-size='sm'] {
|
|
72
|
-
--xeno-h: 32px;
|
|
73
|
-
--xeno-padx: 12px;
|
|
74
|
-
--xeno-gap: 7px;
|
|
75
|
-
--xeno-font: 14px;
|
|
76
|
-
--xeno-icon: 17px;
|
|
77
|
-
}
|
|
78
|
-
.xeno[data-surface='mobile'] [data-xeno-size='md'] {
|
|
79
|
-
--xeno-h: 40px;
|
|
80
|
-
--xeno-padx: 16px;
|
|
81
|
-
--xeno-gap: 8px;
|
|
82
|
-
--xeno-font: 16px;
|
|
83
|
-
--xeno-icon: 20px;
|
|
84
|
-
}
|
|
85
|
-
.xeno[data-surface='mobile'] [data-xeno-size='lg'] {
|
|
86
|
-
--xeno-h: 48px;
|
|
87
|
-
--xeno-padx: 18px;
|
|
88
|
-
--xeno-gap: 9px;
|
|
89
|
-
--xeno-font: 16px;
|
|
90
|
-
--xeno-icon: 22px;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/* ── Glyphs follow the surface ────────────────────────────────────────────────────────
|
|
94
|
-
* A 16px glyph inside a 40px button is not the same design as a 16px glyph inside a 32px one — the
|
|
95
|
-
* ratio is what reads, and it has to hold. Desktop md is 32/16; touch md is 40/20. Same 50%.
|
|
96
|
-
*
|
|
97
|
-
* The renderer writes `width`/`height` as ATTRIBUTES on the svg, and a CSS rule outranks a
|
|
98
|
-
* presentation attribute — so this needs no renderer change, no new prop and no context, and the
|
|
99
|
-
* attributes stay put for the "renders correctly with no stylesheet" invariant.
|
|
100
|
-
*
|
|
101
|
-
* Only under a surface: with no surface declared the attribute already carries the right number, and
|
|
102
|
-
* a rule competing with it by default would be pure risk for no gain.
|
|
103
|
-
*
|
|
104
|
-
* Each owner is named rather than reaching every descendant, and that is the whole point — a blanket
|
|
105
|
-
* rule would also flatten the glyphs that are deliberately SMALLER than the control's own. The chip's
|
|
106
|
-
* remove × and the model picker's are `glyph - 2` by decision, and they keep their attribute. */
|
|
107
|
-
.xeno[data-surface='mobile'] .xeno-btn > .xeno-element,
|
|
108
|
-
.xeno[data-surface='mobile'] .xeno-input-icon > .xeno-element,
|
|
109
|
-
.xeno[data-surface='mobile'] .xeno-tab > .xeno-element,
|
|
110
|
-
.xeno[data-surface='mobile'] .xeno-chip-body > .xeno-element {
|
|
111
|
-
width: var(--xeno-icon);
|
|
112
|
-
height: var(--xeno-icon);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/* ── The hit target ───────────────────────────────────────────────────────────────────
|
|
116
|
-
* 44px square, Apple's HIG minimum and WCAG 2.1 AAA. It is the TAPPABLE area, not the visual one —
|
|
117
|
-
* keeping those separate is what lets the interface stay dense under a finger instead of becoming a
|
|
118
|
-
* column of slabs. Material does the same thing: 24dp of ink inside a 48dp target.
|
|
119
|
-
*
|
|
120
|
-
* It is gated on `pointer: coarse`, NOT on the surface, and the distinction matters. The surface is a
|
|
121
|
-
* decision the PRODUCT makes about density; the hit target answers a fact about the DEVICE. A phone
|
|
122
|
-
* opening the web platform has fingers whether or not anyone declared a surface, and it gets safe
|
|
123
|
-
* targets either way.
|
|
124
|
-
*
|
|
125
|
-
* The pseudo-element only exists inside the query, so on a precise pointer there is no extra layer
|
|
126
|
-
* over any control at all. `::after` on purpose: `::before` is already the goo fill on the toggle and
|
|
127
|
-
* the filter pill. Rows that can hold their own actions (list rows, table rows) are left out — an
|
|
128
|
-
* overlay across them would swallow clicks meant for a trailing button. */
|
|
129
|
-
.xeno {
|
|
130
|
-
--xeno-hit: 44px;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@media (pointer: coarse) {
|
|
134
|
-
.xeno-btn,
|
|
135
|
-
.xeno-icon-btn,
|
|
136
|
-
.xeno-chip,
|
|
137
|
-
.xeno-pill,
|
|
138
|
-
.xeno-tab,
|
|
139
|
-
.xeno-segmented-option,
|
|
140
|
-
.xeno-switch,
|
|
141
|
-
.xeno-checkbox,
|
|
142
|
-
.xeno-radio,
|
|
143
|
-
.xeno-sidebar-item {
|
|
144
|
-
position: relative;
|
|
145
|
-
}
|
|
146
|
-
.xeno-btn::after,
|
|
147
|
-
.xeno-icon-btn::after,
|
|
148
|
-
.xeno-chip::after,
|
|
149
|
-
.xeno-pill::after,
|
|
150
|
-
.xeno-tab::after,
|
|
151
|
-
.xeno-segmented-option::after,
|
|
152
|
-
.xeno-switch::after,
|
|
153
|
-
.xeno-checkbox::after,
|
|
154
|
-
.xeno-radio::after,
|
|
155
|
-
.xeno-sidebar-item::after {
|
|
156
|
-
content: '';
|
|
157
|
-
position: absolute;
|
|
158
|
-
left: 50%;
|
|
159
|
-
top: 50%;
|
|
160
|
-
translate: -50% -50%;
|
|
161
|
-
width: 100%;
|
|
162
|
-
height: 100%;
|
|
163
|
-
min-width: var(--xeno-hit, 44px);
|
|
164
|
-
min-height: var(--xeno-hit, 44px);
|
|
165
|
-
/* Invisible, and it must never paint over the control it is extending. */
|
|
166
|
-
background: none;
|
|
167
|
-
border-radius: inherit;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/*
|
|
171
|
-
* Text fields are the exception, and they have to be: an overlay across a field would take the tap
|
|
172
|
-
* that was meant to focus it — a pseudo-element belongs to the wrapper, and clicking a div does not
|
|
173
|
-
* put a caret in the input inside it. A field cannot fake its target, so it earns one for real.
|
|
174
|
-
*/
|
|
175
|
-
.xeno-input,
|
|
176
|
-
.xeno-textarea {
|
|
177
|
-
min-height: var(--xeno-hit, 44px);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Control size metrics, as CSS.
|
|
3
|
+
*
|
|
4
|
+
* These four numbers used to be written onto every control as an INLINE style, computed in JS from
|
|
5
|
+
* `controlSize` at render time. That worked, but it put the box metrics in the one place nothing can
|
|
6
|
+
* override: an inline style beats every stylesheet rule there is. Which means a surface — a product
|
|
7
|
+
* rendering the same elements for touch instead of a mouse — had no seam to reach them through.
|
|
8
|
+
*
|
|
9
|
+
* Here they are ambient instead. A control emits `data-xeno-size="md"` and reads the four variables;
|
|
10
|
+
* a surface redefines the variables for the sizes it wants bigger, once, at the root:
|
|
11
|
+
*
|
|
12
|
+
* .xeno[data-surface='mobile'] [data-xeno-size='md'] { --xeno-h: 44px; }
|
|
13
|
+
*
|
|
14
|
+
* Nothing at the call site changes, no context is threaded through the tree, and a product that sets
|
|
15
|
+
* no surface keeps exactly what it had.
|
|
16
|
+
*
|
|
17
|
+
* The attribute is namespaced (`data-xeno-size`, not `data-size`) for two reasons: it cannot collide
|
|
18
|
+
* with a host application's own `data-size`, and it therefore does not need to be scoped under
|
|
19
|
+
* `.xeno` — a control rendered outside the theme root still gets its metrics.
|
|
20
|
+
*
|
|
21
|
+
* DRIFT: the numbers below mirror `controlSize` in `@xenosystem/elements/tokens`, which stays the
|
|
22
|
+
* source of truth. `size.test.ts` fails if the two ever disagree.
|
|
23
|
+
*
|
|
24
|
+
* NOT here: glyph px. A `<XenoElement>` takes its size as a number prop, so an icon's scale is still
|
|
25
|
+
* resolved in JS (`iconPx`). Making icons follow a surface too is a separate change.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
[data-xeno-size='xs'] {
|
|
29
|
+
--xeno-h: 24px;
|
|
30
|
+
--xeno-padx: 8px;
|
|
31
|
+
--xeno-gap: 5px;
|
|
32
|
+
--xeno-font: 12px;
|
|
33
|
+
--xeno-icon: 15px;
|
|
34
|
+
}
|
|
35
|
+
[data-xeno-size='sm'] {
|
|
36
|
+
--xeno-h: 28px;
|
|
37
|
+
--xeno-padx: 10px;
|
|
38
|
+
--xeno-gap: 6px;
|
|
39
|
+
--xeno-font: 13px;
|
|
40
|
+
--xeno-icon: 16px;
|
|
41
|
+
}
|
|
42
|
+
[data-xeno-size='md'] {
|
|
43
|
+
--xeno-h: 32px;
|
|
44
|
+
--xeno-padx: 12px;
|
|
45
|
+
--xeno-gap: 6px;
|
|
46
|
+
--xeno-font: 14px;
|
|
47
|
+
--xeno-icon: 16px;
|
|
48
|
+
}
|
|
49
|
+
[data-xeno-size='lg'] {
|
|
50
|
+
--xeno-h: 36px;
|
|
51
|
+
--xeno-padx: 14px;
|
|
52
|
+
--xeno-gap: 7px;
|
|
53
|
+
--xeno-font: 14px;
|
|
54
|
+
--xeno-icon: 18px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ── The touch surface ────────────────────────────────────────────────────────────────
|
|
58
|
+
* One block, declared by the product at its root:
|
|
59
|
+
*
|
|
60
|
+
* <div className="xeno" data-surface="mobile">
|
|
61
|
+
*
|
|
62
|
+
* `font` at md is 16px and that is not a taste call — iOS Safari ZOOMS the page when a focused input
|
|
63
|
+
* has a font under 16px, so a 14px text field makes the whole layout jump the instant it is tapped. */
|
|
64
|
+
.xeno[data-surface='mobile'] [data-xeno-size='xs'] {
|
|
65
|
+
--xeno-h: 28px;
|
|
66
|
+
--xeno-padx: 10px;
|
|
67
|
+
--xeno-gap: 6px;
|
|
68
|
+
--xeno-font: 13px;
|
|
69
|
+
--xeno-icon: 16px;
|
|
70
|
+
}
|
|
71
|
+
.xeno[data-surface='mobile'] [data-xeno-size='sm'] {
|
|
72
|
+
--xeno-h: 32px;
|
|
73
|
+
--xeno-padx: 12px;
|
|
74
|
+
--xeno-gap: 7px;
|
|
75
|
+
--xeno-font: 14px;
|
|
76
|
+
--xeno-icon: 17px;
|
|
77
|
+
}
|
|
78
|
+
.xeno[data-surface='mobile'] [data-xeno-size='md'] {
|
|
79
|
+
--xeno-h: 40px;
|
|
80
|
+
--xeno-padx: 16px;
|
|
81
|
+
--xeno-gap: 8px;
|
|
82
|
+
--xeno-font: 16px;
|
|
83
|
+
--xeno-icon: 20px;
|
|
84
|
+
}
|
|
85
|
+
.xeno[data-surface='mobile'] [data-xeno-size='lg'] {
|
|
86
|
+
--xeno-h: 48px;
|
|
87
|
+
--xeno-padx: 18px;
|
|
88
|
+
--xeno-gap: 9px;
|
|
89
|
+
--xeno-font: 16px;
|
|
90
|
+
--xeno-icon: 22px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ── Glyphs follow the surface ────────────────────────────────────────────────────────
|
|
94
|
+
* A 16px glyph inside a 40px button is not the same design as a 16px glyph inside a 32px one — the
|
|
95
|
+
* ratio is what reads, and it has to hold. Desktop md is 32/16; touch md is 40/20. Same 50%.
|
|
96
|
+
*
|
|
97
|
+
* The renderer writes `width`/`height` as ATTRIBUTES on the svg, and a CSS rule outranks a
|
|
98
|
+
* presentation attribute — so this needs no renderer change, no new prop and no context, and the
|
|
99
|
+
* attributes stay put for the "renders correctly with no stylesheet" invariant.
|
|
100
|
+
*
|
|
101
|
+
* Only under a surface: with no surface declared the attribute already carries the right number, and
|
|
102
|
+
* a rule competing with it by default would be pure risk for no gain.
|
|
103
|
+
*
|
|
104
|
+
* Each owner is named rather than reaching every descendant, and that is the whole point — a blanket
|
|
105
|
+
* rule would also flatten the glyphs that are deliberately SMALLER than the control's own. The chip's
|
|
106
|
+
* remove × and the model picker's are `glyph - 2` by decision, and they keep their attribute. */
|
|
107
|
+
.xeno[data-surface='mobile'] .xeno-btn > .xeno-element,
|
|
108
|
+
.xeno[data-surface='mobile'] .xeno-input-icon > .xeno-element,
|
|
109
|
+
.xeno[data-surface='mobile'] .xeno-tab > .xeno-element,
|
|
110
|
+
.xeno[data-surface='mobile'] .xeno-chip-body > .xeno-element {
|
|
111
|
+
width: var(--xeno-icon);
|
|
112
|
+
height: var(--xeno-icon);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ── The hit target ───────────────────────────────────────────────────────────────────
|
|
116
|
+
* 44px square, Apple's HIG minimum and WCAG 2.1 AAA. It is the TAPPABLE area, not the visual one —
|
|
117
|
+
* keeping those separate is what lets the interface stay dense under a finger instead of becoming a
|
|
118
|
+
* column of slabs. Material does the same thing: 24dp of ink inside a 48dp target.
|
|
119
|
+
*
|
|
120
|
+
* It is gated on `pointer: coarse`, NOT on the surface, and the distinction matters. The surface is a
|
|
121
|
+
* decision the PRODUCT makes about density; the hit target answers a fact about the DEVICE. A phone
|
|
122
|
+
* opening the web platform has fingers whether or not anyone declared a surface, and it gets safe
|
|
123
|
+
* targets either way.
|
|
124
|
+
*
|
|
125
|
+
* The pseudo-element only exists inside the query, so on a precise pointer there is no extra layer
|
|
126
|
+
* over any control at all. `::after` on purpose: `::before` is already the goo fill on the toggle and
|
|
127
|
+
* the filter pill. Rows that can hold their own actions (list rows, table rows) are left out — an
|
|
128
|
+
* overlay across them would swallow clicks meant for a trailing button. */
|
|
129
|
+
.xeno {
|
|
130
|
+
--xeno-hit: 44px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@media (pointer: coarse) {
|
|
134
|
+
.xeno-btn,
|
|
135
|
+
.xeno-icon-btn,
|
|
136
|
+
.xeno-chip,
|
|
137
|
+
.xeno-pill,
|
|
138
|
+
.xeno-tab,
|
|
139
|
+
.xeno-segmented-option,
|
|
140
|
+
.xeno-switch,
|
|
141
|
+
.xeno-checkbox,
|
|
142
|
+
.xeno-radio,
|
|
143
|
+
.xeno-sidebar-item {
|
|
144
|
+
position: relative;
|
|
145
|
+
}
|
|
146
|
+
.xeno-btn::after,
|
|
147
|
+
.xeno-icon-btn::after,
|
|
148
|
+
.xeno-chip::after,
|
|
149
|
+
.xeno-pill::after,
|
|
150
|
+
.xeno-tab::after,
|
|
151
|
+
.xeno-segmented-option::after,
|
|
152
|
+
.xeno-switch::after,
|
|
153
|
+
.xeno-checkbox::after,
|
|
154
|
+
.xeno-radio::after,
|
|
155
|
+
.xeno-sidebar-item::after {
|
|
156
|
+
content: '';
|
|
157
|
+
position: absolute;
|
|
158
|
+
left: 50%;
|
|
159
|
+
top: 50%;
|
|
160
|
+
translate: -50% -50%;
|
|
161
|
+
width: 100%;
|
|
162
|
+
height: 100%;
|
|
163
|
+
min-width: var(--xeno-hit, 44px);
|
|
164
|
+
min-height: var(--xeno-hit, 44px);
|
|
165
|
+
/* Invisible, and it must never paint over the control it is extending. */
|
|
166
|
+
background: none;
|
|
167
|
+
border-radius: inherit;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
* Text fields are the exception, and they have to be: an overlay across a field would take the tap
|
|
172
|
+
* that was meant to focus it — a pseudo-element belongs to the wrapper, and clicking a div does not
|
|
173
|
+
* put a caret in the input inside it. A field cannot fake its target, so it earns one for real.
|
|
174
|
+
*/
|
|
175
|
+
.xeno-input,
|
|
176
|
+
.xeno-textarea {
|
|
177
|
+
min-height: var(--xeno-hit, 44px);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* ProgressBar — a determinate progress meter. A flat track (`--xeno-control`, `radius-sm`, ~4px tall)
|
|
3
|
-
* holds a fill (`--xeno-text`) whose width is the clamped value (0..1) as a percentage. An optional
|
|
4
|
-
* header row carries the label + a tabular percent readout; it is `aria-hidden` because the track
|
|
5
|
-
* (`role="progressbar"`) already announces name + value. Rounded squares, monochrome shell, theme
|
|
6
|
-
* tokens only — no literal colours.
|
|
7
|
-
*/
|
|
8
|
-
.xeno-progressbar {
|
|
9
|
-
display: flex;
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
gap: 6px;
|
|
12
|
-
width: 100%;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* label + percent readout above the track */
|
|
16
|
-
.xeno-progressbar-header {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: baseline;
|
|
19
|
-
justify-content: space-between;
|
|
20
|
-
gap: 8px;
|
|
21
|
-
font-family: inherit;
|
|
22
|
-
font-size: 12px;
|
|
23
|
-
line-height: 1;
|
|
24
|
-
color: var(--xeno-muted);
|
|
25
|
-
}
|
|
26
|
-
.xeno-progressbar-value {
|
|
27
|
-
font-variant-numeric: tabular-nums;
|
|
28
|
-
color: var(--xeno-text);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* The track IS the progressbar element.
|
|
32
|
-
*
|
|
33
|
-
* A radius only reads as a CORNER while it stays well under half the smaller dimension; at half it is
|
|
34
|
-
* a semicircle and the bar is a capsule, whatever the token was called. `--xeno-radius-sm` (5px) on a
|
|
35
|
-
* 4px bar was 125% of its own height — as round as a rounded shape can get.
|
|
36
|
-
*
|
|
37
|
-
* The fix is height, not a smaller number: 6px with `--xeno-radius-hair` puts it at 25%, the same
|
|
38
|
-
* ratio as the scrollbar thumb, and the corner is actually visible at that size. Squeezing the radius
|
|
39
|
-
* instead would have meant a sub-token literal for a bar too thin to show it anyway. */
|
|
40
|
-
.xeno-progressbar-track {
|
|
41
|
-
display: block;
|
|
42
|
-
width: 100%;
|
|
43
|
-
height: 6px;
|
|
44
|
-
background: var(--xeno-control);
|
|
45
|
-
border-radius: var(--xeno-radius-hair);
|
|
46
|
-
overflow: hidden;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.xeno-progressbar-fill {
|
|
50
|
-
display: block;
|
|
51
|
-
height: 100%;
|
|
52
|
-
min-width: 0;
|
|
53
|
-
background: var(--xeno-text);
|
|
54
|
-
border-radius: inherit;
|
|
55
|
-
transition: width var(--xeno-dur) var(--xeno-ease);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* ── Indeterminate ────────────────────────────────────────────────────────────────────
|
|
59
|
-
* Work is happening; how much is left is not known. A short segment sweeps the track end to end,
|
|
60
|
-
* because that is the one motion that says "running" without also claiming a position — a bar that
|
|
61
|
-
* grows says something about progress whether or not it means to.
|
|
62
|
-
*
|
|
63
|
-
* The sweep is deliberately not symmetric: it enters at speed, slows across the middle where the eye
|
|
64
|
-
* can read it, then leaves. `translate` on a fixed-width segment, so nothing is laid out per frame.
|
|
65
|
-
* The track already clips (`overflow: hidden`), so the segment simply runs off both ends. */
|
|
66
|
-
.xeno-progressbar-track[data-state='indeterminate'] .xeno-progressbar-fill {
|
|
67
|
-
width: 34%;
|
|
68
|
-
transition: none;
|
|
69
|
-
animation: xeno-progress-sweep 1400ms var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1))
|
|
70
|
-
infinite;
|
|
71
|
-
}
|
|
72
|
-
@keyframes xeno-progress-sweep {
|
|
73
|
-
from { translate: -110% 0; }
|
|
74
|
-
to { translate: 330% 0; }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
@media (prefers-reduced-motion: reduce) {
|
|
78
|
-
.xeno-progressbar-fill {
|
|
79
|
-
transition: none;
|
|
80
|
-
}
|
|
81
|
-
/* A perpetual sweep is exactly what reduced-motion is asking not to see. The bar still says "busy"
|
|
82
|
-
— it just says it by sitting there at a partial width instead of running. */
|
|
83
|
-
.xeno-progressbar-track[data-state='indeterminate'] .xeno-progressbar-fill {
|
|
84
|
-
animation: none;
|
|
85
|
-
width: 100%;
|
|
86
|
-
opacity: 0.45;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* ProgressBar — a determinate progress meter. A flat track (`--xeno-control`, `radius-sm`, ~4px tall)
|
|
3
|
+
* holds a fill (`--xeno-text`) whose width is the clamped value (0..1) as a percentage. An optional
|
|
4
|
+
* header row carries the label + a tabular percent readout; it is `aria-hidden` because the track
|
|
5
|
+
* (`role="progressbar"`) already announces name + value. Rounded squares, monochrome shell, theme
|
|
6
|
+
* tokens only — no literal colours.
|
|
7
|
+
*/
|
|
8
|
+
.xeno-progressbar {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 6px;
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* label + percent readout above the track */
|
|
16
|
+
.xeno-progressbar-header {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: baseline;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
gap: 8px;
|
|
21
|
+
font-family: inherit;
|
|
22
|
+
font-size: 12px;
|
|
23
|
+
line-height: 1;
|
|
24
|
+
color: var(--xeno-muted);
|
|
25
|
+
}
|
|
26
|
+
.xeno-progressbar-value {
|
|
27
|
+
font-variant-numeric: tabular-nums;
|
|
28
|
+
color: var(--xeno-text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* The track IS the progressbar element.
|
|
32
|
+
*
|
|
33
|
+
* A radius only reads as a CORNER while it stays well under half the smaller dimension; at half it is
|
|
34
|
+
* a semicircle and the bar is a capsule, whatever the token was called. `--xeno-radius-sm` (5px) on a
|
|
35
|
+
* 4px bar was 125% of its own height — as round as a rounded shape can get.
|
|
36
|
+
*
|
|
37
|
+
* The fix is height, not a smaller number: 6px with `--xeno-radius-hair` puts it at 25%, the same
|
|
38
|
+
* ratio as the scrollbar thumb, and the corner is actually visible at that size. Squeezing the radius
|
|
39
|
+
* instead would have meant a sub-token literal for a bar too thin to show it anyway. */
|
|
40
|
+
.xeno-progressbar-track {
|
|
41
|
+
display: block;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 6px;
|
|
44
|
+
background: var(--xeno-control);
|
|
45
|
+
border-radius: var(--xeno-radius-hair);
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.xeno-progressbar-fill {
|
|
50
|
+
display: block;
|
|
51
|
+
height: 100%;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
background: var(--xeno-text);
|
|
54
|
+
border-radius: inherit;
|
|
55
|
+
transition: width var(--xeno-dur) var(--xeno-ease);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ── Indeterminate ────────────────────────────────────────────────────────────────────
|
|
59
|
+
* Work is happening; how much is left is not known. A short segment sweeps the track end to end,
|
|
60
|
+
* because that is the one motion that says "running" without also claiming a position — a bar that
|
|
61
|
+
* grows says something about progress whether or not it means to.
|
|
62
|
+
*
|
|
63
|
+
* The sweep is deliberately not symmetric: it enters at speed, slows across the middle where the eye
|
|
64
|
+
* can read it, then leaves. `translate` on a fixed-width segment, so nothing is laid out per frame.
|
|
65
|
+
* The track already clips (`overflow: hidden`), so the segment simply runs off both ends. */
|
|
66
|
+
.xeno-progressbar-track[data-state='indeterminate'] .xeno-progressbar-fill {
|
|
67
|
+
width: 34%;
|
|
68
|
+
transition: none;
|
|
69
|
+
animation: xeno-progress-sweep 1400ms var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1))
|
|
70
|
+
infinite;
|
|
71
|
+
}
|
|
72
|
+
@keyframes xeno-progress-sweep {
|
|
73
|
+
from { translate: -110% 0; }
|
|
74
|
+
to { translate: 330% 0; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-reduced-motion: reduce) {
|
|
78
|
+
.xeno-progressbar-fill {
|
|
79
|
+
transition: none;
|
|
80
|
+
}
|
|
81
|
+
/* A perpetual sweep is exactly what reduced-motion is asking not to see. The bar still says "busy"
|
|
82
|
+
— it just says it by sitting there at a partial width instead of running. */
|
|
83
|
+
.xeno-progressbar-track[data-state='indeterminate'] .xeno-progressbar-fill {
|
|
84
|
+
animation: none;
|
|
85
|
+
width: 100%;
|
|
86
|
+
opacity: 0.45;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/status/spinner.css
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Spinner — an indeterminate loader. A small rounded SQUARE outline (never a circle): all four edges
|
|
3
|
-
* sit in `--xeno-muted` as the track and the top edge burns to `--xeno-text` as the bright leading
|
|
4
|
-
* edge, the whole square rotating. Size + stroke arrive as inline vars (`--xeno-spinner-size`/
|
|
5
|
-
* `--xeno-spinner-stroke`) set by the component from `size`, so the metrics never drift from the data.
|
|
6
|
-
* Motion is gated behind `prefers-reduced-motion: no-preference`. Monochrome shell, theme tokens only.
|
|
7
|
-
*/
|
|
8
|
-
.xeno-spinner {
|
|
9
|
-
display: inline-block;
|
|
10
|
-
box-sizing: border-box;
|
|
11
|
-
width: var(--xeno-spinner-size, 16px);
|
|
12
|
-
height: var(--xeno-spinner-size, 16px);
|
|
13
|
-
/* Track and leading edge are named, and default to the two inks. A consumer that needs the loader
|
|
14
|
-
to belong to something coloured — a status row for one live activity, say — can say so here
|
|
15
|
-
instead of reassigning `--xeno-text` on the element, which is the whole app's ink and means
|
|
16
|
-
something else. Omit them and nothing changes. */
|
|
17
|
-
border: var(--xeno-spinner-stroke, 2px) solid var(--xeno-spinner-track, var(--xeno-muted));
|
|
18
|
-
border-top-color: var(--xeno-spinner-edge, var(--xeno-text));
|
|
19
|
-
border-radius: var(--xeno-radius-xs);
|
|
20
|
-
vertical-align: middle;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
24
|
-
.xeno-spinner {
|
|
25
|
-
animation: xeno-spinner-rot 0.7s linear infinite;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@keyframes xeno-spinner-rot {
|
|
30
|
-
to {
|
|
31
|
-
transform: rotate(360deg);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Spinner — an indeterminate loader. A small rounded SQUARE outline (never a circle): all four edges
|
|
3
|
+
* sit in `--xeno-muted` as the track and the top edge burns to `--xeno-text` as the bright leading
|
|
4
|
+
* edge, the whole square rotating. Size + stroke arrive as inline vars (`--xeno-spinner-size`/
|
|
5
|
+
* `--xeno-spinner-stroke`) set by the component from `size`, so the metrics never drift from the data.
|
|
6
|
+
* Motion is gated behind `prefers-reduced-motion: no-preference`. Monochrome shell, theme tokens only.
|
|
7
|
+
*/
|
|
8
|
+
.xeno-spinner {
|
|
9
|
+
display: inline-block;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
width: var(--xeno-spinner-size, 16px);
|
|
12
|
+
height: var(--xeno-spinner-size, 16px);
|
|
13
|
+
/* Track and leading edge are named, and default to the two inks. A consumer that needs the loader
|
|
14
|
+
to belong to something coloured — a status row for one live activity, say — can say so here
|
|
15
|
+
instead of reassigning `--xeno-text` on the element, which is the whole app's ink and means
|
|
16
|
+
something else. Omit them and nothing changes. */
|
|
17
|
+
border: var(--xeno-spinner-stroke, 2px) solid var(--xeno-spinner-track, var(--xeno-muted));
|
|
18
|
+
border-top-color: var(--xeno-spinner-edge, var(--xeno-text));
|
|
19
|
+
border-radius: var(--xeno-radius-xs);
|
|
20
|
+
vertical-align: middle;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
24
|
+
.xeno-spinner {
|
|
25
|
+
animation: xeno-spinner-rot 0.7s linear infinite;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@keyframes xeno-spinner-rot {
|
|
30
|
+
to {
|
|
31
|
+
transform: rotate(360deg);
|
|
32
|
+
}
|
|
33
|
+
}
|