shelving 1.292.0 → 1.292.2
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/package.json +1 -1
- package/ui/router/Navigation.d.ts +2 -0
- package/ui/router/Navigation.js +28 -6
- package/ui/router/Navigation.md +16 -0
- package/ui/router/Navigation.tsx +30 -6
- package/ui/transition/HorizontalTransition.css +13 -2
- package/ui/transition/HorizontalTransition.md +21 -17
- package/ui/transition/Transition.md +32 -15
- package/ui/transition/VerticalTransition.css +13 -2
- package/ui/transition/VerticalTransition.md +13 -2
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ export interface NavigationProps extends PossibleMeta, OptionalChildProps {
|
|
|
14
14
|
* - Intercepts same-origin anchor clicks (excluding `download` anchors) and turns them into `forward()` calls.
|
|
15
15
|
* - Listens for `popstate` to sync the store with browser back/forward.
|
|
16
16
|
* - Publishes the live URL into the `Meta` context via `mergeMeta()`, so descendant `<Router>`s re-render on navigation and merge invariants hold (e.g. `root` defaults to the live URL's origin when unset).
|
|
17
|
+
* - Publishes each URL change inside `startTransition()` with a `"forward"` or `"back"` transition type, so a `<Transition>` around the routes runs a view transition.
|
|
18
|
+
* - Skips the transition when the browser already animated the change (`PopStateEvent.hasUAVisualTransition`, e.g. a swipe-back gesture).
|
|
17
19
|
*
|
|
18
20
|
* Exactly one `<Navigation>` per app — nested routers share this single store.
|
|
19
21
|
*
|
package/ui/router/Navigation.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect } from "react";
|
|
2
|
+
import { startTransition, useEffect, useState } from "react";
|
|
3
3
|
import { useInstance } from "../../react/useInstance.js";
|
|
4
|
-
import { useStore } from "../../react/useStore.js";
|
|
5
4
|
import { MetaContext, requireMeta } from "../misc/MetaContext.js";
|
|
5
|
+
import { setTransitionType } from "../transition/util.js";
|
|
6
6
|
import { mergeMeta } from "../util/index.js";
|
|
7
7
|
import { NavigationContext } from "./NavigationContext.js";
|
|
8
8
|
import { NavigationStore } from "./NavigationStore.js";
|
|
@@ -12,6 +12,8 @@ import { NavigationStore } from "./NavigationStore.js";
|
|
|
12
12
|
* - Intercepts same-origin anchor clicks (excluding `download` anchors) and turns them into `forward()` calls.
|
|
13
13
|
* - Listens for `popstate` to sync the store with browser back/forward.
|
|
14
14
|
* - Publishes the live URL into the `Meta` context via `mergeMeta()`, so descendant `<Router>`s re-render on navigation and merge invariants hold (e.g. `root` defaults to the live URL's origin when unset).
|
|
15
|
+
* - Publishes each URL change inside `startTransition()` with a `"forward"` or `"back"` transition type, so a `<Transition>` around the routes runs a view transition.
|
|
16
|
+
* - Skips the transition when the browser already animated the change (`PopStateEvent.hasUAVisualTransition`, e.g. a swipe-back gesture).
|
|
15
17
|
*
|
|
16
18
|
* Exactly one `<Navigation>` per app — nested routers share this single store.
|
|
17
19
|
*
|
|
@@ -23,10 +25,26 @@ import { NavigationStore } from "./NavigationStore.js";
|
|
|
23
25
|
export function Navigation({ children, ...meta }) {
|
|
24
26
|
const current = requireMeta(meta);
|
|
25
27
|
const nav = useInstance(NavigationStore, current.url, current.root);
|
|
26
|
-
|
|
28
|
+
// React runs a `<ViewTransition>` only for a transition update, and `useSyncExternalStore()` always renders a sync update.
|
|
29
|
+
// So keep the published URL in state and copy each store change into it inside `startTransition()`.
|
|
30
|
+
const [url, setURL] = useState(nav.value);
|
|
27
31
|
useEffect(() => {
|
|
28
32
|
if (typeof document === "undefined" || typeof window === "undefined")
|
|
29
33
|
return;
|
|
34
|
+
// Type of the next transition: `"back"` for a `popstate`, else `"forward"` (link click, `forward()`, `redirect()`).
|
|
35
|
+
// `null` means no transition, because the browser already animated the change (e.g. a swipe-back gesture).
|
|
36
|
+
let type = "forward";
|
|
37
|
+
const stop = nav.subscribe(value => {
|
|
38
|
+
const t = type;
|
|
39
|
+
type = "forward";
|
|
40
|
+
if (!t)
|
|
41
|
+
return setURL(value);
|
|
42
|
+
// React renders a transition that starts inside a `popstate` event as a sync update with no view transition, so leave the event first.
|
|
43
|
+
setTimeout(() => startTransition(() => {
|
|
44
|
+
setTransitionType(t);
|
|
45
|
+
setURL(value);
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
30
48
|
const onClick = (e) => {
|
|
31
49
|
if (e.target instanceof Element) {
|
|
32
50
|
const anchor = e.target.closest("a") ||
|
|
@@ -38,15 +56,19 @@ export function Navigation({ children, ...meta }) {
|
|
|
38
56
|
}
|
|
39
57
|
}
|
|
40
58
|
};
|
|
41
|
-
const onPopState = () => {
|
|
42
|
-
|
|
59
|
+
const onPopState = (e) => {
|
|
60
|
+
const href = window.location.href;
|
|
61
|
+
if (href !== nav.value.href)
|
|
62
|
+
type = e.hasUAVisualTransition ? null : "back";
|
|
63
|
+
nav.value = href;
|
|
43
64
|
};
|
|
44
65
|
document.addEventListener("click", onClick);
|
|
45
66
|
window.addEventListener("popstate", onPopState);
|
|
46
67
|
return () => {
|
|
47
68
|
document.removeEventListener("click", onClick);
|
|
48
69
|
window.removeEventListener("popstate", onPopState);
|
|
70
|
+
stop();
|
|
49
71
|
};
|
|
50
72
|
}, [nav]);
|
|
51
|
-
return (_jsx(NavigationContext, { value: nav, children: _jsx(MetaContext, { value: mergeMeta(current, { url
|
|
73
|
+
return (_jsx(NavigationContext, { value: nav, children: _jsx(MetaContext, { value: mergeMeta(current, { url }, Navigation), children: children }) }));
|
|
52
74
|
}
|
package/ui/router/Navigation.md
CHANGED
|
@@ -6,6 +6,9 @@ The top-level navigation provider for a client-side app. It owns a single `Navig
|
|
|
6
6
|
|
|
7
7
|
- Same-origin anchor clicks are intercepted automatically and turned into `forward()` calls. Add a `download` attribute to an anchor to opt out.
|
|
8
8
|
- It listens for `popstate` so the store stays in sync with browser back/forward.
|
|
9
|
+
- It publishes each page change inside `startTransition()`, so a `<Transition>` around the routes animates it. A link click, `NavigationStore.forward()` or `NavigationStore.redirect()` sets the `"forward"` transition type. The browser back or forward button sets `"back"` (`popstate` does not say which way it went).
|
|
10
|
+
- When the browser already animated the change, for example a swipe-back gesture on a phone, there is no view transition, so the page does not slide twice. The browser says so with `hasUAVisualTransition` on the `popstate` event. Other browsers still slide.
|
|
11
|
+
- The `popstate` update waits for the next task (`setTimeout()`). React renders a transition that starts inside `popstate` as a sync update with no view transition.
|
|
9
12
|
- It initialises the store from the surrounding `<Meta>` url/base, so set those on an ancestor `<App>` / `<HTML>` / `<Page>`. In the browser the store falls back to `window.location.href` when no url is set.
|
|
10
13
|
- The meta it publishes always has a `root` — when none is set anywhere, `root` defaults to the live URL's origin. Setting `root` explicitly on `<App>` / `<HTML>` is still strongly recommended, especially for apps served under a sub-path.
|
|
11
14
|
- `<Router>` works with no `<Navigation>` at all (SSR, static rendering, tests) — `<Navigation>` is only what makes the URL *live* on the client.
|
|
@@ -22,6 +25,19 @@ import { HTML, Navigation, Router } from "shelving/ui";
|
|
|
22
25
|
</HTML>
|
|
23
26
|
```
|
|
24
27
|
|
|
28
|
+
### Page transitions
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { HorizontalTransition, Navigation, Router } from "shelving/ui";
|
|
32
|
+
|
|
33
|
+
// Links slide right, the browser back button slides left.
|
|
34
|
+
<Navigation>
|
|
35
|
+
<HorizontalTransition>
|
|
36
|
+
<Router routes={ROUTES}/>
|
|
37
|
+
</HorizontalTransition>
|
|
38
|
+
</Navigation>
|
|
39
|
+
```
|
|
40
|
+
|
|
25
41
|
### Imperative navigation
|
|
26
42
|
|
|
27
43
|
Read the navigation store from anywhere in the tree with `requireNavigation()` for imperative URL changes:
|
package/ui/router/Navigation.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type ReactElement, useEffect } from "react";
|
|
1
|
+
import { type ReactElement, startTransition, useEffect, useState } from "react";
|
|
2
2
|
import { useInstance } from "../../react/useInstance.js";
|
|
3
|
-
import { useStore } from "../../react/useStore.js";
|
|
4
3
|
import { MetaContext, requireMeta } from "../misc/MetaContext.js";
|
|
4
|
+
import { setTransitionType, type TransitionType } from "../transition/util.js";
|
|
5
5
|
import { mergeMeta, type PossibleMeta } from "../util/index.js";
|
|
6
6
|
import type { OptionalChildProps } from "../util/props.js";
|
|
7
7
|
import { NavigationContext } from "./NavigationContext.js";
|
|
@@ -20,6 +20,8 @@ export interface NavigationProps extends PossibleMeta, OptionalChildProps {}
|
|
|
20
20
|
* - Intercepts same-origin anchor clicks (excluding `download` anchors) and turns them into `forward()` calls.
|
|
21
21
|
* - Listens for `popstate` to sync the store with browser back/forward.
|
|
22
22
|
* - Publishes the live URL into the `Meta` context via `mergeMeta()`, so descendant `<Router>`s re-render on navigation and merge invariants hold (e.g. `root` defaults to the live URL's origin when unset).
|
|
23
|
+
* - Publishes each URL change inside `startTransition()` with a `"forward"` or `"back"` transition type, so a `<Transition>` around the routes runs a view transition.
|
|
24
|
+
* - Skips the transition when the browser already animated the change (`PopStateEvent.hasUAVisualTransition`, e.g. a swipe-back gesture).
|
|
23
25
|
*
|
|
24
26
|
* Exactly one `<Navigation>` per app — nested routers share this single store.
|
|
25
27
|
*
|
|
@@ -31,11 +33,30 @@ export interface NavigationProps extends PossibleMeta, OptionalChildProps {}
|
|
|
31
33
|
export function Navigation({ children, ...meta }: NavigationProps): ReactElement {
|
|
32
34
|
const current = requireMeta(meta);
|
|
33
35
|
const nav = useInstance(NavigationStore, current.url, current.root);
|
|
34
|
-
|
|
36
|
+
|
|
37
|
+
// React runs a `<ViewTransition>` only for a transition update, and `useSyncExternalStore()` always renders a sync update.
|
|
38
|
+
// So keep the published URL in state and copy each store change into it inside `startTransition()`.
|
|
39
|
+
const [url, setURL] = useState(nav.value);
|
|
35
40
|
|
|
36
41
|
useEffect(() => {
|
|
37
42
|
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
38
43
|
|
|
44
|
+
// Type of the next transition: `"back"` for a `popstate`, else `"forward"` (link click, `forward()`, `redirect()`).
|
|
45
|
+
// `null` means no transition, because the browser already animated the change (e.g. a swipe-back gesture).
|
|
46
|
+
let type: TransitionType | null = "forward";
|
|
47
|
+
const stop = nav.subscribe(value => {
|
|
48
|
+
const t = type;
|
|
49
|
+
type = "forward";
|
|
50
|
+
if (!t) return setURL(value);
|
|
51
|
+
// React renders a transition that starts inside a `popstate` event as a sync update with no view transition, so leave the event first.
|
|
52
|
+
setTimeout(() =>
|
|
53
|
+
startTransition(() => {
|
|
54
|
+
setTransitionType(t);
|
|
55
|
+
setURL(value);
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
39
60
|
const onClick = (e: MouseEvent) => {
|
|
40
61
|
if (e.target instanceof Element) {
|
|
41
62
|
const anchor =
|
|
@@ -48,8 +69,10 @@ export function Navigation({ children, ...meta }: NavigationProps): ReactElement
|
|
|
48
69
|
}
|
|
49
70
|
}
|
|
50
71
|
};
|
|
51
|
-
const onPopState = () => {
|
|
52
|
-
|
|
72
|
+
const onPopState = (e: PopStateEvent) => {
|
|
73
|
+
const href = window.location.href;
|
|
74
|
+
if (href !== nav.value.href) type = e.hasUAVisualTransition ? null : "back";
|
|
75
|
+
nav.value = href;
|
|
53
76
|
};
|
|
54
77
|
|
|
55
78
|
document.addEventListener("click", onClick);
|
|
@@ -58,12 +81,13 @@ export function Navigation({ children, ...meta }: NavigationProps): ReactElement
|
|
|
58
81
|
return () => {
|
|
59
82
|
document.removeEventListener("click", onClick);
|
|
60
83
|
window.removeEventListener("popstate", onPopState);
|
|
84
|
+
stop();
|
|
61
85
|
};
|
|
62
86
|
}, [nav]);
|
|
63
87
|
|
|
64
88
|
return (
|
|
65
89
|
<NavigationContext value={nav}>
|
|
66
|
-
<MetaContext value={mergeMeta(current, { url
|
|
90
|
+
<MetaContext value={mergeMeta(current, { url }, Navigation)}>{children}</MetaContext>
|
|
67
91
|
</NavigationContext>
|
|
68
92
|
);
|
|
69
93
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
@
|
|
1
|
+
@import url("../style/layers.css");
|
|
2
|
+
@import url("../style/Duration.module.css");
|
|
3
|
+
|
|
4
|
+
/* Global CSS, not a CSS module — see `Transition.css` for why. */
|
|
5
|
+
@layer defaults {
|
|
2
6
|
:root {
|
|
3
|
-
|
|
7
|
+
/* A percentage is of the snapshot's own size, so the old and new content sit edge to edge and never overlap. */
|
|
8
|
+
--horizontal-transition-size: 100%;
|
|
4
9
|
--horizontal-transition-duration: var(--duration-normal);
|
|
5
10
|
}
|
|
6
11
|
}
|
|
@@ -53,6 +58,12 @@
|
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/* Clip the slide to the element's own box, so it does not paint over the content around it. */
|
|
62
|
+
::view-transition-group(.slide-right),
|
|
63
|
+
::view-transition-group(.slide-left) {
|
|
64
|
+
overflow: clip;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
::view-transition-new(.slide-right) {
|
|
57
68
|
animation: slide-in-from-right var(--horizontal-transition-duration) ease-in-out both;
|
|
58
69
|
}
|
|
@@ -5,35 +5,39 @@ A direction-aware `<Transition>` preset that slides its children horizontally
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
7
7
|
- Slides right by default and when the type is `"forward"`; slides left when the type is `"back"`.
|
|
8
|
-
-
|
|
8
|
+
- Inside `<Navigation>` the direction is automatic: a link click, `NavigationStore.forward()` or `NavigationStore.redirect()` sets the `"forward"` type, and the browser back or forward button sets `"back"`. Outside `<Navigation>`, set the direction with `setTransitionType("forward" | "back")` inside a `startTransition()` callback — see `<Transition>`.
|
|
9
9
|
- Pass `overlay` to raise the transition group above surrounding content during the animation (`z-index: 100`).
|
|
10
|
+
- The old and new content slide a full width apart, so they sit edge to edge and never overlap.
|
|
11
|
+
- The slide is clipped to the element's own box (`overflow: clip` on the group), so it does not paint over the content around it.
|
|
12
|
+
- During the slide, the snapshots paint above fixed elements such as a bottom bar. To keep a fixed element on top, give it its own `view-transition-name` and put the slide groups below it:
|
|
13
|
+
|
|
14
|
+
```css
|
|
15
|
+
::view-transition-group(.slide-right),
|
|
16
|
+
::view-transition-group(.slide-left) {
|
|
17
|
+
z-index: -1;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
10
21
|
- Under `prefers-reduced-motion: reduce` the slide distance is forced to `0`, so the transition degrades to an opacity-only crossfade with no positional movement (large viewport-level slides are exactly what the preference exists to suppress).
|
|
11
22
|
|
|
12
23
|
## Usage
|
|
13
24
|
|
|
14
25
|
```tsx
|
|
15
|
-
import { HorizontalTransition,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// In the layout:
|
|
27
|
-
<HorizontalTransition>
|
|
28
|
-
<Router routes={ROUTES}/>
|
|
29
|
-
</HorizontalTransition>
|
|
26
|
+
import { HorizontalTransition, Navigation, Router } from "shelving/ui";
|
|
27
|
+
|
|
28
|
+
// Links slide right, the browser back button slides left.
|
|
29
|
+
<Navigation>
|
|
30
|
+
<HorizontalTransition>
|
|
31
|
+
<Router routes={ROUTES}/>
|
|
32
|
+
</HorizontalTransition>
|
|
33
|
+
</Navigation>
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
## Styling
|
|
33
37
|
|
|
34
38
|
| Variable | Styles | Default |
|
|
35
39
|
|---|---|---|
|
|
36
|
-
| `--horizontal-transition-size` | Slide distance for the enter/leave keyframes | `
|
|
40
|
+
| `--horizontal-transition-size` | Slide distance for the enter/leave keyframes. A percentage is of the element's own width. | `100%` |
|
|
37
41
|
| `--horizontal-transition-duration` | Duration of the slide keyframes | `var(--duration-normal)` |
|
|
38
42
|
|
|
39
43
|
**Global tokens it reads** — `--duration-normal`.
|
|
@@ -6,7 +6,8 @@ The base View Transition wrapper. It wraps its children in React 19's `<ViewTran
|
|
|
6
6
|
|
|
7
7
|
- Set `default` for the base transition; `forward` and `back` default to it and let you pick a direction-aware variant. The class names must correspond to `::view-transition-old(.className)` / `::view-transition-new(.className)` rules in your CSS.
|
|
8
8
|
- Pass `overlay` to raise the transition group above surrounding content during the animation (`z-index: 100`, from `Transition.css`).
|
|
9
|
-
- Direction is driven by the active view-transition type.
|
|
9
|
+
- Direction is driven by the active view-transition type. `<Navigation>` sets it for each page change (`"forward"` for a link or `NavigationStore.forward()`, `"back"` for the browser back button). For any other change, call `setTransitionType("forward")` (or `"back"`) inside a `startTransition()` callback; the variants read that type to choose the correct slide.
|
|
10
|
+
- React runs a view transition only for a transition update (`startTransition()`, `useDeferredValue()` or a Suspense reveal). A sync update, for example a `useStore()` change, swaps the content with no animation.
|
|
10
11
|
- The preset variants honour `prefers-reduced-motion: reduce` — positional movement (slides, collapse) is removed while opacity-only fades are kept (see each preset's page). Custom transition classes should ship their own `@media (prefers-reduced-motion: reduce)` override; `animation: none` on the `::view-transition-*` pseudo-elements makes the swap an instant cut while the DOM update still happens.
|
|
11
12
|
|
|
12
13
|
## Usage
|
|
@@ -31,24 +32,40 @@ import { FadeTransition } from "shelving/ui";
|
|
|
31
32
|
</FadeTransition>
|
|
32
33
|
```
|
|
33
34
|
|
|
35
|
+
### Page transitions
|
|
36
|
+
|
|
37
|
+
`<Navigation>` publishes each page change as a transition with a direction type:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { HorizontalTransition, Navigation, Router } from "shelving/ui";
|
|
41
|
+
|
|
42
|
+
// Slides right on forward, left on back:
|
|
43
|
+
<Navigation>
|
|
44
|
+
<HorizontalTransition>
|
|
45
|
+
<Router routes={ROUTES}/>
|
|
46
|
+
</HorizontalTransition>
|
|
47
|
+
</Navigation>
|
|
48
|
+
```
|
|
49
|
+
|
|
34
50
|
### Setting the direction with `setTransitionType()`
|
|
35
51
|
|
|
36
52
|
```tsx
|
|
37
|
-
import { HorizontalTransition, setTransitionType
|
|
38
|
-
import { startTransition } from "react";
|
|
39
|
-
|
|
40
|
-
function
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
import { HorizontalTransition, setTransitionType } from "shelving/ui";
|
|
54
|
+
import { startTransition, useState } from "react";
|
|
55
|
+
|
|
56
|
+
function Steps() {
|
|
57
|
+
const [step, setStep] = useState(0);
|
|
58
|
+
const go = (direction: "forward" | "back") =>
|
|
59
|
+
startTransition(() => {
|
|
60
|
+
setTransitionType(direction);
|
|
61
|
+
setStep(s => s + (direction === "forward" ? 1 : -1));
|
|
62
|
+
});
|
|
63
|
+
return (
|
|
64
|
+
<HorizontalTransition>
|
|
65
|
+
<Step key={step} step={step} onNext={() => go("forward")} onBack={() => go("back")}/>
|
|
66
|
+
</HorizontalTransition>
|
|
67
|
+
);
|
|
46
68
|
}
|
|
47
|
-
|
|
48
|
-
// In the layout — slides right on forward, left on back:
|
|
49
|
-
<HorizontalTransition>
|
|
50
|
-
<Router routes={ROUTES}/>
|
|
51
|
-
</HorizontalTransition>
|
|
52
69
|
```
|
|
53
70
|
|
|
54
71
|
## Styling
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
@
|
|
1
|
+
@import url("../style/layers.css");
|
|
2
|
+
@import url("../style/Duration.module.css");
|
|
3
|
+
|
|
4
|
+
/* Global CSS, not a CSS module — see `Transition.css` for why. */
|
|
5
|
+
@layer defaults {
|
|
2
6
|
:root {
|
|
3
|
-
|
|
7
|
+
/* A percentage is of the snapshot's own size, so the old and new content sit edge to edge and never overlap. */
|
|
8
|
+
--vertical-transition-size: 100%;
|
|
4
9
|
--vertical-transition-duration: var(--duration-normal);
|
|
5
10
|
}
|
|
6
11
|
}
|
|
@@ -53,6 +58,12 @@
|
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/* Clip the slide to the element's own box, so it does not paint over the content around it. */
|
|
62
|
+
::view-transition-group(.slide-up),
|
|
63
|
+
::view-transition-group(.slide-down) {
|
|
64
|
+
overflow: clip;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
::view-transition-new(.slide-up) {
|
|
57
68
|
animation: slide-in-from-bottom var(--vertical-transition-duration) ease-in-out both;
|
|
58
69
|
}
|
|
@@ -5,8 +5,19 @@ A direction-aware `<Transition>` preset that slides its children vertically —
|
|
|
5
5
|
**Things to know:**
|
|
6
6
|
|
|
7
7
|
- Slides down by default and when the type is `"forward"`; slides up when the type is `"back"`.
|
|
8
|
-
-
|
|
8
|
+
- Inside `<Navigation>` the direction is automatic: a link click, `NavigationStore.forward()` or `NavigationStore.redirect()` sets the `"forward"` type, and the browser back or forward button sets `"back"`. Outside `<Navigation>`, set the direction with `setTransitionType("forward" | "back")` inside a `startTransition()` callback — see `<Transition>`.
|
|
9
9
|
- Pass `overlay` to raise the transition group above surrounding content during the animation (`z-index: 100`).
|
|
10
|
+
- The old and new content slide a full height apart, so they sit edge to edge and never overlap.
|
|
11
|
+
- The slide is clipped to the element's own box (`overflow: clip` on the group), so it does not paint over the content around it.
|
|
12
|
+
- During the slide, the snapshots paint above fixed elements such as a bottom bar. To keep a fixed element on top, give it its own `view-transition-name` and put the slide groups below it:
|
|
13
|
+
|
|
14
|
+
```css
|
|
15
|
+
::view-transition-group(.slide-up),
|
|
16
|
+
::view-transition-group(.slide-down) {
|
|
17
|
+
z-index: -1;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
10
21
|
- Under `prefers-reduced-motion: reduce` the slide distance is forced to `0`, so the transition degrades to an opacity-only crossfade with no positional movement (large viewport-level slides are exactly what the preference exists to suppress).
|
|
11
22
|
|
|
12
23
|
## Usage
|
|
@@ -23,7 +34,7 @@ import { VerticalTransition } from "shelving/ui";
|
|
|
23
34
|
|
|
24
35
|
| Variable | Styles | Default |
|
|
25
36
|
|---|---|---|
|
|
26
|
-
| `--vertical-transition-size` | Slide distance for the enter/leave keyframes | `
|
|
37
|
+
| `--vertical-transition-size` | Slide distance for the enter/leave keyframes. A percentage is of the element's own height. | `100%` |
|
|
27
38
|
| `--vertical-transition-duration` | Duration of the slide keyframes | `var(--duration-normal)` |
|
|
28
39
|
|
|
29
40
|
**Global tokens it reads** — `--duration-normal`.
|