@junoput01/junoui 0.2.0 → 0.4.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 +251 -0
- package/README.md +23 -19
- package/dist/css/juno-custom-media.css +32 -0
- package/dist/css/juno.css +1355 -40
- package/dist/icons/inline.js +22 -0
- package/docs/accessibility.md +188 -0
- package/docs/boot-shell.md +295 -0
- package/docs/components/README.md +71 -0
- package/docs/components/accordion.md +49 -0
- package/docs/components/alert.md +46 -0
- package/docs/components/avatar.md +45 -0
- package/docs/components/badge.md +35 -0
- package/docs/components/breadcrumb.md +31 -0
- package/docs/components/button.md +32 -0
- package/docs/components/card.md +41 -0
- package/docs/components/checkbox.md +41 -0
- package/docs/components/chip.md +44 -0
- package/docs/components/divider.md +32 -0
- package/docs/components/dock.md +215 -0
- package/docs/components/drawer.md +100 -0
- package/docs/components/field.md +44 -0
- package/docs/components/fold-slot.md +31 -0
- package/docs/components/gauge.md +48 -0
- package/docs/components/icon-loader.md +122 -0
- package/docs/components/icon.md +70 -0
- package/docs/components/input.md +45 -0
- package/docs/components/list.md +76 -0
- package/docs/components/load-state.md +131 -0
- package/docs/components/loader.md +88 -0
- package/docs/components/menu.md +54 -0
- package/docs/components/modal.md +52 -0
- package/docs/components/navbar.md +53 -0
- package/docs/components/pagination.md +42 -0
- package/docs/components/pillbar.md +211 -0
- package/docs/components/popover.md +43 -0
- package/docs/components/rail.md +54 -0
- package/docs/components/readout.md +39 -0
- package/docs/components/reload.md +41 -0
- package/docs/components/segmented.md +46 -0
- package/docs/components/select.md +33 -0
- package/docs/components/skeleton.md +45 -0
- package/docs/components/slider.md +48 -0
- package/docs/components/spark.md +51 -0
- package/docs/components/status.md +30 -0
- package/docs/components/stepper.md +46 -0
- package/docs/components/switch.md +38 -0
- package/docs/components/table.md +126 -0
- package/docs/components/tabs.md +61 -0
- package/docs/components/thumb.md +81 -0
- package/docs/components/toast.md +48 -0
- package/docs/components/toggle-button.md +40 -0
- package/docs/components/tooltip.md +63 -0
- package/docs/design-guidelines.md +173 -0
- package/docs/flutter.md +42 -0
- package/docs/getting-started.md +93 -0
- package/docs/integration.md +95 -0
- package/docs/ios-conformance.md +224 -0
- package/docs/layout.md +271 -0
- package/docs/native.md +52 -0
- package/docs/roadmap.md +106 -0
- package/docs/tokens-reference.md +223 -0
- package/docs/web.md +95 -0
- package/package.json +7 -3
- package/src/css/base.css +171 -1
- package/src/css/components/dock.css +335 -0
- package/src/css/components/drawer.css +40 -3
- package/src/css/components/fold-slot.css +44 -0
- package/src/css/components/icon-loader.css +67 -0
- package/src/css/components/icon.css +6 -4
- package/src/css/components/load-state.css +136 -0
- package/src/css/components/loader.css +6 -0
- package/src/css/components/menu.css +4 -0
- package/src/css/components/modal.css +24 -3
- package/src/css/components/navbar.css +5 -1
- package/src/css/components/pillbar.css +222 -7
- package/src/css/components/rail.css +8 -0
- package/src/css/components/reload.css +48 -0
- package/src/css/components/skeleton.css +41 -13
- package/src/css/components/tabs.css +5 -0
- package/src/css/components/thumb.css +63 -1
- package/src/css/components/toast.css +5 -1
- package/src/css/density.css +22 -0
- package/src/css/layout.css +95 -5
- package/src/css/utilities.css +4 -1
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
.juno-dock {
|
|
22
22
|
--juno-role: var(--juno-active);
|
|
23
23
|
|
|
24
|
+
/* Consumer-driven shrink (e.g. dim/compact the bar on scroll) — default 1,
|
|
25
|
+
a no-op. Applies to the whole bar regardless of variant (plain/--fixed/
|
|
26
|
+
--pill/--float/--icon). Transition duration is authored through the
|
|
27
|
+
motion scale (see base.css's JS-readable --juno-motion contract) so
|
|
28
|
+
prefers-reduced-motion collapses it to 0 without a component-local
|
|
29
|
+
media query. */
|
|
30
|
+
--juno-dock-scale: 1;
|
|
31
|
+
|
|
24
32
|
position: sticky;
|
|
25
33
|
inset-block-end: 0;
|
|
26
34
|
z-index: var(--juno-z-raised);
|
|
@@ -29,6 +37,9 @@
|
|
|
29
37
|
background: var(--juno-s1);
|
|
30
38
|
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
31
39
|
padding-block-end: env(safe-area-inset-bottom, 0);
|
|
40
|
+
transform: scale(var(--juno-dock-scale));
|
|
41
|
+
transform-origin: bottom center;
|
|
42
|
+
transition: transform calc(var(--juno-motion-duration-quick) * var(--juno-motion-scale)) var(--juno-motion-ease-standard);
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
.juno-dock__item {
|
|
@@ -41,6 +52,13 @@
|
|
|
41
52
|
justify-content: center;
|
|
42
53
|
gap: var(--juno-space-2);
|
|
43
54
|
padding-block: var(--juno-space-4);
|
|
55
|
+
|
|
56
|
+
/* Full reset, not just the top edge: a plain `border-block-start` override
|
|
57
|
+
leaves the UA default border on the other three sides of a <button>
|
|
58
|
+
item (links have none, so this went unnoticed until <button> showed up
|
|
59
|
+
in the pill variant's usage). Re-add only the edge that carries the
|
|
60
|
+
active accent. */
|
|
61
|
+
border: none;
|
|
44
62
|
border-block-start: var(--juno-border-width-2) solid transparent;
|
|
45
63
|
font-family: var(--juno-font-family-sans);
|
|
46
64
|
font-size: var(--juno-font-size-10);
|
|
@@ -72,3 +90,320 @@
|
|
|
72
90
|
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
73
91
|
outline-offset: calc(-1 * var(--juno-space-2));
|
|
74
92
|
}
|
|
93
|
+
|
|
94
|
+
/* Fixed — same story as the pillbar: sticky only pins while the column
|
|
95
|
+
overflows, so on a short page the dock lands mid-content. Pin to the
|
|
96
|
+
viewport foot in a page-scroll shell where content height varies. Since
|
|
97
|
+
fixed leaves the flow, reserve its height at the page foot (e.g.
|
|
98
|
+
padding-block-end) so it doesn't cover the last row. In-flow
|
|
99
|
+
.juno-app-shell (main is the scroller, dock at the body foot) needs none
|
|
100
|
+
of this — prefer it. */
|
|
101
|
+
.juno-dock--fixed {
|
|
102
|
+
position: fixed;
|
|
103
|
+
inset-inline: 0;
|
|
104
|
+
inset-block-end: 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ── Pill variant — the full-width bar becomes a rounded floating pill with
|
|
108
|
+
big glyphs in circular bubbles. Labels drop; each icon sits in a circular
|
|
109
|
+
.juno-dock__bubble that fills only on the active tab (not the whole item).
|
|
110
|
+
Out of flow (fixed) so the page scrolls UNDER it — reserve room on the
|
|
111
|
+
scroller with padding-block-end: var(--juno-dock-clearance).
|
|
112
|
+
Usage — pair the bubble with .juno-icon-loader, junoui's concentric-stack
|
|
113
|
+
primitive, so a loading arc can be dropped in without re-rolling geometry:
|
|
114
|
+
<nav class="juno-dock juno-dock--pill juno-hide-from-md" aria-label="Primary">
|
|
115
|
+
<a class="juno-dock__item" href="…" aria-current="page" aria-label="Library">
|
|
116
|
+
<span class="juno-dock__bubble juno-icon-loader">
|
|
117
|
+
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
118
|
+
</span>
|
|
119
|
+
</a>
|
|
120
|
+
</nav>
|
|
121
|
+
Labels are hidden, so every item MUST carry an aria-label (on the <a>/<button>)
|
|
122
|
+
— the glyph alone is not an accessible name. */
|
|
123
|
+
.juno-dock--pill {
|
|
124
|
+
--juno-role: var(--juno-active);
|
|
125
|
+
|
|
126
|
+
position: fixed;
|
|
127
|
+
inset-inline: 0;
|
|
128
|
+
inset-block-end: 0;
|
|
129
|
+
z-index: var(--juno-z-raised);
|
|
130
|
+
margin: 0 var(--juno-space-12) calc(var(--juno-space-16) + env(safe-area-inset-bottom, 0px));
|
|
131
|
+
padding: var(--juno-space-4);
|
|
132
|
+
background: color-mix(in srgb, var(--juno-s1) 88%, transparent);
|
|
133
|
+
-webkit-backdrop-filter: blur(12px);
|
|
134
|
+
backdrop-filter: blur(12px);
|
|
135
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
136
|
+
border-radius: 999px;
|
|
137
|
+
box-shadow: var(--juno-shadow-2);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.juno-dock--pill .juno-dock__item {
|
|
141
|
+
min-block-size: 0;
|
|
142
|
+
gap: 0;
|
|
143
|
+
padding-block: var(--juno-space-4);
|
|
144
|
+
|
|
145
|
+
/* full reset: the base only zeroes the block-start border, but the pill has
|
|
146
|
+
no top edge to accent — the active state moves to the bubble. Also kill the
|
|
147
|
+
UA tap-highlight so no square flashes past the round bubble on tap. */
|
|
148
|
+
border: none;
|
|
149
|
+
background: none;
|
|
150
|
+
-webkit-tap-highlight-color: transparent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.juno-dock--pill .juno-dock__label { display: none; }
|
|
154
|
+
|
|
155
|
+
/* Focus lands on the bubble so the ring hugs the round target, not the wide
|
|
156
|
+
item box. */
|
|
157
|
+
.juno-dock--pill .juno-dock__item:focus-visible { outline: none; }
|
|
158
|
+
|
|
159
|
+
.juno-dock--pill .juno-dock__item:focus-visible .juno-dock__bubble {
|
|
160
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
161
|
+
outline-offset: var(--juno-space-2);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* The bubble is a definite-size circular surface that centres its glyph. It
|
|
165
|
+
declares the same single-cell grid as .juno-icon-loader so composing the
|
|
166
|
+
two (see the usage block above) can't fight over `display`, and so the
|
|
167
|
+
ring — when one is present — is concentric with the glyph. */
|
|
168
|
+
.juno-dock__bubble {
|
|
169
|
+
/* Section-loading ring — with .juno-icon-loader on the same element, an
|
|
170
|
+
indeterminate .juno-arc dropped inside rings the bubble's EDGE (diameter
|
|
171
|
+
= the bubble, thin stroke) instead of the glyph. Because the bubble's box
|
|
172
|
+
is definite, adding or removing the arc never resizes it. */
|
|
173
|
+
--juno-icon-loader-ring: var(--juno-size-tap-comfortable);
|
|
174
|
+
--juno-icon-loader-ring-width: 2px;
|
|
175
|
+
|
|
176
|
+
display: grid;
|
|
177
|
+
place-items: center;
|
|
178
|
+
inline-size: var(--juno-size-tap-comfortable);
|
|
179
|
+
block-size: var(--juno-size-tap-comfortable);
|
|
180
|
+
border-radius: 50%;
|
|
181
|
+
transition: background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Explicit big glyph so the pill reads at a glance. Set on the glyph itself so
|
|
185
|
+
it wins regardless of context (the base .juno-icon no longer pins a size —
|
|
186
|
+
see 20260727-011). */
|
|
187
|
+
.juno-dock--pill .juno-icon { --juno-icon-size: var(--juno-space-32); }
|
|
188
|
+
|
|
189
|
+
/* Active reads on the bubble alone — suppress the base full-item fill so only
|
|
190
|
+
the circular highlight shows. */
|
|
191
|
+
.juno-dock--pill .juno-dock__item[aria-current] {
|
|
192
|
+
background: none;
|
|
193
|
+
color: var(--juno-role);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.juno-dock--pill .juno-dock__item[aria-current] .juno-dock__bubble {
|
|
197
|
+
background: var(--juno-s3);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* reduced-transparency → solid surface, no blur (matches the pillbar) */
|
|
201
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
202
|
+
.juno-dock--pill {
|
|
203
|
+
background: var(--juno-s1);
|
|
204
|
+
-webkit-backdrop-filter: none;
|
|
205
|
+
backdrop-filter: none;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ── Float variant — the pillbar's floating capsule chrome (fixed, rounded
|
|
210
|
+
shell, blur, shadow, off-edge margin) applied to the dock's OWN item model:
|
|
211
|
+
unlike --pill it keeps icon+label items and the full-item active fill —
|
|
212
|
+
only the exterior changes. Combine with --icon below for the bubble
|
|
213
|
+
treatment inside a capsule (the old --pill look, decomposed). `border`
|
|
214
|
+
fully replaces the base's `border-block-start` (no top seam on a capsule),
|
|
215
|
+
and `padding` fully replaces the base's safe-area padding (the margin
|
|
216
|
+
below carries the safe area instead — additive offset off the edge, not
|
|
217
|
+
`max()`: see docs/ios-conformance.md). `overflow: hidden` clips the
|
|
218
|
+
items' square corners to the capsule's rounded shape.
|
|
219
|
+
Usage:
|
|
220
|
+
<nav class="juno-dock juno-dock--float juno-hide-from-md" aria-label="Primary">
|
|
221
|
+
<a class="juno-dock__item" href="…" aria-current="page">
|
|
222
|
+
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
223
|
+
<span class="juno-dock__label">Library</span>
|
|
224
|
+
</a>
|
|
225
|
+
</nav>
|
|
226
|
+
Out of flow (fixed) so the page scrolls UNDER it — reserve room on the
|
|
227
|
+
scroller with padding-block-end: var(--juno-dock-clearance). */
|
|
228
|
+
.juno-dock--float {
|
|
229
|
+
position: fixed;
|
|
230
|
+
inset-inline: 0;
|
|
231
|
+
inset-block-end: 0;
|
|
232
|
+
z-index: var(--juno-z-raised);
|
|
233
|
+
margin: 0 var(--juno-space-12) calc(var(--juno-space-16) + env(safe-area-inset-bottom, 0px));
|
|
234
|
+
padding: var(--juno-space-4);
|
|
235
|
+
overflow: hidden;
|
|
236
|
+
background: color-mix(in srgb, var(--juno-s1) 88%, transparent);
|
|
237
|
+
-webkit-backdrop-filter: blur(12px);
|
|
238
|
+
backdrop-filter: blur(12px);
|
|
239
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
240
|
+
border-radius: 999px;
|
|
241
|
+
box-shadow: var(--juno-shadow-2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* reduced-transparency → solid surface, no blur (matches the pillbar / --pill) */
|
|
245
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
246
|
+
.juno-dock--float {
|
|
247
|
+
background: var(--juno-s1);
|
|
248
|
+
-webkit-backdrop-filter: none;
|
|
249
|
+
backdrop-filter: none;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ── Icon variant — labels drop, glyphs grow, and the active state moves to
|
|
254
|
+
a circular .juno-dock__bubble instead of filling the whole item, WITHOUT
|
|
255
|
+
forcing the floating capsule chrome (that's --float, above — combine both
|
|
256
|
+
for the old --pill look). Usable standalone on a plain sticky/--fixed bar
|
|
257
|
+
for a compact, icon-only full-width dock.
|
|
258
|
+
Usage — same .juno-dock__bubble contract as --pill (pair with
|
|
259
|
+
.juno-icon-loader; see docs/components/dock.md#pill-variant):
|
|
260
|
+
<nav class="juno-dock juno-dock--icon juno-hide-from-md" aria-label="Primary">
|
|
261
|
+
<a class="juno-dock__item" href="…" aria-current="page" aria-label="Library">
|
|
262
|
+
<span class="juno-dock__bubble juno-icon-loader">
|
|
263
|
+
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
264
|
+
</span>
|
|
265
|
+
</a>
|
|
266
|
+
</nav>
|
|
267
|
+
Labels are hidden, so every item MUST carry an aria-label — the glyph
|
|
268
|
+
alone is not an accessible name. */
|
|
269
|
+
.juno-dock--icon .juno-dock__item {
|
|
270
|
+
gap: 0;
|
|
271
|
+
|
|
272
|
+
/* accent moves to the bubble; kill the item-edge border in both states */
|
|
273
|
+
border-color: transparent;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.juno-dock--icon .juno-dock__label { display: none; }
|
|
277
|
+
|
|
278
|
+
/* Explicit big glyph so the icon-only bar reads at a glance — set on the
|
|
279
|
+
glyph itself so it wins regardless of context (the base .juno-icon no
|
|
280
|
+
longer pins a size — see 20260727-011). */
|
|
281
|
+
.juno-dock--icon .juno-icon { --juno-icon-size: var(--juno-space-32); }
|
|
282
|
+
|
|
283
|
+
/* Focus lands on the bubble so the ring hugs the round target, not the wide
|
|
284
|
+
item box. */
|
|
285
|
+
.juno-dock--icon .juno-dock__item:focus-visible { outline: none; }
|
|
286
|
+
|
|
287
|
+
.juno-dock--icon .juno-dock__item:focus-visible .juno-dock__bubble {
|
|
288
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
289
|
+
outline-offset: var(--juno-space-2);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Active reads on the bubble alone — suppress the base full-item fill so
|
|
293
|
+
only the circular highlight shows. */
|
|
294
|
+
.juno-dock--icon .juno-dock__item[aria-current] {
|
|
295
|
+
background: none;
|
|
296
|
+
color: var(--juno-role);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.juno-dock--icon .juno-dock__item[aria-current] .juno-dock__bubble {
|
|
300
|
+
background: var(--juno-s3);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* ── Collapsible variant — the whole bar folds into a single circular
|
|
304
|
+
.juno-dock__knob at the inline-end edge, and unfolds back to full width.
|
|
305
|
+
Driven by ONE inherited custom prop the app writes (junoui ships no JS):
|
|
306
|
+
--juno-dock-fold, 0 = fully open … 1 = the circle. Write it per scroll
|
|
307
|
+
frame for a gesture-tracked fold (set it on the dock, an ancestor, or
|
|
308
|
+
:root — it is only CONSUMED here, never declared, so an inherited value
|
|
309
|
+
always lands), or flip it 0/1 for a plain toggle.
|
|
310
|
+
|
|
311
|
+
The fold has two phases, split at --juno-dock-fold-split: first the bar
|
|
312
|
+
SHRINKS in place to --juno-dock-fold-scale (transform, no relayout),
|
|
313
|
+
then it SLIDES shut to --juno-dock-collapsed-size. Width interpolates
|
|
314
|
+
between two definite lengths because a transition cannot run to or from
|
|
315
|
+
an intrinsic size; --juno-dock-fold-smoothing is smoothing over
|
|
316
|
+
scroll-event discretization, not the fold itself — anything longer lags
|
|
317
|
+
the finger driving it.
|
|
318
|
+
|
|
319
|
+
Compose with a fixed placement (--pill, --float, or --fixed): the fold
|
|
320
|
+
re-anchors the bar to the inline-end edge, which only means something
|
|
321
|
+
out of flow. Transform-origin is the physical bottom right — the fold's
|
|
322
|
+
own scale REPLACES the base --juno-dock-scale hook (one transform slot);
|
|
323
|
+
RTL consumers flip the origin and inset overrides together.
|
|
324
|
+
|
|
325
|
+
Structure — items live in a tray; the knob is the circle's face:
|
|
326
|
+
<nav class="juno-dock juno-dock--pill juno-dock--collapsible …"
|
|
327
|
+
data-juno-collapsed>
|
|
328
|
+
<div class="juno-dock__tray"> …the usual __item children… </div>
|
|
329
|
+
<button class="juno-dock__knob" aria-label="Show navigation">
|
|
330
|
+
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
|
|
331
|
+
</button>
|
|
332
|
+
</nav>
|
|
333
|
+
|
|
334
|
+
data-juno-collapsed is the END state, set by the app only at fold = 1
|
|
335
|
+
(and removed the moment the fold reopens): it flips the tray to
|
|
336
|
+
visibility:hidden — which removes its items from the tab order — and
|
|
337
|
+
reveals the knob. Mid-fold both stay live; the tray's opacity empties
|
|
338
|
+
ahead of the slide (×1.6) so the glyphs are gone before the circle
|
|
339
|
+
closes. The knob MUST carry an aria-label; collapsing while focus is
|
|
340
|
+
inside the tray is the app's edge — move focus to the knob first.
|
|
341
|
+
prefers-reduced-motion needs nothing component-local: the base layer
|
|
342
|
+
zeroes every transition, and the fold value itself is app-driven. */
|
|
343
|
+
.juno-dock--collapsible {
|
|
344
|
+
--juno-dock-fold-split: 0.35;
|
|
345
|
+
--juno-dock-fold-scale: 0.78;
|
|
346
|
+
--juno-dock-collapsed-size: calc(
|
|
347
|
+
var(--juno-size-tap-comfortable) + 2 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
348
|
+
);
|
|
349
|
+
--juno-dock-edge-gap: calc(2 * var(--juno-space-12));
|
|
350
|
+
--juno-dock-fold-smoothing: 90ms;
|
|
351
|
+
|
|
352
|
+
/* per-phase normalized progress, derived — never set these directly */
|
|
353
|
+
--juno-dock-fold-shrink: min(1, var(--juno-dock-fold, 0) / var(--juno-dock-fold-split));
|
|
354
|
+
--juno-dock-fold-slide: clamp(
|
|
355
|
+
0,
|
|
356
|
+
(var(--juno-dock-fold, 0) - var(--juno-dock-fold-split)) / (1 - var(--juno-dock-fold-split)),
|
|
357
|
+
1
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
inset-inline-start: auto;
|
|
361
|
+
inline-size: calc(
|
|
362
|
+
var(--juno-dock-collapsed-size) +
|
|
363
|
+
(100% - var(--juno-dock-edge-gap) - var(--juno-dock-collapsed-size)) *
|
|
364
|
+
(1 - var(--juno-dock-fold-slide))
|
|
365
|
+
);
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
transform: scale(calc(1 - (1 - var(--juno-dock-fold-scale)) * var(--juno-dock-fold-shrink)));
|
|
368
|
+
transform-origin: bottom right;
|
|
369
|
+
transition:
|
|
370
|
+
transform var(--juno-dock-fold-smoothing) linear,
|
|
371
|
+
inline-size var(--juno-dock-fold-smoothing) linear;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.juno-dock__tray {
|
|
375
|
+
flex: 1;
|
|
376
|
+
min-inline-size: 0;
|
|
377
|
+
display: flex;
|
|
378
|
+
align-items: stretch;
|
|
379
|
+
opacity: calc(1 - var(--juno-dock-fold-slide, 0) * 1.6);
|
|
380
|
+
transition: opacity var(--juno-dock-fold-smoothing, 90ms) linear;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.juno-dock__knob {
|
|
384
|
+
position: absolute;
|
|
385
|
+
inset-block: 0;
|
|
386
|
+
inset-inline-end: var(--juno-space-4);
|
|
387
|
+
margin-block: auto;
|
|
388
|
+
inline-size: var(--juno-size-tap-comfortable);
|
|
389
|
+
block-size: var(--juno-size-tap-comfortable);
|
|
390
|
+
display: grid;
|
|
391
|
+
place-items: center;
|
|
392
|
+
padding: 0;
|
|
393
|
+
border: none;
|
|
394
|
+
border-radius: 50%;
|
|
395
|
+
background: none;
|
|
396
|
+
color: var(--juno-label);
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
-webkit-tap-highlight-color: transparent;
|
|
399
|
+
opacity: 0;
|
|
400
|
+
visibility: hidden;
|
|
401
|
+
transition: opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.juno-dock--collapsible[data-juno-collapsed] .juno-dock__tray { visibility: hidden; }
|
|
405
|
+
|
|
406
|
+
.juno-dock--collapsible[data-juno-collapsed] .juno-dock__knob {
|
|
407
|
+
opacity: 1;
|
|
408
|
+
visibility: visible;
|
|
409
|
+
}
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* Reuses .juno-modal for the surface + scrim; this file re-pins + re-slides.
|
|
11
11
|
* Selectors are compound (.juno-modal.juno-drawer) so they override the
|
|
12
12
|
* modal's centered transform regardless of bundle order.
|
|
13
|
+
*
|
|
14
|
+
* --bottom is a real bottom sheet: a grab handle (.juno-sheet__handle,
|
|
15
|
+
* decorative) plus a CSS height knob (--juno-sheet-h, default 60dvh —
|
|
16
|
+
* apps swap it for peek/half/full snap points, capped by --juno-sheet-max).
|
|
17
|
+
* No drag gesture ships here: resizing/dismissing by drag is stateful and
|
|
18
|
+
* stays in the app. See docs/components/drawer.md and 20260802-019.
|
|
13
19
|
* ════════════════════════════════════════════════════════════════════ */
|
|
14
20
|
|
|
15
21
|
.juno-modal.juno-drawer {
|
|
@@ -55,15 +61,24 @@
|
|
|
55
61
|
.juno-modal.juno-drawer--bottom {
|
|
56
62
|
inline-size: 100%;
|
|
57
63
|
max-inline-size: 100%;
|
|
58
|
-
|
|
64
|
+
|
|
65
|
+
/* height knob: swap --juno-sheet-h for snap points (peek/half/full).
|
|
66
|
+
--juno-sheet-max is the hard ceiling so a swapped value can't run past
|
|
67
|
+
it. CSS-only — the drag gesture a real snap-point picker needs is
|
|
68
|
+
stateful and stays in the app. */
|
|
69
|
+
block-size: var(--juno-sheet-h, 60dvh);
|
|
70
|
+
max-block-size: var(--juno-sheet-max, 92dvh);
|
|
59
71
|
margin-block: auto 0;
|
|
60
72
|
margin-inline: 0;
|
|
61
73
|
border: none;
|
|
62
74
|
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
75
|
+
border-start-start-radius: var(--juno-radius-8);
|
|
76
|
+
border-start-end-radius: var(--juno-radius-8);
|
|
63
77
|
transform: translateY(100%);
|
|
64
78
|
|
|
65
|
-
/*
|
|
66
|
-
|
|
79
|
+
/* safe area moves to .juno-modal__body below (the scroll port) — padding
|
|
80
|
+
here would just be dead space under the grab handle. See 20260802-019. */
|
|
81
|
+
padding-block-end: 0;
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
.juno-modal.juno-drawer--bottom[open] { transform: translateY(0); }
|
|
@@ -72,6 +87,28 @@
|
|
|
72
87
|
.juno-modal.juno-drawer--bottom[open] { transform: translateY(100%); }
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
/* grab handle — visual affordance only. aria-hidden in markup: the dialog's
|
|
91
|
+
own aria-labelledby (→ the title) stays the accessible name. Dragging it
|
|
92
|
+
to resize/dismiss is stateful, so junoui draws it but never wires a
|
|
93
|
+
gesture to it — see docs/accessibility.md. */
|
|
94
|
+
.juno-sheet__handle {
|
|
95
|
+
inline-size: var(--juno-space-40);
|
|
96
|
+
block-size: var(--juno-space-4);
|
|
97
|
+
margin: var(--juno-space-8) auto;
|
|
98
|
+
border-radius: 999px;
|
|
99
|
+
background: var(--juno-border-strong);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* the scrolling body owns the safe area now, mirroring modal.css's mobile
|
|
103
|
+
sheet. Compound selector for specificity: drawer.css sorts before
|
|
104
|
+
modal.css in the bundle (scripts/bundle-css.mjs), so a bare
|
|
105
|
+
.juno-modal__body override here would lose to modal.css's own rule at
|
|
106
|
+
equal specificity. env() fallback keeps its unit inside calc() — see
|
|
107
|
+
docs/ios-conformance.md. */
|
|
108
|
+
.juno-modal.juno-drawer--bottom .juno-modal__body {
|
|
109
|
+
padding-block-end: calc(var(--juno-pad-surface-inline) + env(safe-area-inset-bottom, 0px));
|
|
110
|
+
}
|
|
111
|
+
|
|
75
112
|
/* ── narrow viewports ────────────────────────────────────────────────────
|
|
76
113
|
Side drawers cap below the full width so a sliver of scrim survives —
|
|
77
114
|
the visual cue (and tap target) that "outside" closes the panel. */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* Component — Fold slot (animated presence for a row member)
|
|
3
|
+
* A slot inside any flex/grid row that animates between PRESENT and
|
|
4
|
+
* FOLDED-AWAY instead of popping in and out of the DOM: its inline-size
|
|
5
|
+
* folds to zero, it fades, and at the end of the fold it leaves the
|
|
6
|
+
* tab order via visibility (discrete transition: instant coming in,
|
|
7
|
+
* end-of-fade going out). Keep the element mounted; drive the state
|
|
8
|
+
* with the data-juno-in attribute — zero JS here, the app owns when.
|
|
9
|
+
*
|
|
10
|
+
* The size must be DEFINITE (--juno-fold-size) because a transition
|
|
11
|
+
* cannot run to or from an intrinsic width. If the row uses a flex/grid
|
|
12
|
+
* gap, the folded slot still occupies one gap — name it in
|
|
13
|
+
* --juno-fold-gap and the fold swallows it with a negative margin.
|
|
14
|
+
*
|
|
15
|
+
* Canonical use: a transient action in a toolbar/pill row (a
|
|
16
|
+
* scroll-to-top arrow, a contextual button) whose arrival should slide
|
|
17
|
+
* the row open rather than jump it.
|
|
18
|
+
* Usage:
|
|
19
|
+
* <div style="display:flex; gap:4px"> (any row)
|
|
20
|
+
* <button class="juno-fold" data-juno-in aria-label="Scroll to top">…</button>
|
|
21
|
+
* …other members…
|
|
22
|
+
* </div>
|
|
23
|
+
* prefers-reduced-motion needs nothing component-local — the base
|
|
24
|
+
* layer zeroes every transition duration. */
|
|
25
|
+
.juno-fold {
|
|
26
|
+
--juno-fold-size: var(--juno-size-tap-comfortable);
|
|
27
|
+
--juno-fold-gap: 0px;
|
|
28
|
+
|
|
29
|
+
flex-shrink: 0;
|
|
30
|
+
inline-size: var(--juno-fold-size);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
transition:
|
|
33
|
+
inline-size var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
34
|
+
margin var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
35
|
+
opacity var(--juno-motion-duration-base) var(--juno-motion-ease-standard),
|
|
36
|
+
visibility var(--juno-motion-duration-base) var(--juno-motion-ease-standard);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.juno-fold:not([data-juno-in]) {
|
|
40
|
+
inline-size: 0;
|
|
41
|
+
margin-inline-end: calc(-1 * var(--juno-fold-gap));
|
|
42
|
+
opacity: 0;
|
|
43
|
+
visibility: hidden;
|
|
44
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* Component — Icon loader (the ring-a-control primitive)
|
|
3
|
+
* A control ringed by the spinning arc while its section loads — the
|
|
4
|
+
* "this is loading" affordance for a rail / dock / pillbar item, but also
|
|
5
|
+
* any other control an app needs to ring: an icon button, a badge, an
|
|
6
|
+
* avatar. What it wraps stays static on top; the arc rings it and eats no
|
|
7
|
+
* pointer events, so the wrapped control still clicks through.
|
|
8
|
+
*
|
|
9
|
+
* This is the ONLY concentric-ring mechanism in junoui: anything that
|
|
10
|
+
* wants an arc around it — a nav glyph, a dock bubble, an arbitrary
|
|
11
|
+
* button or badge — composes this class instead of re-rolling absolute
|
|
12
|
+
* positioning. Both ring dimensions are custom props, so one wrapper
|
|
13
|
+
* serves a 14px glyph and a 44px bubble:
|
|
14
|
+
* --juno-icon-loader-ring diameter (default 1.9em — em, so it
|
|
15
|
+
* tracks the glyph it wraps)
|
|
16
|
+
* --juno-icon-loader-ring-width stroke (default 0.14em)
|
|
17
|
+
* A host with a definite box (a dock bubble, an icon button) sets the
|
|
18
|
+
* diameter to that box's size, so the ring hugs the edge and the box does
|
|
19
|
+
* not resize when the arc appears or leaves — ring it without changing
|
|
20
|
+
* its box. Below ~24px, add .juno-arc--smooth (loader.css) alongside
|
|
21
|
+
* --indeterminate: the default 12-step sweep reads as jitter that small.
|
|
22
|
+
*
|
|
23
|
+
* The app owns the state: add .juno-arc--indeterminate to spin, drop it
|
|
24
|
+
* when loaded (gate the blink to first-load, not every refetch). For a
|
|
25
|
+
* determinate ring, set --juno-progress on the .juno-arc instead.
|
|
26
|
+
* Usage:
|
|
27
|
+
* <a class="juno-rail__item" href="…" aria-current="page">
|
|
28
|
+
* <span class="juno-icon-loader">
|
|
29
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
30
|
+
* <span class="juno-arc juno-arc--indeterminate" role="status" aria-label="Loading"></span>
|
|
31
|
+
* </span>
|
|
32
|
+
* <span class="juno-rail__label">Library</span>
|
|
33
|
+
* </a>
|
|
34
|
+
*
|
|
35
|
+
* Gotcha encoded here: the arc's rotate animation overrides `transform`, so
|
|
36
|
+
* the ring is centred with a single-cell grid — never translate(-50%,-50%),
|
|
37
|
+
* which the spin would clobber.
|
|
38
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
39
|
+
|
|
40
|
+
.juno-icon-loader {
|
|
41
|
+
--juno-role: var(--juno-active);
|
|
42
|
+
|
|
43
|
+
/* Single-cell grid: every child occupies the SAME cell and is centred in
|
|
44
|
+
it, so the ring and what it rings are guaranteed concentric — no
|
|
45
|
+
transform (the arc's rotation would clobber a translate) and no fragile
|
|
46
|
+
margin math. With no explicit size the cell sizes to its largest child
|
|
47
|
+
(normally the ring, --juno-icon-loader-ring); give the host a definite
|
|
48
|
+
inline/block size to pin the cell and keep the box jitter-free. */
|
|
49
|
+
display: inline-grid;
|
|
50
|
+
place-items: center;
|
|
51
|
+
line-height: 0; /* drop the inline strut so the box hugs the ring */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* every child shares the one cell; the ringed content paints above the ring */
|
|
55
|
+
.juno-icon-loader > * {
|
|
56
|
+
grid-area: 1 / 1;
|
|
57
|
+
z-index: 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* the ring: sized from the two custom props, transparent to clicks */
|
|
61
|
+
.juno-icon-loader > .juno-arc {
|
|
62
|
+
--juno-arc-size: var(--juno-icon-loader-ring, 1.9em);
|
|
63
|
+
--juno-arc-width: var(--juno-icon-loader-ring-width, 0.14em);
|
|
64
|
+
|
|
65
|
+
z-index: 0;
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
}
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
* ════════════════════════════════════════════════════════════════════ */
|
|
17
17
|
|
|
18
18
|
.juno-icon {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/* Default lives in the var() fallback, NOT as an element-level declaration:
|
|
20
|
+
declaring --juno-icon-size on the element would shadow an ancestor that
|
|
21
|
+
sets it for contextual sizing (e.g. .juno-list__chevron). Explicit
|
|
22
|
+
modifiers below still set it on the element and win. See 20260727-011. */
|
|
21
23
|
display: inline-block;
|
|
22
|
-
inline-size: var(--juno-icon-size);
|
|
23
|
-
block-size: var(--juno-icon-size);
|
|
24
|
+
inline-size: var(--juno-icon-size, 1.25em);
|
|
25
|
+
block-size: var(--juno-icon-size, 1.25em);
|
|
24
26
|
flex-shrink: 0;
|
|
25
27
|
vertical-align: -0.2em; /* optical baseline alignment next to text */
|
|
26
28
|
fill: currentcolor;
|