@junoput01/junoui 0.6.0 → 0.8.0
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/CHANGELOG.md +209 -0
- package/README.md +17 -16
- package/dist/android/dimens.xml +2 -0
- package/dist/classes.json +1774 -0
- package/dist/css/juno-tokens.css +10 -0
- package/dist/css/juno.css +1057 -56
- package/dist/flutter/juno_tokens.dart +10 -0
- package/dist/ios/JunoTokens.swift +10 -0
- package/dist/js/tokens.js +16 -0
- package/dist/json/tokens.json +51 -0
- package/dist/rust/juno_tokens.rs +383 -0
- package/dist/scss/_juno-tokens.scss +10 -0
- package/docs/accessibility.md +6 -0
- package/docs/browser-support.md +3 -0
- package/docs/components/button.md +11 -2
- package/docs/components/canvas-ink.md +71 -0
- package/docs/components/dock.md +34 -0
- package/docs/components/fold-slot.md +26 -1
- package/docs/components/gizmo.md +114 -0
- package/docs/components/swatch.md +95 -0
- package/docs/components/tree.md +112 -0
- package/docs/conformance-kit.md +255 -0
- package/docs/getting-started.md +14 -0
- package/docs/integration.md +52 -6
- package/docs/ios-conformance.md +200 -3
- package/docs/ios-pwa.md +273 -0
- package/docs/native.md +38 -1
- package/docs/tokens-reference.md +15 -0
- package/package.json +8 -2
- package/src/css/base.css +35 -43
- package/src/css/components/button.css +42 -2
- package/src/css/components/canvas-ink.css +97 -0
- package/src/css/components/dock.css +65 -6
- package/src/css/components/fold-slot.css +49 -3
- package/src/css/components/gizmo.css +238 -0
- package/src/css/components/segmented.css +15 -2
- package/src/css/components/swatch.css +187 -0
- package/src/css/components/tree.css +259 -0
- package/src/css/touch-surfaces.mjs +95 -0
- package/tools/gizmo.mjs +144 -0
- package/tools/testing.mjs +177 -0
- package/tools/tree.mjs +178 -0
package/docs/tokens-reference.md
CHANGED
|
@@ -113,6 +113,21 @@ Color encodes status, never decoration. Each role has exactly one meaning.
|
|
|
113
113
|
| `shadow.2` | `0 4px 14px rgb(0 0 0 / 0.35)` | `var(--juno-shadow-2)` | `JunoTokens.shadow2` |
|
|
114
114
|
| `shadow.3` | `0 12px 32px rgb(0 0 0 / 0.50)` | `var(--juno-shadow-3)` | `JunoTokens.shadow3` |
|
|
115
115
|
|
|
116
|
+
### ink
|
|
117
|
+
|
|
118
|
+
| Token | Value | CSS variable | Flutter / iOS |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| `ink.canvas.ink` | `#FFFFFF` | `var(--juno-ink-canvas-ink)` | `JunoTokens.inkCanvasInk` |
|
|
121
|
+
| `ink.canvas.halo` | `#000000` | `var(--juno-ink-canvas-halo)` | `JunoTokens.inkCanvasHalo` |
|
|
122
|
+
| `ink.canvas.halo-width` | `2px` | `var(--juno-ink-canvas-halo-width)` | `JunoTokens.inkCanvasHalo-width` |
|
|
123
|
+
| `ink.canvas.halo-width-lg` | `3px` | `var(--juno-ink-canvas-halo-width-lg)` | `JunoTokens.inkCanvasHalo-width-lg` |
|
|
124
|
+
| `ink.canvas.scrim` | `0.28` | `var(--juno-ink-canvas-scrim)` | `JunoTokens.inkCanvasScrim` |
|
|
125
|
+
| `ink.vivid.nominal` | `oklch(76% 0.28 148)` | `var(--juno-ink-vivid-nominal)` | `JunoTokens.inkVividNominal` |
|
|
126
|
+
| `ink.vivid.active` | `oklch(76% 0.24 205)` | `var(--juno-ink-vivid-active)` | `JunoTokens.inkVividActive` |
|
|
127
|
+
| `ink.vivid.target` | `oklch(70% 0.30 328)` | `var(--juno-ink-vivid-target)` | `JunoTokens.inkVividTarget` |
|
|
128
|
+
| `ink.vivid.caution` | `oklch(82% 0.20 82)` | `var(--juno-ink-vivid-caution)` | `JunoTokens.inkVividCaution` |
|
|
129
|
+
| `ink.vivid.warning` | `oklch(68% 0.28 25)` | `var(--juno-ink-vivid-warning)` | `JunoTokens.inkVividWarning` |
|
|
130
|
+
|
|
116
131
|
### Motion
|
|
117
132
|
|
|
118
133
|
| Token | Value | CSS variable | Flutter / iOS |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junoput01/junoui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "junoui — a token-driven design system. Color carries semantic meaning, never decoration: every hue has one assigned role (NOMINAL / ACTIVE / TARGET / CAUTION / WARNING). Ships multi-platform tokens (CSS, SCSS, JS/TS, JSON, Android, iOS, Flutter) plus a framework-agnostic CSS component layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,8 +48,13 @@
|
|
|
48
48
|
"./android/dimens": "./dist/android/dimens.xml",
|
|
49
49
|
"./ios": "./dist/ios/JunoTokens.swift",
|
|
50
50
|
"./flutter": "./dist/flutter/juno_tokens.dart",
|
|
51
|
+
"./rust": "./dist/rust/juno_tokens.rs",
|
|
51
52
|
"./icons": "./dist/icons/juno-icons.svg",
|
|
52
53
|
"./subset": "./tools/subset-sprite.mjs",
|
|
54
|
+
"./tree": "./tools/tree.mjs",
|
|
55
|
+
"./gizmo": "./tools/gizmo.mjs",
|
|
56
|
+
"./testing": "./tools/testing.mjs",
|
|
57
|
+
"./classes.json": "./dist/classes.json",
|
|
53
58
|
"./icons/inline": "./dist/icons/inline.js",
|
|
54
59
|
"./icons/install": "./dist/icons/install.js",
|
|
55
60
|
"./package.json": "./package.json"
|
|
@@ -68,7 +73,8 @@
|
|
|
68
73
|
"build:tokens": "node style-dictionary.config.mjs",
|
|
69
74
|
"build:icons": "node scripts/build-icons.mjs",
|
|
70
75
|
"build:css": "node scripts/bundle-css.mjs",
|
|
71
|
-
"build": "
|
|
76
|
+
"build:classes": "node scripts/build-classes.mjs",
|
|
77
|
+
"build": "npm run build:tokens && npm run build:icons && npm run build:css && npm run build:classes",
|
|
72
78
|
"gen-docs": "node scripts/gen-docs.mjs",
|
|
73
79
|
"clean": "node -e \"import('node:fs').then(fs=>fs.rmSync('dist',{recursive:true,force:true}))\"",
|
|
74
80
|
"lint:css": "stylelint \"src/css/**/*.css\"",
|
package/src/css/base.css
CHANGED
|
@@ -47,9 +47,30 @@
|
|
|
47
47
|
var(--juno-size-tap-comfortable) + 4 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
48
48
|
);
|
|
49
49
|
--juno-dock-clearance-scale: 1;
|
|
50
|
+
|
|
51
|
+
/* The bar's own offset from the bottom edge — declared ONCE and consumed by
|
|
52
|
+
both the bar's margin (dock.css) and the clearance below, so the two cannot
|
|
53
|
+
disagree about where the bar sits.
|
|
54
|
+
|
|
55
|
+
That is the whole reason this is a token rather than a literal in each
|
|
56
|
+
place. junoui's default is additive, per docs/ios-conformance.md: a
|
|
57
|
+
FLOATING element sits off the edge, so its gap and the inset stack. A
|
|
58
|
+
consumer whose design wants the bar flush above the home indicator instead
|
|
59
|
+
writes `max(8px, env(safe-area-inset-bottom, 0px))` here and BOTH the
|
|
60
|
+
margin and the reservation follow it. Before this token the form was baked
|
|
61
|
+
into each site separately, so a consumer that changed one silently
|
|
62
|
+
disagreed with the other — measured at 16px of dead band at inset 0 and
|
|
63
|
+
24px at inset 34 (20260815-055), and no single --juno-dock-h could
|
|
64
|
+
reconcile them because one side added the inset and the other maxed it. */
|
|
65
|
+
--juno-dock-edge-offset: calc(var(--juno-space-16) + env(safe-area-inset-bottom, 0px));
|
|
66
|
+
|
|
67
|
+
/* Gap between the bar's top edge and the last row of content. Zero is a
|
|
68
|
+
legitimate setting: a translucent bar that content is MEANT to scroll under
|
|
69
|
+
wants overlap, not breathing room. */
|
|
70
|
+
--juno-dock-clearance-breathing: var(--juno-space-8);
|
|
50
71
|
--juno-dock-clearance: calc(
|
|
51
|
-
var(--juno-dock-h) * var(--juno-dock-clearance-scale) + var(--juno-
|
|
52
|
-
var(--juno-
|
|
72
|
+
var(--juno-dock-h) * var(--juno-dock-clearance-scale) + var(--juno-dock-edge-offset) +
|
|
73
|
+
var(--juno-dock-clearance-breathing)
|
|
53
74
|
);
|
|
54
75
|
|
|
55
76
|
/* The pillbar is the same shape one size down: no bubble, so its height is
|
|
@@ -58,8 +79,8 @@
|
|
|
58
79
|
var(--juno-size-tap-comfortable) + 2 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
59
80
|
);
|
|
60
81
|
--juno-pillbar-clearance: calc(
|
|
61
|
-
var(--juno-pillbar-h) * var(--juno-dock-clearance-scale) + var(--juno-
|
|
62
|
-
var(--juno-
|
|
82
|
+
var(--juno-pillbar-h) * var(--juno-dock-clearance-scale) + var(--juno-dock-edge-offset) +
|
|
83
|
+
var(--juno-dock-clearance-breathing)
|
|
63
84
|
);
|
|
64
85
|
|
|
65
86
|
/* JS-readable motion contract. CSS-only `prefers-reduced-motion` never reaches
|
|
@@ -168,47 +189,18 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
168
189
|
`.juno-input` font-size later in the bundle. It did, silently, until
|
|
169
190
|
20260815-006's coarse-pointer project measured it. */
|
|
170
191
|
|
|
171
|
-
/*
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.juno-btn,
|
|
176
|
-
.juno-dock__item,
|
|
177
|
-
.juno-pillbar__item,
|
|
178
|
-
.juno-tabs__tab,
|
|
179
|
-
.juno-list__item,
|
|
180
|
-
.juno-menu__item
|
|
181
|
-
) {
|
|
182
|
-
-webkit-tap-highlight-color: transparent;
|
|
183
|
-
}
|
|
192
|
+
/* The tap-highlight default that used to live here is GENERATED now, from
|
|
193
|
+
src/css/touch-surfaces.mjs, together with the touch-action default that
|
|
194
|
+
shares its member list. Two hand-maintained lists had drifted from the
|
|
195
|
+
classes and from each other; see that file. */
|
|
184
196
|
}
|
|
185
197
|
|
|
186
|
-
/*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
NOT inside the pointer:coarse block above: a hybrid device (touch laptop,
|
|
193
|
-
iPad with a trackpad) reports a fine primary pointer while still taking
|
|
194
|
-
touch input, and the property is inert on a mouse anyway.
|
|
195
|
-
Community convention — no primary Apple/WebKit source names it; see
|
|
196
|
-
docs/ios-conformance.md. Named components only, so a consumer's own elements
|
|
197
|
-
are untouched. See 20260803-038. */
|
|
198
|
-
:where(
|
|
199
|
-
.juno-btn,
|
|
200
|
-
.juno-dock__item,
|
|
201
|
-
.juno-pillbar__item,
|
|
202
|
-
.juno-pillbar__overflow,
|
|
203
|
-
.juno-tabs__tab,
|
|
204
|
-
.juno-list__item,
|
|
205
|
-
.juno-menu__item,
|
|
206
|
-
.juno-seg__option,
|
|
207
|
-
.juno-chip,
|
|
208
|
-
.juno-toggle-btn
|
|
209
|
-
) {
|
|
210
|
-
touch-action: manipulation;
|
|
211
|
-
}
|
|
198
|
+
/* The touch defaults that used to be two hand-maintained `:where()` lists here
|
|
199
|
+
are GENERATED from src/css/touch-surfaces.mjs and emitted straight after this
|
|
200
|
+
file. Both lists had drifted — from the classes (`.juno-seg__option`,
|
|
201
|
+
`.juno-list__item`: neither exists, so `:where()` matched nothing and the
|
|
202
|
+
rule parsed anyway) and from each other. One declared set now feeds both.
|
|
203
|
+
See 20260826-024 and docs/conformance-kit.md. */
|
|
212
204
|
|
|
213
205
|
/* Gesture-owned surfaces — for an element whose pointer events are fully
|
|
214
206
|
driven by app JS (drag-pan, pinch-zoom, swipe classification: a state
|
|
@@ -40,8 +40,10 @@
|
|
|
40
40
|
.juno-btn--ghost:hover { background: var(--juno-s2); filter: none; }
|
|
41
41
|
|
|
42
42
|
/* Small — for dense desktop toolbars (40–46px bars) where the default
|
|
43
|
-
control would not fit.
|
|
44
|
-
|
|
43
|
+
control would not fit. Combines with --ghost + roles.
|
|
44
|
+
On a FINE pointer this is 24px: the WCAG 2.2 AA floor (2.5.8) exactly, and
|
|
45
|
+
no more. On a coarse pointer it promotes to the comfortable target — see
|
|
46
|
+
below. */
|
|
45
47
|
.juno-btn--sm {
|
|
46
48
|
min-height: var(--juno-space-24);
|
|
47
49
|
font-size: var(--juno-font-size-11);
|
|
@@ -50,6 +52,44 @@
|
|
|
50
52
|
border-radius: var(--juno-radius-3);
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/* --sm names a DENSITY, and consumers reach for it as a SEMANTIC. Audited
|
|
56
|
+
across one app: 40 call sites, nearly all of them `--sm --ghost` meaning
|
|
57
|
+
"secondary", shipping a 24px target on a phone — and junoui's own showcase
|
|
58
|
+
does it twice in a navbar action slot. A size modifier should not quietly
|
|
59
|
+
become a tap-target decision, so on touch it holds the same floor every
|
|
60
|
+
other control does. Type and padding still shrink: --sm stays a density
|
|
61
|
+
modifier, it just stops being a touch-target one (20260826-026).
|
|
62
|
+
|
|
63
|
+
THE RULE LIVES HERE, not in base.css's (pointer: coarse) block: a media
|
|
64
|
+
query adds no specificity, so a `.juno-btn--sm` there would lose to this
|
|
65
|
+
file's own `.juno-btn--sm` later in the bundle. That is not a guess — it is
|
|
66
|
+
what silently happened to the .juno-input font floor until 20260815-006
|
|
67
|
+
measured it.
|
|
68
|
+
|
|
69
|
+
--dense is the opt-out, and it is deliberately only meaningful ON --sm:
|
|
70
|
+
a dense touch toolbar is a real thing (a media scrubber, an editor rail),
|
|
71
|
+
but it is a decision someone has to make by name rather than inherit from a
|
|
72
|
+
size.
|
|
73
|
+
|
|
74
|
+
The two rules below are REDUNDANT ON PURPOSE, and mutation testing is how
|
|
75
|
+
that became a decision rather than an accident: deleting either one leaves
|
|
76
|
+
--dense at 24px. The `:not()` excludes a dense button from the promotion, so
|
|
77
|
+
the pairing below it is dead weight — until the `:not()` names the wrong
|
|
78
|
+
class, at which point the pairing (also (0,2,0), and later in the file) is
|
|
79
|
+
the only thing holding the opt-out. Each covers the other's failure, so a
|
|
80
|
+
single edit to either cannot silently promote a --dense button. Both
|
|
81
|
+
selectors are (0,2,0) and mutually exclusive in the correct state, so they
|
|
82
|
+
never race. */
|
|
83
|
+
@media (pointer: coarse) {
|
|
84
|
+
.juno-btn--sm:not(.juno-btn--dense) {
|
|
85
|
+
min-height: var(--juno-size-tap-min);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.juno-btn--sm.juno-btn--dense {
|
|
89
|
+
min-height: var(--juno-space-24);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
53
93
|
.juno-btn:disabled {
|
|
54
94
|
background: var(--juno-muted);
|
|
55
95
|
color: var(--juno-label);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* Component — Canvas ink (marks drawn over arbitrary imagery)
|
|
3
|
+
* For text and vector ink sitting on a photo, a map, a video frame or a
|
|
4
|
+
* camera feed: anywhere the background is CONTENT rather than one of
|
|
5
|
+
* junoui's surfaces.
|
|
6
|
+
*
|
|
7
|
+
* WHY IT IS NOT JUST A COLOUR. junoui's whole contrast story assumes a
|
|
8
|
+
* controlled surface, s0 through s3 — every role colour's ratio is
|
|
9
|
+
* computed against a known background. Over imagery there is no known
|
|
10
|
+
* background. In one orthophoto a black shadow and a snowfield are
|
|
11
|
+
* adjacent pixels, so no single ink colour is legible and no contrast
|
|
12
|
+
* ratio can be asserted about one. The answer is a PAIR that spans the
|
|
13
|
+
* luminance range: over a light backing the halo carries the contrast,
|
|
14
|
+
* over a dark one the ink does. Neither half works alone, which is why
|
|
15
|
+
* they are applied together by one class.
|
|
16
|
+
*
|
|
17
|
+
* NOT THEMED, on purpose. A satellite image does not get lighter because
|
|
18
|
+
* the user chose light mode. Theming this pair would make it track the
|
|
19
|
+
* app's surface, which is precisely the background it is NOT over.
|
|
20
|
+
* Usage:
|
|
21
|
+
* <figcaption class="juno-canvas-ink">Sector 7 · 1.2 km</figcaption>
|
|
22
|
+
* <svg class="juno-canvas-ink"><path class="juno-canvas-ink__stroke" …/></svg>
|
|
23
|
+
* <div class="juno-canvas-scrim">…chrome floating over the content…</div>
|
|
24
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
25
|
+
|
|
26
|
+
/* Text. Four offset shadows rather than one blur: a blurred shadow fades at
|
|
27
|
+
the glyph's corners, which is exactly where a thin stroke needs the most
|
|
28
|
+
help, and it costs the same. `paint-order` is for SVG text, where a real
|
|
29
|
+
stroke is available and better. */
|
|
30
|
+
.juno-canvas-ink {
|
|
31
|
+
color: var(--juno-ink-canvas-ink);
|
|
32
|
+
paint-order: stroke fill;
|
|
33
|
+
stroke: var(--juno-ink-canvas-halo);
|
|
34
|
+
stroke-width: var(--juno-ink-canvas-halo-width);
|
|
35
|
+
text-shadow:
|
|
36
|
+
var(--juno-ink-canvas-halo-width) 0 0 var(--juno-ink-canvas-halo),
|
|
37
|
+
calc(-1 * var(--juno-ink-canvas-halo-width)) 0 0 var(--juno-ink-canvas-halo),
|
|
38
|
+
0 var(--juno-ink-canvas-halo-width) 0 var(--juno-ink-canvas-halo),
|
|
39
|
+
0 calc(-1 * var(--juno-ink-canvas-halo-width)) 0 var(--juno-ink-canvas-halo);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Display sizes: a 2px halo reads as a hairline against a heavier stroke. */
|
|
43
|
+
.juno-canvas-ink--lg {
|
|
44
|
+
stroke-width: var(--juno-ink-canvas-halo-width-lg);
|
|
45
|
+
text-shadow:
|
|
46
|
+
var(--juno-ink-canvas-halo-width-lg) 0 0 var(--juno-ink-canvas-halo),
|
|
47
|
+
calc(-1 * var(--juno-ink-canvas-halo-width-lg)) 0 0 var(--juno-ink-canvas-halo),
|
|
48
|
+
0 var(--juno-ink-canvas-halo-width-lg) 0 var(--juno-ink-canvas-halo),
|
|
49
|
+
0 calc(-1 * var(--juno-ink-canvas-halo-width-lg)) 0 var(--juno-ink-canvas-halo);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Vector ink — measurement lines, selection outlines. Two passes: the halo
|
|
53
|
+
is the same path drawn wider underneath. In SVG give the halo element
|
|
54
|
+
this class and the mark element __stroke; in canvas, stroke twice. */
|
|
55
|
+
.juno-canvas-ink__halo {
|
|
56
|
+
fill: none;
|
|
57
|
+
stroke: var(--juno-ink-canvas-halo);
|
|
58
|
+
stroke-width: calc(var(--juno-ink-canvas-halo-width) * 2);
|
|
59
|
+
stroke-linecap: round;
|
|
60
|
+
stroke-linejoin: round;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.juno-canvas-ink__stroke {
|
|
64
|
+
fill: none;
|
|
65
|
+
stroke: var(--juno-ink-canvas-ink);
|
|
66
|
+
stroke-linecap: round;
|
|
67
|
+
stroke-linejoin: round;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Status over canvas. The themed role hues are chosen against s0–s3 and wash
|
|
71
|
+
out over a saturated backdrop, so these are the same hues at raised chroma.
|
|
72
|
+
Louder than the panel roles by design — they will look wrong on a panel. */
|
|
73
|
+
.juno-canvas-ink--nominal { color: var(--juno-ink-vivid-nominal); }
|
|
74
|
+
.juno-canvas-ink--active { color: var(--juno-ink-vivid-active); }
|
|
75
|
+
.juno-canvas-ink--target { color: var(--juno-ink-vivid-target); }
|
|
76
|
+
.juno-canvas-ink--caution { color: var(--juno-ink-vivid-caution); }
|
|
77
|
+
.juno-canvas-ink--warning { color: var(--juno-ink-vivid-warning); }
|
|
78
|
+
|
|
79
|
+
/* Chrome floating over live content. Deliberately NOT --juno-opacity-scrim
|
|
80
|
+
(0.62): that value suppresses a modal's background, and here the background
|
|
81
|
+
is the thing the chrome is annotating — greying it out defeats the purpose. */
|
|
82
|
+
.juno-canvas-scrim {
|
|
83
|
+
background: color-mix(in srgb, var(--juno-s0) calc(var(--juno-ink-canvas-scrim) * 100%), transparent);
|
|
84
|
+
-webkit-backdrop-filter: blur(8px);
|
|
85
|
+
backdrop-filter: blur(8px);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* A translucent bar over moving content is the case where reduced
|
|
89
|
+
transparency matters most — the motion behind the text is the problem, not
|
|
90
|
+
the blur. Fall back to the opaque surface. */
|
|
91
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
92
|
+
.juno-canvas-scrim {
|
|
93
|
+
background: var(--juno-s0);
|
|
94
|
+
-webkit-backdrop-filter: none;
|
|
95
|
+
backdrop-filter: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -29,6 +29,55 @@
|
|
|
29
29
|
media query. */
|
|
30
30
|
--juno-dock-scale: 1;
|
|
31
31
|
|
|
32
|
+
/* ── Horizontal budget contract ──────────────────────────────────────
|
|
33
|
+
Published so a consumer's capacity planner ("how many items fit, and
|
|
34
|
+
do they still hold a tap target?") reads junoui's real geometry
|
|
35
|
+
instead of re-deriving it from the numbers in this file. Two
|
|
36
|
+
consumers had derived it by hand, twice, in prose — and both had
|
|
37
|
+
drifted: they subtracted 12px of inline padding where the pill
|
|
38
|
+
actually spends 8, so their per-item width came out ~0.8px low at
|
|
39
|
+
every viewport (20260826-027).
|
|
40
|
+
|
|
41
|
+
--juno-dock-items the item budget. junoui does not enforce it —
|
|
42
|
+
__item is `flex: 1 1 0`, so the layout divides
|
|
43
|
+
by however many items are present — it is the
|
|
44
|
+
number the DERIVATIONS below are about, and the
|
|
45
|
+
consumer sets it to what it renders.
|
|
46
|
+
--juno-dock-item-inline the width one item gets. A prediction of what
|
|
47
|
+
the flex layout produces, not an input to it;
|
|
48
|
+
asserted against the measured box in the test
|
|
49
|
+
suite so it cannot drift from the layout.
|
|
50
|
+
--juno-dock-fit-inline the narrowest viewport at which every item
|
|
51
|
+
still holds --juno-size-tap-comfortable. Use it
|
|
52
|
+
in a media query and DROP an item; that is the
|
|
53
|
+
answer junoui can give exactly.
|
|
54
|
+
|
|
55
|
+
A SCALE floor (44px / the item's width) is deliberately not published:
|
|
56
|
+
it is a ratio of two lengths, and CSS cannot divide by a length. A
|
|
57
|
+
consumer that must scale rather than drop compares
|
|
58
|
+
--juno-dock-item-inline against --juno-size-tap-comfortable itself.
|
|
59
|
+
|
|
60
|
+
The three inline terms are declared ONCE here and consumed by both the
|
|
61
|
+
variants' own margin/padding/border and the sums below, so the budget
|
|
62
|
+
cannot disagree with the bar it describes — the same reason
|
|
63
|
+
--juno-dock-edge-offset exists (20260815-055). The base bar is
|
|
64
|
+
full-bleed with a top-edge border only, so all three are zero; --pill
|
|
65
|
+
and --float set them. */
|
|
66
|
+
--juno-dock-items: 5;
|
|
67
|
+
--juno-dock-margin-inline: 0px;
|
|
68
|
+
--juno-dock-pad-inline: 0px;
|
|
69
|
+
--juno-dock-border-inline: 0px;
|
|
70
|
+
--juno-dock-chrome-inline: calc(
|
|
71
|
+
2 * (var(--juno-dock-margin-inline) + var(--juno-dock-pad-inline) + var(--juno-dock-border-inline))
|
|
72
|
+
);
|
|
73
|
+
--juno-dock-avail: 100vw;
|
|
74
|
+
--juno-dock-item-inline: calc(
|
|
75
|
+
(var(--juno-dock-avail) - var(--juno-dock-chrome-inline)) / var(--juno-dock-items)
|
|
76
|
+
);
|
|
77
|
+
--juno-dock-fit-inline: calc(
|
|
78
|
+
var(--juno-dock-items) * var(--juno-size-tap-comfortable) + var(--juno-dock-chrome-inline)
|
|
79
|
+
);
|
|
80
|
+
|
|
32
81
|
position: sticky;
|
|
33
82
|
inset-block-end: 0;
|
|
34
83
|
z-index: var(--juno-z-raised);
|
|
@@ -123,16 +172,21 @@
|
|
|
123
172
|
.juno-dock--pill {
|
|
124
173
|
--juno-role: var(--juno-active);
|
|
125
174
|
|
|
175
|
+
/* the three inline terms the budget contract sums — see .juno-dock */
|
|
176
|
+
--juno-dock-margin-inline: var(--juno-space-12);
|
|
177
|
+
--juno-dock-pad-inline: var(--juno-space-4);
|
|
178
|
+
--juno-dock-border-inline: var(--juno-border-width-1);
|
|
179
|
+
|
|
126
180
|
position: fixed;
|
|
127
181
|
inset-inline: 0;
|
|
128
182
|
inset-block-end: 0;
|
|
129
183
|
z-index: var(--juno-z-raised);
|
|
130
|
-
margin: 0 var(--juno-
|
|
131
|
-
padding: var(--juno-
|
|
184
|
+
margin: 0 var(--juno-dock-margin-inline) var(--juno-dock-edge-offset);
|
|
185
|
+
padding: var(--juno-dock-pad-inline);
|
|
132
186
|
background: color-mix(in srgb, var(--juno-s1) 88%, transparent);
|
|
133
187
|
-webkit-backdrop-filter: blur(12px);
|
|
134
188
|
backdrop-filter: blur(12px);
|
|
135
|
-
border: var(--juno-border-
|
|
189
|
+
border: var(--juno-dock-border-inline) solid var(--juno-border);
|
|
136
190
|
border-radius: 999px;
|
|
137
191
|
box-shadow: var(--juno-shadow-2);
|
|
138
192
|
}
|
|
@@ -226,17 +280,22 @@
|
|
|
226
280
|
Out of flow (fixed) so the page scrolls UNDER it — reserve room on the
|
|
227
281
|
scroller with padding-block-end: var(--juno-dock-clearance). */
|
|
228
282
|
.juno-dock--float {
|
|
283
|
+
/* the three inline terms the budget contract sums — see .juno-dock */
|
|
284
|
+
--juno-dock-margin-inline: var(--juno-space-12);
|
|
285
|
+
--juno-dock-pad-inline: var(--juno-space-4);
|
|
286
|
+
--juno-dock-border-inline: var(--juno-border-width-1);
|
|
287
|
+
|
|
229
288
|
position: fixed;
|
|
230
289
|
inset-inline: 0;
|
|
231
290
|
inset-block-end: 0;
|
|
232
291
|
z-index: var(--juno-z-raised);
|
|
233
|
-
margin: 0 var(--juno-
|
|
234
|
-
padding: var(--juno-
|
|
292
|
+
margin: 0 var(--juno-dock-margin-inline) var(--juno-dock-edge-offset);
|
|
293
|
+
padding: var(--juno-dock-pad-inline);
|
|
235
294
|
overflow: hidden;
|
|
236
295
|
background: color-mix(in srgb, var(--juno-s1) 88%, transparent);
|
|
237
296
|
-webkit-backdrop-filter: blur(12px);
|
|
238
297
|
backdrop-filter: blur(12px);
|
|
239
|
-
border: var(--juno-border-
|
|
298
|
+
border: var(--juno-dock-border-inline) solid var(--juno-border);
|
|
240
299
|
border-radius: 999px;
|
|
241
300
|
box-shadow: var(--juno-shadow-2);
|
|
242
301
|
}
|
|
@@ -12,17 +12,44 @@
|
|
|
12
12
|
* gap, the folded slot still occupies one gap — name it in
|
|
13
13
|
* --juno-fold-gap and the fold swallows it with a negative margin.
|
|
14
14
|
*
|
|
15
|
+
* COMPOSITION. The canonical use puts .juno-fold on an element that is
|
|
16
|
+
* already a component — a .juno-pillbar__item, a .juno-btn, a .juno-chip
|
|
17
|
+
* — because that is where the capsule chrome (tap floor, padding, hover,
|
|
18
|
+
* focus ring, aria-pressed) lives. Two things follow, and both are
|
|
19
|
+
* load-bearing rather than defensive:
|
|
20
|
+
*
|
|
21
|
+
* 1. The fold's own declarations are stated at ATTRIBUTE specificity
|
|
22
|
+
* (0,2,0), on both branches of data-juno-in, i.e. always. A component
|
|
23
|
+
* class is (0,1,0), and `transition` is a SHORTHAND: a later rule does
|
|
24
|
+
* not add to the list, it replaces it. Measured in Chromium against the
|
|
25
|
+
* built bundle, before this was raised: on a
|
|
26
|
+
* .juno-fold.juno-pillbar__item, transition-property resolved to
|
|
27
|
+
* `color, background-color` — pillbar.css sorts after this file, so the
|
|
28
|
+
* fold had no transition at all and jumped shut instead of folding.
|
|
29
|
+
* inline-size and overflow would lose the same way to a component that
|
|
30
|
+
* sets either.
|
|
31
|
+
*
|
|
32
|
+
* 2. Owning `transition` means owning ALL of it, so the capsule chrome
|
|
33
|
+
* properties are in the fold's list too, at the quick duration the
|
|
34
|
+
* components themselves use. Dropping them would trade a broken fold
|
|
35
|
+
* for a broken hover.
|
|
36
|
+
*
|
|
37
|
+
* Which floors the fold RELEASES when folded — see the folded rule below:
|
|
38
|
+
* min-inline-size, padding-inline, border-inline-width. Those are exactly
|
|
39
|
+
* the three inputs that can hold a border-box inline size above zero.
|
|
40
|
+
*
|
|
15
41
|
* Canonical use: a transient action in a toolbar/pill row (a
|
|
16
42
|
* scroll-to-top arrow, a contextual button) whose arrival should slide
|
|
17
43
|
* the row open rather than jump it.
|
|
18
44
|
* Usage:
|
|
19
45
|
* <div style="display:flex; gap:4px"> (any row)
|
|
20
|
-
* <button class="juno-fold" data-juno-in aria-label="Scroll to top">…</button>
|
|
46
|
+
* <button class="juno-fold juno-pillbar__item" data-juno-in aria-label="Scroll to top">…</button>
|
|
21
47
|
* …other members…
|
|
22
48
|
* </div>
|
|
23
49
|
* prefers-reduced-motion needs nothing component-local — the base
|
|
24
50
|
* layer zeroes every transition duration. */
|
|
25
|
-
.juno-fold
|
|
51
|
+
.juno-fold[data-juno-in],
|
|
52
|
+
.juno-fold:not([data-juno-in]) {
|
|
26
53
|
--juno-fold-size: var(--juno-size-tap-comfortable);
|
|
27
54
|
--juno-fold-gap: 0px;
|
|
28
55
|
|
|
@@ -31,13 +58,32 @@
|
|
|
31
58
|
overflow: hidden;
|
|
32
59
|
transition:
|
|
33
60
|
inline-size var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
61
|
+
min-inline-size var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
62
|
+
padding var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
63
|
+
border-width var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
34
64
|
margin var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
35
65
|
opacity var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
36
|
-
visibility var(--juno-motion-duration-base) var(--juno-motion-ease-standard)
|
|
66
|
+
visibility var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
67
|
+
color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
68
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
37
69
|
}
|
|
38
70
|
|
|
71
|
+
/* Folded away. A min-* floor clamps the USED value whichever rule won the
|
|
72
|
+
cascade, and a border-box inline size can never resolve below its own
|
|
73
|
+
padding plus border — so a composed capsule holds the slot open however
|
|
74
|
+
hard the fold pushes. Measured against the built bundle, composed with
|
|
75
|
+
.juno-pillbar__item at a 390px viewport: 44px with both floors in place
|
|
76
|
+
(the tap target), 20px with min-inline-size released (the padding alone),
|
|
77
|
+
0px with all of them released.
|
|
78
|
+
min-inline-size is in the transition list above for the OPENING half:
|
|
79
|
+
released to 0 it would otherwise snap back to the capsule's tap floor the
|
|
80
|
+
instant data-juno-in returns, and the used width — max(inline-size,
|
|
81
|
+
min-inline-size) — would jump to full width with nothing to animate. */
|
|
39
82
|
.juno-fold:not([data-juno-in]) {
|
|
40
83
|
inline-size: 0;
|
|
84
|
+
min-inline-size: 0;
|
|
85
|
+
padding-inline: 0;
|
|
86
|
+
border-inline-width: 0;
|
|
41
87
|
margin-inline-end: calc(-1 * var(--juno-fold-gap));
|
|
42
88
|
opacity: 0;
|
|
43
89
|
visibility: hidden;
|