@ssgoi/react 7.0.0-beta.0 → 7.0.0-beta.1
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/README.md +194 -380
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,476 +1,290 @@
|
|
|
1
1
|
# @ssgoi/react
|
|
2
2
|
|
|
3
|
-
React bindings for SSGOI
|
|
3
|
+
React bindings for SSGOI.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://ssgoi.dev)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## AI-Assisted Setup
|
|
10
|
-
|
|
11
|
-
Using Claude, Cursor, ChatGPT, or another AI assistant? Point it at:
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
https://ssgoi.dev/llms.txt
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
It has the full setup guide, every transition, the API, and troubleshooting — everything an agent needs to wire SSGOI into your app.
|
|
18
|
-
|
|
19
|
-
## What is SSGOI?
|
|
20
|
-
|
|
21
|
-
SSGOI brings native app-like page transitions to the web. Transform your static page navigations into smooth, delightful experiences that users love.
|
|
22
|
-
|
|
23
|
-
### ✨ Key Features
|
|
24
|
-
|
|
25
|
-
- **🌍 Works Everywhere** - Unlike the browser's View Transition API, SSGOI works in all modern browsers (Chrome, Firefox, Safari)
|
|
26
|
-
- **🚀 SSR Ready** - Perfect compatibility with Next.js. No hydration issues, SEO-friendly
|
|
27
|
-
- **🎯 Use Your Router** - Keep your existing routing. React Router, Next.js App Router - all work seamlessly
|
|
28
|
-
- **💾 State Persistence** - Remembers animation state during navigation, even with browser back/forward
|
|
29
|
-
- **⚛️ React Optimized** - Built specifically for React with hooks and modern patterns
|
|
30
|
-
|
|
31
|
-
## Installation
|
|
7
|
+
[Live demos](https://ssgoi.dev) · [Hero, Zoom, Film, and Sheet in motion](https://ssgoi.dev/blog/view-transition-api-limitations)
|
|
32
8
|
|
|
33
9
|
```bash
|
|
34
10
|
npm install @ssgoi/react
|
|
35
|
-
# or
|
|
36
|
-
yarn add @ssgoi/react
|
|
37
|
-
# or
|
|
38
|
-
pnpm add @ssgoi/react
|
|
39
11
|
```
|
|
40
12
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### 1. Wrap your app
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
import { Ssgoi } from "@ssgoi/react";
|
|
47
|
-
import { fade } from "@ssgoi/react/view-transitions";
|
|
48
|
-
import { SsgoiTransitionBoundary } from "./ssgoi-transition-boundary";
|
|
49
|
-
|
|
50
|
-
const config = {
|
|
51
|
-
transitions: [{ from: "/", to: "/about", transition: fade() }],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export default function App() {
|
|
55
|
-
return (
|
|
56
|
-
<div className="relative z-0 min-h-dvh bg-white">
|
|
57
|
-
{/* Layout shell above: positioned ancestor + stacking context for the OUT clone. */}
|
|
58
|
-
<Ssgoi config={config}>
|
|
59
|
-
{/* Routed content marker. Layout positioning belongs to the outer wrapper. */}
|
|
60
|
-
<SsgoiTransitionBoundary className="min-h-full bg-white">
|
|
61
|
-
{/* Your app */}
|
|
62
|
-
</SsgoiTransitionBoundary>
|
|
63
|
-
</Ssgoi>
|
|
64
|
-
</div>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
```
|
|
13
|
+
Agent setup guide: https://ssgoi.dev/llms.txt
|
|
68
14
|
|
|
69
|
-
|
|
15
|
+
## Contents
|
|
70
16
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
</main>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
```
|
|
17
|
+
- Next.js
|
|
18
|
+
- Persistent layouts and sliding tabs
|
|
19
|
+
- React Router
|
|
20
|
+
- TanStack Router
|
|
21
|
+
- Config
|
|
22
|
+
- Effect index
|
|
81
23
|
|
|
82
|
-
|
|
83
|
-
It reads the current pathname internally, sets the transition boundary key, and
|
|
84
|
-
uses that pathname as a logical page id matched by config such as
|
|
85
|
-
`/products/*`. Use `/products/**` when the parent path itself should match too.
|
|
24
|
+
## Next.js
|
|
86
25
|
|
|
87
|
-
|
|
26
|
+
Keep one config and one `<Ssgoi>` above route boundaries.
|
|
88
27
|
|
|
89
28
|
```tsx
|
|
29
|
+
// app/ssgoi-provider.tsx
|
|
90
30
|
"use client";
|
|
91
31
|
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
94
|
-
|
|
95
|
-
export function SsgoiTransitionBoundary({
|
|
96
|
-
children,
|
|
97
|
-
as,
|
|
98
|
-
className,
|
|
99
|
-
}: {
|
|
100
|
-
children: ReactNode;
|
|
101
|
-
as?: ElementType;
|
|
102
|
-
className?: string;
|
|
103
|
-
}) {
|
|
104
|
-
const pathname = usePathname();
|
|
105
|
-
const Component = as ?? "div";
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<Component
|
|
109
|
-
key={pathname}
|
|
110
|
-
data-ssgoi-transition={pathname}
|
|
111
|
-
className={className}
|
|
112
|
-
>
|
|
113
|
-
{children}
|
|
114
|
-
</Component>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
React Router and TanStack Router use the same component body with their own
|
|
120
|
-
pathname hook.
|
|
121
|
-
|
|
122
|
-
**That's it!** Your configured pages now transition smoothly with a fade effect.
|
|
123
|
-
|
|
124
|
-
## Layout Requirements
|
|
125
|
-
|
|
126
|
-
The outer element wrapping the SSGOI provider / `<Ssgoi>` needs
|
|
127
|
-
`position: relative` and `z-index: 0` (`relative z-0` in Tailwind).
|
|
128
|
-
|
|
129
|
-
When a page leaves, SSGOI clones it back into the DOM with `position: absolute`
|
|
130
|
-
so it can animate out while the new page animates in. Without a positioned,
|
|
131
|
-
stacking-context ancestor the clone jumps to the wrong place or falls behind the
|
|
132
|
-
background. Add `overflow-x-clip` too if you use horizontal transitions
|
|
133
|
-
(`slide`, `drill`). Keep these layout classes on the outer wrapper, not on the
|
|
134
|
-
route boundary marker.
|
|
135
|
-
|
|
136
|
-
## Advanced Transitions
|
|
137
|
-
|
|
138
|
-
### Route-based Transitions
|
|
139
|
-
|
|
140
|
-
Transition factories return effect configs. Put each effect in a flat route rule:
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
import { fade, drill, zoom } from "@ssgoi/react/view-transitions";
|
|
32
|
+
import { Ssgoi } from "@ssgoi/react";
|
|
33
|
+
import { drill, slide } from "@ssgoi/react/view-transitions";
|
|
144
34
|
|
|
145
35
|
const config = {
|
|
146
36
|
transitions: [
|
|
147
|
-
|
|
148
|
-
{ from: "/home", to: "/about", transition: fade() },
|
|
149
|
-
|
|
150
|
-
// iOS-style drill-in when entering details
|
|
151
|
-
{ from: "/products", to: "/products/*", transition: drill() },
|
|
152
|
-
|
|
153
|
-
// Card-to-detail zoom (needs matching data-zoom-*-key)
|
|
37
|
+
{ on: "/posts/**", except: "/posts", transition: drill() },
|
|
154
38
|
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
transition: zoom({ type: "expand" }),
|
|
39
|
+
ordered: ["/products/all", "/products/electronics", "/products/fashion"],
|
|
40
|
+
transition: slide(),
|
|
158
41
|
},
|
|
159
42
|
],
|
|
160
43
|
};
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Route rules come in three shapes:
|
|
164
|
-
|
|
165
|
-
- **`{ on, except?, transition }`** — a route family / stack
|
|
166
|
-
- **`{ from, to, transition }`** — a precise bidirectional pair
|
|
167
|
-
- **`{ ordered, transition }`** — index order decides forward / backward
|
|
168
44
|
|
|
169
|
-
|
|
45
|
+
export function SsgoiProvider({ children }) {
|
|
46
|
+
return <Ssgoi config={config}>{children}</Ssgoi>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
170
49
|
|
|
171
|
-
|
|
50
|
+
Build the layout shell next:
|
|
172
51
|
|
|
173
52
|
```tsx
|
|
174
|
-
|
|
175
|
-
import {
|
|
53
|
+
// app/layout.tsx
|
|
54
|
+
import { SsgoiProvider } from "./ssgoi-provider";
|
|
176
55
|
|
|
177
|
-
function
|
|
56
|
+
export default function RootLayout({ children }) {
|
|
178
57
|
return (
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
>
|
|
186
|
-
<h2>Animated Card</h2>
|
|
187
|
-
</div>
|
|
58
|
+
<html>
|
|
59
|
+
<body>
|
|
60
|
+
<main className="relative z-0 min-h-dvh overflow-x-clip">
|
|
61
|
+
<SsgoiProvider>{children}</SsgoiProvider>
|
|
62
|
+
</main>
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
|
188
65
|
);
|
|
189
66
|
}
|
|
190
67
|
```
|
|
191
68
|
|
|
192
|
-
|
|
69
|
+
When React removes an outgoing route, SSGOI temporarily reinserts that
|
|
70
|
+
detached DOM node with `position: absolute`. `relative` gives it a containing
|
|
71
|
+
block, `z-0` creates the local stacking context, and `overflow-x-clip` prevents
|
|
72
|
+
horizontal transition flashes. These classes belong on the shell around
|
|
73
|
+
`<Ssgoi>`, not on route boundaries.
|
|
74
|
+
|
|
75
|
+
### Route boundary
|
|
193
76
|
|
|
194
|
-
|
|
77
|
+
A React route boundary is a keyed element marked with a route id:
|
|
195
78
|
|
|
196
|
-
|
|
79
|
+
```tsx
|
|
80
|
+
<div key={boundary.key} data-ssgoi-transition={boundary.id}>
|
|
81
|
+
{children}
|
|
82
|
+
</div>
|
|
83
|
+
```
|
|
197
84
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
85
|
+
- Changing `key` makes React unmount the old region and mount the new one.
|
|
86
|
+
- The attribute identifies those regions for SSGOI config matching.
|
|
87
|
+
- `<Ssgoi>` observes that lifecycle and runs OUT/IN; it does not navigate or
|
|
88
|
+
change the key.
|
|
201
89
|
|
|
202
|
-
|
|
90
|
+
The legacy `<SsgoiTransition>` wrapper only added this attribute and is
|
|
91
|
+
deprecated. Set `data-ssgoi-transition` directly on the keyed application
|
|
92
|
+
boundary.
|
|
203
93
|
|
|
204
|
-
|
|
94
|
+
Create a router-aware utility in the application. Keep its route logic behind
|
|
95
|
+
semantic names so layouts cannot invent inconsistent keys:
|
|
205
96
|
|
|
206
97
|
```tsx
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
|
|
210
|
-
|
|
211
|
-
const nextConfig: NextConfig = {
|
|
212
|
-
webpack: (config) => {
|
|
213
|
-
config.plugins.push(SsgoiAutoKey());
|
|
214
|
-
return config;
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
export default nextConfig;
|
|
219
|
-
```
|
|
98
|
+
// app/ssgoi-route-boundary.tsx
|
|
99
|
+
"use client";
|
|
220
100
|
|
|
221
|
-
|
|
101
|
+
import { type ReactNode } from "react";
|
|
102
|
+
import { usePathname, useSelectedLayoutSegments } from "next/navigation";
|
|
222
103
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
import { defineConfig } from "vite";
|
|
226
|
-
import react from "@vitejs/plugin-react";
|
|
227
|
-
import SsgoiAutoKey from "@ssgoi/react/unplugin/vite";
|
|
104
|
+
type BoundaryName = "app-shell" | "main-content" | "project-content";
|
|
105
|
+
type BoundaryIdentity = { id: string; key: string };
|
|
228
106
|
|
|
229
|
-
|
|
230
|
-
plugins: [react(), SsgoiAutoKey()],
|
|
231
|
-
});
|
|
232
|
-
```
|
|
107
|
+
const INTERCEPTION_PREFIX = /^(?:\(\.\.\.\)|\(\.\.\)|\(\.\))+/;
|
|
233
108
|
|
|
234
|
-
|
|
109
|
+
function isRouteGroup(segment: string) {
|
|
110
|
+
return segment.startsWith("(") && segment.endsWith(")");
|
|
111
|
+
}
|
|
235
112
|
|
|
236
|
-
|
|
113
|
+
function pathFromSegments(segments: string[]) {
|
|
114
|
+
const path = segments
|
|
115
|
+
.filter((segment) => !isRouteGroup(segment))
|
|
116
|
+
.map((segment) => segment.replace(INTERCEPTION_PREFIX, ""))
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join("/");
|
|
237
119
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
import { fade, slide } from "@ssgoi/react/transitions";
|
|
120
|
+
return path ? `/${path}` : null;
|
|
121
|
+
}
|
|
241
122
|
|
|
242
|
-
function
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
);
|
|
123
|
+
function resolveBoundary(
|
|
124
|
+
name: BoundaryName,
|
|
125
|
+
pathname: string,
|
|
126
|
+
segments: string[],
|
|
127
|
+
): BoundaryIdentity {
|
|
128
|
+
const ownedRoute = pathFromSegments(segments) ?? pathname;
|
|
129
|
+
|
|
130
|
+
switch (name) {
|
|
131
|
+
case "app-shell": {
|
|
132
|
+
const routeGroup = segments.find(isRouteGroup);
|
|
133
|
+
if (routeGroup === "(main)") {
|
|
134
|
+
return { id: ownedRoute, key: "main-shell" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const project = ownedRoute.match(/^\/projects\/[^/]+/)?.[0];
|
|
138
|
+
return { id: ownedRoute, key: project ?? ownedRoute };
|
|
139
|
+
}
|
|
140
|
+
case "project-content": {
|
|
141
|
+
const project = pathname.match(/^\/projects\/[^/]+/)?.[0];
|
|
142
|
+
const child = pathFromSegments(segments);
|
|
143
|
+
const id = project
|
|
144
|
+
? child
|
|
145
|
+
? `${project}${child}`
|
|
146
|
+
: project
|
|
147
|
+
: ownedRoute;
|
|
148
|
+
return { id, key: id };
|
|
149
|
+
}
|
|
150
|
+
case "main-content":
|
|
151
|
+
return { id: ownedRoute, key: ownedRoute };
|
|
152
|
+
}
|
|
248
153
|
}
|
|
249
|
-
```
|
|
250
154
|
|
|
251
|
-
|
|
155
|
+
export function SsgoiRouteBoundary({
|
|
156
|
+
children,
|
|
157
|
+
name,
|
|
158
|
+
}: {
|
|
159
|
+
children: ReactNode;
|
|
160
|
+
name: BoundaryName;
|
|
161
|
+
}) {
|
|
162
|
+
const pathname = usePathname();
|
|
163
|
+
const segments = useSelectedLayoutSegments("children");
|
|
164
|
+
const boundary = resolveBoundary(name, pathname, segments);
|
|
252
165
|
|
|
253
|
-
```tsx
|
|
254
|
-
// Explicit key required for transition state tracking
|
|
255
|
-
function Card() {
|
|
256
166
|
return (
|
|
257
|
-
<div
|
|
258
|
-
|
|
259
|
-
key: "my-card",
|
|
260
|
-
...fade(),
|
|
261
|
-
})}
|
|
262
|
-
>
|
|
263
|
-
<h2>Animated Card</h2>
|
|
167
|
+
<div key={boundary.key} data-ssgoi-transition={boundary.id}>
|
|
168
|
+
{children}
|
|
264
169
|
</div>
|
|
265
170
|
);
|
|
266
171
|
}
|
|
267
172
|
```
|
|
268
173
|
|
|
269
|
-
|
|
174
|
+
## Persistent layouts
|
|
175
|
+
|
|
176
|
+
Place one named boundary in the common app layout, then add child boundaries
|
|
177
|
+
only around routed content that changes while its parent remains mounted.
|
|
270
178
|
|
|
271
179
|
```tsx
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
{items.map((item) => (
|
|
277
|
-
<li
|
|
278
|
-
key={item.id} // JSX key is enough - plugin generates file:line:col:${key}
|
|
279
|
-
ref={transition(fade())}
|
|
280
|
-
>
|
|
281
|
-
{item.name}
|
|
282
|
-
</li>
|
|
283
|
-
))}
|
|
284
|
-
</ul>
|
|
285
|
-
);
|
|
286
|
-
}
|
|
180
|
+
// app/layout.tsx — replace the provider line from the shell example.
|
|
181
|
+
<SsgoiProvider>
|
|
182
|
+
<SsgoiRouteBoundary name="app-shell">{children}</SsgoiRouteBoundary>
|
|
183
|
+
</SsgoiProvider>
|
|
287
184
|
```
|
|
288
185
|
|
|
289
|
-
## Next.js App Router Example
|
|
290
|
-
|
|
291
186
|
```tsx
|
|
292
|
-
// app/
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
import { type ReactNode } from "react";
|
|
296
|
-
import { Ssgoi } from "@ssgoi/react";
|
|
297
|
-
import { drill, fade } from "@ssgoi/react/view-transitions";
|
|
298
|
-
import { SsgoiTransitionBoundary } from "./ssgoi-transition-boundary";
|
|
299
|
-
|
|
300
|
-
const config = {
|
|
301
|
-
transitions: [
|
|
302
|
-
{ on: "/post/**", except: "/post", transition: drill() },
|
|
303
|
-
{ from: "/", to: "/about", transition: fade() },
|
|
304
|
-
],
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
export function SsgoiProvider({ children }: { children: ReactNode }) {
|
|
308
|
-
return (
|
|
309
|
-
<Ssgoi config={config}>
|
|
310
|
-
<SsgoiTransitionBoundary className="min-h-full bg-white">
|
|
311
|
-
{children}
|
|
312
|
-
</SsgoiTransitionBoundary>
|
|
313
|
-
</Ssgoi>
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// app/layout.tsx
|
|
318
|
-
import { type ReactNode } from "react";
|
|
319
|
-
import { SsgoiProvider } from "./ssgoi-provider";
|
|
320
|
-
|
|
321
|
-
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
187
|
+
// app/(main)/layout.tsx
|
|
188
|
+
export default function MainLayout({ children }) {
|
|
322
189
|
return (
|
|
323
|
-
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
</main>
|
|
328
|
-
</body>
|
|
329
|
-
</html>
|
|
190
|
+
<>
|
|
191
|
+
<SsgoiRouteBoundary name="main-content">{children}</SsgoiRouteBoundary>
|
|
192
|
+
<BottomNav />
|
|
193
|
+
</>
|
|
330
194
|
);
|
|
331
195
|
}
|
|
332
196
|
```
|
|
333
197
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
```tsx
|
|
350
|
-
<Ssgoi config={ssgoiConfig}>{children}</Ssgoi>
|
|
198
|
+
- Main → main: only the inner content boundary changes; the nav stays.
|
|
199
|
+
- Main → detail: the common app-shell key changes; the nav leaves with it.
|
|
200
|
+
- Project tab → project tab: the current project base-path key stays; a project
|
|
201
|
+
content boundary can change below its header and tabs.
|
|
202
|
+
|
|
203
|
+
Use Next.js route groups to describe layout ownership, not to duplicate the
|
|
204
|
+
outer boundary:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
app/
|
|
208
|
+
layout.tsx # one <Ssgoi> + one named app-shell boundary
|
|
209
|
+
(main)/layout.tsx
|
|
210
|
+
(main)/page.tsx
|
|
211
|
+
(main)/search/page.tsx
|
|
212
|
+
(detail)/post/[id]/page.tsx
|
|
351
213
|
```
|
|
352
214
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
```tsx
|
|
360
|
-
<SsgoiTransitionBoundary className="min-h-full bg-white">
|
|
361
|
-
{children}
|
|
362
|
-
</SsgoiTransitionBoundary>
|
|
363
|
-
```
|
|
215
|
+
For Next.js parallel and intercepting routes, the browser pathname may point at
|
|
216
|
+
a modal while the background `children` slot has not changed. Resolve that
|
|
217
|
+
boundary’s id/key from `useSelectedLayoutSegments("children")`. In the layout
|
|
218
|
+
that owns `@modal`, `useSelectedLayoutSegment("modal") !== null` is an explicit
|
|
219
|
+
active-modal check. A soft intercept keeps the background key; direct entry to
|
|
220
|
+
the same URL resolves to the detail slot and receives a detail key.
|
|
364
221
|
|
|
365
|
-
|
|
222
|
+
Full pattern: https://ssgoi.dev/llms/bottom-nav.txt
|
|
366
223
|
|
|
367
|
-
|
|
224
|
+
## React Router
|
|
368
225
|
|
|
369
|
-
|
|
226
|
+
Replace `usePathname()` with `useLocation()` and render the boundary in a
|
|
227
|
+
nested layout route:
|
|
370
228
|
|
|
371
229
|
```tsx
|
|
372
|
-
const {
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Functions
|
|
376
|
-
|
|
377
|
-
#### `transition()`
|
|
230
|
+
const { pathname } = useLocation();
|
|
378
231
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
key: "unique-key",
|
|
385
|
-
in: fade(),
|
|
386
|
-
out: fade(),
|
|
387
|
-
})}
|
|
388
|
-
>
|
|
389
|
-
Content
|
|
390
|
-
</div>
|
|
232
|
+
return (
|
|
233
|
+
<div key={pathname} data-ssgoi-transition={pathname}>
|
|
234
|
+
<Outlet />
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
391
237
|
```
|
|
392
238
|
|
|
393
|
-
##
|
|
394
|
-
|
|
395
|
-
### Page Transitions (`@ssgoi/react/view-transitions`)
|
|
396
|
-
|
|
397
|
-
- `fade()` - Calm cross-fade. Safe default for unrelated pages
|
|
398
|
-
- `drill()` - iOS-style hierarchical navigation (list → detail)
|
|
399
|
-
- `slide()` - Horizontal push for tabs / sequential flows
|
|
400
|
-
- `scroll()` - Vertical page scroll for onboarding / paginated views
|
|
401
|
-
- `axis()` - Material/Flutter shared-axis swap for sibling/tab routes
|
|
402
|
-
- `sheet()` - Bottom sheet that slides up (modal-like flows)
|
|
403
|
-
- `hero()` - Shared element transition (matching `data-hero-enter-key` / `data-hero-exit-key`)
|
|
404
|
-
- `zoom()` - Card-to-detail expansion (matching `data-zoom-enter-key` / `data-zoom-exit-key`)
|
|
405
|
-
- `strip()` - 3D Y-axis perspective flip
|
|
406
|
-
- `blind()` - Window-blinds wipe reveal
|
|
407
|
-
- `film()` - Cinematic shrink + tile (gallery / lightbox)
|
|
408
|
-
- `rotate()` - Card flip between siblings
|
|
409
|
-
- `jaemin()` - Playful rotated zoom for special moments
|
|
410
|
-
|
|
411
|
-
### Element Transitions (`@ssgoi/react/transitions`)
|
|
412
|
-
|
|
413
|
-
- `fade()` - Fade in/out
|
|
414
|
-
- `scale()` - Scale in/out
|
|
415
|
-
- `slide()` - Slide (direction: up/down/left/right)
|
|
416
|
-
- `rotate()` - Rotate
|
|
417
|
-
- `bounce()` - Bounce
|
|
418
|
-
- `blur()` - Blur
|
|
419
|
-
- `fly()` - Fly (custom x, y position)
|
|
239
|
+
## TanStack Router
|
|
420
240
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
All transitions use spring physics for natural motion:
|
|
241
|
+
Read the pathname from router state and place the boundary in a parent route:
|
|
424
242
|
|
|
425
243
|
```tsx
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
spring: {
|
|
429
|
-
stiffness: 300, // 1-1000, higher = faster
|
|
430
|
-
damping: 30, // 0-100, higher = less oscillation
|
|
431
|
-
},
|
|
244
|
+
const pathname = useRouterState({
|
|
245
|
+
select: (state) => state.location.pathname,
|
|
432
246
|
});
|
|
433
247
|
```
|
|
434
248
|
|
|
435
|
-
##
|
|
436
|
-
|
|
437
|
-
SSGOI is written in TypeScript and provides full type definitions:
|
|
249
|
+
## Config
|
|
438
250
|
|
|
439
|
-
```
|
|
440
|
-
import
|
|
251
|
+
```ts
|
|
252
|
+
import { drill, slide, zoom } from "@ssgoi/react/view-transitions";
|
|
441
253
|
|
|
442
|
-
const config
|
|
443
|
-
|
|
254
|
+
const config = {
|
|
255
|
+
transitions: [
|
|
256
|
+
{ on: "/posts/**", except: "/posts", transition: drill() },
|
|
257
|
+
{ from: "/gallery", to: "/gallery/*", transition: zoom() },
|
|
258
|
+
{ ordered: ["/tabs/a", "/tabs/b"], transition: slide() },
|
|
259
|
+
],
|
|
444
260
|
};
|
|
445
261
|
```
|
|
446
262
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
450
|
-
-
|
|
451
|
-
-
|
|
452
|
-
- All modern mobile browsers
|
|
453
|
-
|
|
454
|
-
## Performance
|
|
455
|
-
|
|
456
|
-
- Minimal bundle size (~8kb gzipped)
|
|
457
|
-
- Hardware-accelerated animations
|
|
458
|
-
- Automatic cleanup and memory management
|
|
459
|
-
- Smart preloading for instant transitions
|
|
263
|
+
- `on`: route family.
|
|
264
|
+
- `from`/`to`: precise pair.
|
|
265
|
+
- `ordered`: directional sequence.
|
|
266
|
+
- Patterns support exact paths, a `*` path segment, and suffix `**`.
|
|
267
|
+
- `priority` overrides path specificity.
|
|
460
268
|
|
|
461
|
-
##
|
|
269
|
+
## Effect index
|
|
462
270
|
|
|
463
|
-
|
|
271
|
+
- `fade`: unrelated pages.
|
|
272
|
+
- `drill`: list → detail.
|
|
273
|
+
- `slide`: ordered tabs.
|
|
274
|
+
- `axis`: sibling destinations.
|
|
275
|
+
- `sheet`: modal-like routes.
|
|
276
|
+
- `zoom`: card/image → detail.
|
|
277
|
+
- `hero`: shared elements and page chrome.
|
|
278
|
+
- `scroll`: vertical sequences.
|
|
279
|
+
- `strip`, `film`, `rotate`, `blind`, `jaemin`: expressive transitions.
|
|
464
280
|
|
|
465
|
-
-
|
|
466
|
-
- Interactive examples
|
|
467
|
-
- Advanced patterns
|
|
468
|
-
- Migration guides
|
|
281
|
+
References: https://ssgoi.dev/llms.txt#7-transition-index
|
|
469
282
|
|
|
470
|
-
##
|
|
283
|
+
## Low-level APIs
|
|
471
284
|
|
|
472
|
-
|
|
285
|
+
`transition()` and the auto-key plugins remain available for element-level
|
|
286
|
+
mount/unmount animations; they are separate from route boundaries.
|
|
473
287
|
|
|
474
288
|
## License
|
|
475
289
|
|
|
476
|
-
MIT
|
|
290
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssgoi/react",
|
|
3
|
-
"version": "7.0.0-beta.
|
|
3
|
+
"version": "7.0.0-beta.1",
|
|
4
4
|
"description": "React bindings for SSGOI - Native app-like page transitions for React applications",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"unplugin": "^2.3.2",
|
|
68
68
|
"@babel/core": "^7.26.10",
|
|
69
|
-
"@ssgoi/core": "^7.0.0-beta.
|
|
69
|
+
"@ssgoi/core": "^7.0.0-beta.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__core": "^7.20.5",
|