Skip to content

Commit c18a003

Browse files
committed
work
1 parent a8b68a8 commit c18a003

7 files changed

Lines changed: 109 additions & 31 deletions

File tree

packages/frontend/navi/src/layout/dialog.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ const css = /* css */ `
680680
* scroll while open (its backdrop only covers the scrollport, so scrolling
681681
* there would reveal uncovered content); this prop extends the lock to the
682682
* whole page. Defaults to `true` for a dialog docked by `dockedOnSmallTouchScreen`.
683-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"growing"|`slide-from-${string}`} [props.animation]
683+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"growing"|`slide-from-${string}`|"cover-from-top"} [props.animation]
684684
* - `true`/`"auto"` resolves to `"scaling"` for a centered `positionArea`,
685685
* or a concrete `"slide-from-*"` direction otherwise. Any other explicit
686686
* value is used as-is. `"growing"` is the odd one out: every other kind

packages/frontend/navi/src/layout/popover.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ const css = /* css */ `
494494
* popover so the page/container behind it can't scroll while it's open.
495495
* @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
496496
* popover (see `focus_trap.js`).
497-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`} [props.animation]
497+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"cover-from-top"} [props.animation]
498498
* - `true`/`"auto"` resolves to a concrete `"slide-from-*"` direction
499499
* based on `positionArea`. Any other explicit value is used as-is.
500500
* @param {string} [props.animationDuration] - Maps to

packages/frontend/navi/src/layout/popup.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const css = /* css */ `
132132
* identically): the wash the backdrop paints over what is behind.
133133
* @param {string} [props.backdropFilter] - Forwarded as-is: what that wash
134134
* does to the picture underneath, `"blur(4px)"` and the like.
135-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"expanding"|`slide-from-${string}`|`expand-${string}`} [props.animation]
135+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"expanding"|`slide-from-${string}`|"cover-from-top"|`expand-${string}`} [props.animation]
136136
* - Forwarded as-is.
137137
* @param {string} [props.animationDuration] - Forwarded as-is.
138138
* @param {string} [props.maxWidth] - Forwarded as-is to both; also read

packages/frontend/navi/src/layout/popup_css.js

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
* so any consumer can override it per-instance from CSS (or via the
2727
* `animationDuration` prop, wired to --popup-animation-duration through
2828
* Box's styleCSSVars) without touching this file: `--popup-animation-duration`,
29-
* `--popup-scale-from`, `--popup-border-radius`. `slide-from-*`'s own
29+
* `--popup-scale-from`, `--popup-cover-travel`, `--popup-border-radius`.
30+
* `slide-from-*`'s own
3031
* 100%-of-own-size distance is hardcoded for now rather than exposed as a
3132
* variable — fine to revisit if a consumer ever needs to override it.
3233
*
@@ -64,6 +65,19 @@
6465
* so it slides in instead. The word names *where it comes from*: placed
6566
* "top" (a point/corner), it slides in from the top.
6667
*
68+
* `animation="cover-from-top"`: a slide reads through what it shows first,
69+
* and a box travelling its own height from above shows its bottom first —
70+
* the tail of whatever it holds, then, in the last frame, its head. That is
71+
* the wrong way round for a panel whose head sits at the edge it comes from
72+
* (a top-docked SidePanel: title and tabs at its top). So here the cut alone
73+
* does the reveal — the same curtain descending from the container's top
74+
* edge a full slide draws — while the box travels only `--popup-cover-travel`
75+
* (default 15%) of its own height: the head is on screen from the first
76+
* frame, settling its last few pixels into place. The exit is the same
77+
* movement backwards and still reads as a slide, the head leading. One
78+
* direction only: a bottom sheet leads with its head by itself, and a
79+
* left/right panel keeps its head on the axis it does not travel.
80+
*
6781
* `animation="expand-*"` (a real anchor only, explicit opt-in — "scaling"
6882
* reads better overall, see popover.jsx's top comment): grows out of the
6983
* anchor's own edge via `scale` + `transform-origin` instead of a translate, which
@@ -89,6 +103,7 @@ export const popupCss = /* css */ `
89103
.navi_dialog {
90104
--popup-animation-duration: 0.18s;
91105
--popup-scale-from: 0.9;
106+
--popup-cover-travel: 15%;
92107
93108
--popup-opacity-duration: var(--popup-animation-duration);
94109
--popup-translate-duration: var(--popup-animation-duration);
@@ -119,6 +134,31 @@ export const popupCss = /* css */ `
119134
var(--popup-translate-duration);
120135
transition-timing-function: ease;
121136
transition-behavior: allow-discrete;
137+
138+
/* Where the area the popup was placed in has its edges, in the popup's
139+
own coordinates: --container-position-room-* is how far past each of
140+
its own edges the popup may still paint before reaching that edge
141+
(applyNewPosition in @jsenv/dom), so the negated value puts a cut ON
142+
it. Read by the kinds below that cut their travel (slide, cover): each
143+
takes the side(s) it needs. A popup that was never placed reads
144+
100vmax and is not cut at all.
145+
Outside that area is either the glass beside an app narrowed with
146+
--navi-app-max-width (layout/safe_area.js) or whatever surrounds a
147+
container — neither the popup's to paint, and a popup in the top layer
148+
answers to no overflow of the document. The popup itself is never cut
149+
either: the rooms floor at 0. */
150+
--x-popup-cut-top-at-area: calc(
151+
-1 * var(--container-position-room-top, 100vmax)
152+
);
153+
--x-popup-cut-right-at-area: calc(
154+
-1 * var(--container-position-room-right, 100vmax)
155+
);
156+
--x-popup-cut-bottom-at-area: calc(
157+
-1 * var(--container-position-room-bottom, 100vmax)
158+
);
159+
--x-popup-cut-left-at-area: calc(
160+
-1 * var(--container-position-room-left, 100vmax)
161+
);
122162
}
123163
124164
/* box-shadow fades in/out alongside any animation kind, instead of
@@ -213,30 +253,8 @@ export const popupCss = /* css */ `
213253
&[navi-animation="slide-from-bottom-left"],
214254
&[navi-animation="slide-from-bottom-right"] {
215255
opacity: 1;
216-
/* Where the area the popup was placed in has its edges, in the popup's
217-
own coordinates: --container-position-room-* is how far past each of
218-
its own edges the popup may still paint before reaching that edge
219-
(applyNewPosition in @jsenv/dom), so the negated value puts a cut ON
220-
it. Only the side(s) the travel comes from take one (see the direction
221-
rules above); the rest stay far outside the box, uncut. A popup that
222-
was never placed reads 100vmax and is not cut at all.
223-
Outside that area is either the glass beside an app narrowed with
224-
--navi-app-max-width (layout/safe_area.js) or whatever surrounds a
225-
container — neither the popup's to paint, and a popup in the top layer
226-
answers to no overflow of the document. The popup itself is never cut
227-
either: the rooms floor at 0. */
228-
--x-popup-cut-top-at-area: calc(
229-
-1 * var(--container-position-room-top, 100vmax)
230-
);
231-
--x-popup-cut-right-at-area: calc(
232-
-1 * var(--container-position-room-right, 100vmax)
233-
);
234-
--x-popup-cut-bottom-at-area: calc(
235-
-1 * var(--container-position-room-bottom, 100vmax)
236-
);
237-
--x-popup-cut-left-at-area: calc(
238-
-1 * var(--container-position-room-left, 100vmax)
239-
);
256+
/* Only the side(s) the travel comes from take a cut (see the direction
257+
rules above); the rest stay far outside the box, uncut. */
240258
--x-popup-travel-x: calc(var(--x-popup-slide-x, 0) * 100%);
241259
--x-popup-travel-y: calc(var(--x-popup-slide-y, -1) * 100%);
242260
clip-path: inset(
@@ -263,6 +281,40 @@ export const popupCss = /* css */ `
263281
transform: translate(var(--x-popup-travel-x), var(--x-popup-travel-y));
264282
}
265283
}
284+
285+
/* cover — a top-docked popup unrolling from the top edge of its area (see
286+
this file's top comment): the cut does the reveal, the box travels
287+
--popup-cover-travel of its own height. The far cut sits where the
288+
popup's shadow ends (--navi-popup-box-shadow reaches about 50px) or
289+
where the room does, whichever is nearer, so the reveal finishes on
290+
the box and its shadow at the pace of the box's own settle. On the
291+
box's own edge it would shave the shadow off for good; on a distant
292+
container edge it would run far ahead of the box. */
293+
&[navi-animation="cover-from-top"] {
294+
opacity: 1;
295+
--x-popup-cut-top: var(--x-popup-cut-top-at-area);
296+
--x-popup-cut-bottom: max(var(--x-popup-cut-bottom-at-area), -60px);
297+
--x-popup-travel-y: calc(-1 * var(--popup-cover-travel));
298+
clip-path: inset(
299+
var(--x-popup-cut-top) -100vmax var(--x-popup-cut-bottom) -100vmax
300+
);
301+
302+
transform: translate(0px, 0px);
303+
304+
&[aria-expanded="false"] {
305+
/* The near cut follows the travel, so it holds the area's top line at
306+
every instant (the slide family's arithmetic); the far cut starts on
307+
that same line and descends to its open place. What is on screen is
308+
the descending curtain a full slide draws, holding the top of the
309+
popup instead of its tail. */
310+
clip-path: inset(
311+
calc(var(--x-popup-cut-top) - var(--x-popup-travel-y)) -100vmax
312+
calc(100% + var(--x-popup-travel-y) - var(--x-popup-cut-top))
313+
-100vmax
314+
);
315+
transform: translate(0px, var(--x-popup-travel-y));
316+
}
317+
}
266318
}
267319
268320
.navi_popover {

packages/frontend/navi/src/layout/side_panel.jsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ import { withPropsClassName } from "../utils/with_props_class_name.js";
1818
import { Popup } from "./popup.jsx";
1919
import { createSwipeToClose, SWIPE_AXIS_BY_SIDE } from "./swipe_to_close.js";
2020

21+
// What `animation={true}` plays, by the edge the panel is docked to. A top
22+
// panel keeps its head — the one part of it the eye knows — at the edge it
23+
// comes from, so a slide of its own height shows its tail first and its head
24+
// in the last frame; it unrolls instead (popup_css.js, `cover-from-top`). A
25+
// bottom panel leads with its head by itself, and a left/right one keeps it on
26+
// the axis it does not travel.
27+
const ANIMATION_BY_SIDE = {
28+
left: "slide-from-left",
29+
right: "slide-from-right",
30+
top: "cover-from-top",
31+
bottom: "slide-from-bottom",
32+
};
33+
2134
const css = /* css */ `
2235
.navi_side_panel {
2336
/* Side panel create a barriere with the content that is full size */
@@ -236,8 +249,9 @@ const css = /* css */ `
236249
* @param {boolean|"fading"} [props.animation] - Off by default (unlike
237250
* `Dialog`/`Popover` themselves) — SidePanel is commonly toggled instead
238251
* of opened/closed as a one-off, where a slide transition is more often
239-
* undesired noise than not. `true` slides in from `side`; `"fading"` is
240-
* the other common choice. Other values are forwarded as-is but not a
252+
* undesired noise than not. `true` slides in from `side` (a top panel
253+
* unrolls from its edge instead, head first — see `ANIMATION_BY_SIDE`);
254+
* `"fading"` is the other common choice. Other values are forwarded as-is but not a
241255
* documented/encouraged part of this component's own API.
242256
* @param {boolean} [props.closeOnClickOutside=false] - `false` (default):
243257
* maps to `pointerInteractionOutsideEffect="none"` — in popover mode, no
@@ -307,7 +321,7 @@ export const SidePanel = ({
307321
// A side panel is flush against the edge it slides in from — none of
308322
// Dialog's own default gap with the container.
309323
marginWithContainer={0}
310-
animation={animation === true ? `slide-from-${side}` : animation}
324+
animation={animation === true ? ANIMATION_BY_SIDE[side] : animation}
311325
pointerInteractionOutsideEffect={closeOnClickOutside ? "close" : "none"}
312326
focusCapture={closeOnClickOutside}
313327
minWidth={toCssLength(minWidth)}

packages/frontend/navi/src/layout/swipe_to_close.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ export const createSwipeToClose = (side, { grip } = {}) => {
110110
travelTo(sizeOf(panelEl, axis) * closeDirection, 0, restore);
111111
return;
112112
}
113+
// The closed style is rendered once while transitions are still off.
114+
// Where the release travel left the panel and where its closed style
115+
// puts it are the same point only for a kind that travels the panel's
116+
// full size (popup_css.js, slide-from-*); a cover kind rests a fraction
117+
// in, and handing the styles back before this frame would transition
118+
// the panel from one to the other in plain view.
119+
panelEl.getBoundingClientRect();
113120
restore();
114121
};
115122

packages/frontend/navi/src/nav/demos/route_transition_fixed_bars/route_transition_fixed_bars.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
padding: 16px;
6868
background: #f3ecfb;
6969
}
70+
71+
:root {
72+
--app-max-width: 600px;
73+
--navi-app-max-width: var(--app-max-width);
74+
}
7075
</style>
7176
</head>
7277
<body>

0 commit comments

Comments
 (0)