opencode-skills-collection 3.0.35 → 3.0.37
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/bundled-skills/.antigravity-install-manifest.json +15 -1
- package/bundled-skills/accesslint-audit/SKILL.md +115 -0
- package/bundled-skills/accesslint-diff/SKILL.md +81 -0
- package/bundled-skills/accesslint-scan/SKILL.md +47 -0
- package/bundled-skills/composition-patterns/SKILL.md +87 -0
- package/bundled-skills/composition-patterns/rules/_sections.md +29 -0
- package/bundled-skills/composition-patterns/rules/_template.md +24 -0
- package/bundled-skills/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/bundled-skills/composition-patterns/rules/architecture-compound-components.md +112 -0
- package/bundled-skills/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/bundled-skills/composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/bundled-skills/composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/bundled-skills/composition-patterns/rules/state-context-interface.md +191 -0
- package/bundled-skills/composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/bundled-skills/composition-patterns/rules/state-lift-state.md +125 -0
- package/bundled-skills/debugging-toolkit/SKILL.md +35 -0
- package/bundled-skills/deploy-to-vercel/SKILL.md +304 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy.sh +301 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/backups/README-2026-06-02.md +687 -0
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +4 -4
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +245 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/plugins.md +21 -13
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +95 -0
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/polis-protocol/SKILL.md +93 -0
- package/bundled-skills/python-development/SKILL.md +35 -0
- package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
- package/bundled-skills/react-native-skills/SKILL.md +120 -0
- package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
- package/bundled-skills/react-native-skills/rules/_template.md +28 -0
- package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
- package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
- package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
- package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
- package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
- package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
- package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
- package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
- package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
- package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
- package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
- package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
- package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
- package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
- package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
- package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
- package/bundled-skills/skill-issue/SKILL.md +73 -0
- package/bundled-skills/tdd-workflows/SKILL.md +35 -0
- package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
- package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
- package/bundled-skills/vercel-optimize/SKILL.md +331 -0
- package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
- package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
- package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
- package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
- package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
- package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
- package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
- package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
- package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
- package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
- package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
- package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
- package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
- package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
- package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
- package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
- package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
- package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
- package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
- package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
- package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
- package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
- package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
- package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
- package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
- package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
- package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
- package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
- package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
- package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
- package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
- package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
- package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
- package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
- package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
- package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
- package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
- package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
- package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
- package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
- package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
- package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
- package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
- package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
- package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
- package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
- package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
- package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
- package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
- package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
- package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
- package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
- package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
- package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
- package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
- package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
- package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
- package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
- package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
- package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
- package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
- package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
- package/bundled-skills/vercel-optimize/references/verification.md +102 -0
- package/bundled-skills/vercel-optimize/references/voice.md +76 -0
- package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
- package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
- package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
- package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
- package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
- package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
- package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
- package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
- package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
- package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
- package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
- package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
- package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
- package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
- package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
- package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
- package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
- package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
- package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
- package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
- package/package.json +2 -2
- package/skills_index.json +312 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Implementation Workflow
|
|
2
|
+
|
|
3
|
+
Follow these steps in order when adding view transitions to an app. Each step builds on the previous one.
|
|
4
|
+
|
|
5
|
+
## Step 1: Audit the App
|
|
6
|
+
|
|
7
|
+
Before writing any code, scan the codebase thoroughly. Search for:
|
|
8
|
+
|
|
9
|
+
- **Every `<Link>` and `router.push`** — these are your navigation triggers. Open every file that contains one.
|
|
10
|
+
- **Every `<Suspense>` boundary** — each one is a candidate for a reveal animation. Check what its fallback renders.
|
|
11
|
+
- **Every page/route component** — list them all. Each page needs a VT placement decision.
|
|
12
|
+
- **Persistent elements** — headers, navbars, sidebars, sticky controls that stay on screen across navigations. These need `viewTransitionName` isolation.
|
|
13
|
+
- **Shared visual elements** — images, cards, or avatars that appear on both a source and target view (e.g., a thumbnail in a list and the same image on a detail page).
|
|
14
|
+
- **Skeleton-to-content control pairs** — if a Suspense fallback renders a control (search input, tab bar) that also exists in the real content, both need a matching `viewTransitionName`.
|
|
15
|
+
|
|
16
|
+
Then classify every navigation and produce a navigation map:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
| Route | Navigates to | Direction | VT pattern |
|
|
20
|
+
|-----------------|----------------------|--------------|-----------------------|
|
|
21
|
+
| / | /detail/[id] | forward | directional slide |
|
|
22
|
+
| /detail/[id] | / | back | directional slide |
|
|
23
|
+
| /detail/[id] | /detail/[other] | sequential | directional slide (ordered prev/next) or key+share crossfade |
|
|
24
|
+
| /tab/[a] | /tab/[b] | lateral | key+share crossfade |
|
|
25
|
+
| (Suspense) | (content loads) | — | slide-up reveal |
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For each shared element (`name` prop), note every navigation where a pair forms and where it doesn't — this determines whether you need `enter`/`exit` as a fallback alongside `share`.
|
|
29
|
+
|
|
30
|
+
## Step 2: Add CSS Recipes
|
|
31
|
+
|
|
32
|
+
Copy the **complete** CSS recipe set from `css-recipes.md` into your global stylesheet. This includes timing variables, shared keyframes, fade, slide (vertical), directional navigation (forward/back), shared element morph, persistent element isolation, and reduced motion.
|
|
33
|
+
|
|
34
|
+
Do not write your own animation CSS — the recipes handle staggered timing, motion blur on morphs, and reduced motion that are easy to get wrong. You can customize timing variables (`--duration-exit`, `--duration-enter`, `--duration-move`) after the initial setup.
|
|
35
|
+
|
|
36
|
+
## Step 3: Isolate Persistent Elements
|
|
37
|
+
|
|
38
|
+
For every persistent element identified in Step 1, add a `viewTransitionName` style to pull it out of the page content's transition snapshot:
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
<header style={{ viewTransitionName: "site-header" }}>...</header>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then add the persistent element isolation CSS from `css-recipes.md` (prevents the element from animating during page transitions). If the element uses `backdrop-blur` or `backdrop-filter`, use the backdrop-blur workaround from `css-recipes.md` instead.
|
|
45
|
+
|
|
46
|
+
If a Suspense fallback mirrors a persistent control (e.g., a skeleton search input), give both the real control and the skeleton the same `viewTransitionName` so they morph in place.
|
|
47
|
+
|
|
48
|
+
## Step 4: Add Directional Page Transitions
|
|
49
|
+
|
|
50
|
+
For hierarchical navigations identified in Step 1, tag the navigation direction using `addTransitionType` inside `startTransition`:
|
|
51
|
+
|
|
52
|
+
```jsx
|
|
53
|
+
startTransition(() => {
|
|
54
|
+
addTransitionType('nav-forward');
|
|
55
|
+
router.push('/detail/1');
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then wrap each **page component** (not layout) in a type-keyed `<ViewTransition>`:
|
|
60
|
+
|
|
61
|
+
```jsx
|
|
62
|
+
<ViewTransition
|
|
63
|
+
enter={{
|
|
64
|
+
"nav-forward": "nav-forward",
|
|
65
|
+
"nav-back": "nav-back",
|
|
66
|
+
default: "none",
|
|
67
|
+
}}
|
|
68
|
+
exit={{
|
|
69
|
+
"nav-forward": "nav-forward",
|
|
70
|
+
"nav-back": "nav-back",
|
|
71
|
+
default: "none",
|
|
72
|
+
}}
|
|
73
|
+
default="none"
|
|
74
|
+
>
|
|
75
|
+
<div>...page content...</div>
|
|
76
|
+
</ViewTransition>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `nav-forward` and `nav-back` CSS classes from `css-recipes.md` produce horizontal slides. For simpler apps where directional motion isn't needed, a bare `<ViewTransition default="none">` wrapper with `enter="fade-in"` / `exit="fade-out"` works too.
|
|
80
|
+
|
|
81
|
+
Extract this into a reusable component so every page doesn't repeat the verbose type map:
|
|
82
|
+
|
|
83
|
+
```jsx
|
|
84
|
+
export function DirectionalTransition({ children }: { children: React.ReactNode }) {
|
|
85
|
+
return (
|
|
86
|
+
<ViewTransition
|
|
87
|
+
enter={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
|
|
88
|
+
exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
|
|
89
|
+
default="none"
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</ViewTransition>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This also becomes the single place to adjust if you add new transition types later.
|
|
98
|
+
|
|
99
|
+
**Rules:**
|
|
100
|
+
- Always pair `enter` with `exit` — without an exit animation, the old page disappears instantly while the new one animates in.
|
|
101
|
+
- Always include `default: "none"` in type map objects and `default="none"` on the component — otherwise it fires on every transition.
|
|
102
|
+
- Place the directional `<ViewTransition>` in each page component, not in a layout. Layouts persist across navigations and never trigger enter/exit.
|
|
103
|
+
- Only use directional slides for hierarchical navigation or ordered sequences (prev/next). Lateral/sibling navigation (tab-to-tab) should use a bare `<ViewTransition>` (cross-fade) or `default="none"`.
|
|
104
|
+
|
|
105
|
+
## Step 5: Add Suspense Reveals
|
|
106
|
+
|
|
107
|
+
For every `<Suspense>` boundary identified in Step 1, wrap the fallback and content in separate `<ViewTransition>`s:
|
|
108
|
+
|
|
109
|
+
```jsx
|
|
110
|
+
<Suspense
|
|
111
|
+
fallback={
|
|
112
|
+
<ViewTransition exit="slide-down">
|
|
113
|
+
<Skeleton />
|
|
114
|
+
</ViewTransition>
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<ViewTransition enter="slide-up" default="none">
|
|
118
|
+
<AsyncContent />
|
|
119
|
+
</ViewTransition>
|
|
120
|
+
</Suspense>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This example uses `slide-down` / `slide-up` for directional vertical motion. For a simpler reveal, a bare `<ViewTransition>` around the `<Suspense>` gives a cross-fade with zero configuration. Choose based on the spatial meaning — consult the "Choosing the Right Animation Style" table in the main skill file.
|
|
124
|
+
|
|
125
|
+
**Rules:**
|
|
126
|
+
- Always use `default="none"` on the content `<ViewTransition>` to prevent re-animation on revalidation or unrelated transitions.
|
|
127
|
+
- Use simple string props (not type maps) on Suspense `<ViewTransition>`s — Suspense resolves fire as separate transitions with no type, so type-keyed props won't match.
|
|
128
|
+
|
|
129
|
+
## Step 6: Add Shared Element Transitions
|
|
130
|
+
|
|
131
|
+
For every shared visual element identified in Step 1, add matching named `<ViewTransition>` wrappers on both the source and target views:
|
|
132
|
+
|
|
133
|
+
```jsx
|
|
134
|
+
// On the source view (e.g., list/grid page)
|
|
135
|
+
<ViewTransition name={`photo-${photo.id}`} share="morph" default="none">
|
|
136
|
+
<Image src={photo.src} ... />
|
|
137
|
+
</ViewTransition>
|
|
138
|
+
|
|
139
|
+
// On the target view (e.g., detail page) — same name
|
|
140
|
+
<ViewTransition name={`photo-${photo.id}`} share="morph">
|
|
141
|
+
<Image src={photo.src} ... />
|
|
142
|
+
</ViewTransition>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The `share="morph"` class uses the morph recipe from `css-recipes.md` (controlled duration + motion blur). For a simpler cross-fade, use `share="auto"` (browser default).
|
|
146
|
+
|
|
147
|
+
When list items contain shared elements, compose both patterns with two nested `<ViewTransition>` layers — see "Composing Shared Elements with List Identity" in `SKILL.md`.
|
|
148
|
+
|
|
149
|
+
**Rules:**
|
|
150
|
+
- Names must be globally unique — use prefixes like `photo-${id}`.
|
|
151
|
+
- Add `default="none"` on list-side shared elements to prevent per-item cross-fades on filter/search updates.
|
|
152
|
+
|
|
153
|
+
## Step 7: Verify Each Navigation Path
|
|
154
|
+
|
|
155
|
+
Walk through every row in the navigation map from Step 1 and confirm:
|
|
156
|
+
|
|
157
|
+
- Does the VT mount/unmount on this navigation, or does it stay mounted (same-route)?
|
|
158
|
+
- For named VTs: does a shared pair form? If not, does `enter`/`exit` provide a fallback?
|
|
159
|
+
- Does `default="none"` block an animation you actually want?
|
|
160
|
+
- Do persistent elements stay static (not sliding with page content)?
|
|
161
|
+
- Do Suspense reveals animate independently from directional navigations?
|
|
162
|
+
|
|
163
|
+
If any path produces no animation or competing animations, revisit the relevant step.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Common Mistakes
|
|
168
|
+
|
|
169
|
+
- **Bare `<ViewTransition>` without props** — without `default="none"`, it fires the browser's default cross-fade on every transition (every navigation, every Suspense resolve, every revalidation). Always set `default="none"` and explicitly enable only the triggers you want.
|
|
170
|
+
- **Directional `<ViewTransition>` in a layout** — layouts persist across navigations and never unmount/remount. `enter`/`exit` props won't fire on route changes. Place the outer type-keyed `<ViewTransition>` in each page component.
|
|
171
|
+
- **Fade-out exit with shared element morphs** — the page dissolving conflicts with the morph. Use a directional slide exit instead.
|
|
172
|
+
- **Writing custom animation CSS** — the recipes in `css-recipes.md` handle staggered timing, motion blur on morphs, and reduced motion. Copy them; don't reinvent them.
|
|
173
|
+
- **Missing `default: "none"` in type-keyed objects** — TypeScript requires a `default` key, and without it the fallback is `"auto"` which fires on every transition.
|
|
174
|
+
- **Type maps on Suspense reveals** — Suspense resolves fire as separate transitions with no type. Type-keyed props won't match — use simple string props instead.
|
|
175
|
+
- **Raw `viewTransitionName` CSS to trigger animations** — React only calls `document.startViewTransition` when `<ViewTransition>` components are in the tree. A bare `viewTransitionName` style is for isolating elements from a parent's snapshot, not for triggering animations.
|
|
176
|
+
- **`update` trigger for same-route navigations** — nested VTs inside the content steal the mutation from the parent, so `update` never fires on the outer VT. Use `key` + `name` + `share` instead.
|
|
177
|
+
- **Named VT in a reusable component** — if a component with a named VT is rendered in both a modal/popover *and* a page, both mount simultaneously and break the morph. Make the name conditional or move it to the specific consumer.
|
|
178
|
+
- **`router.back()` for back navigation** — `router.back()` triggers synchronous `popstate`, incompatible with view transitions. Use `router.push()` with an explicit URL.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
For Next.js-specific implementation steps (config flag, `transitionTypes` on `<Link>`, same-route dynamic segments), see `nextjs.md`.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# View Transitions in Next.js
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
`<ViewTransition>` works out of the box for `startTransition`/`Suspense` updates. To also animate `<Link>` navigations:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
// next.config.js
|
|
9
|
+
const nextConfig = {
|
|
10
|
+
experimental: { viewTransition: true },
|
|
11
|
+
};
|
|
12
|
+
module.exports = nextConfig;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This wraps every `<Link>` navigation in `document.startViewTransition`. Any VT with `default="auto"` fires on **every** link click — use `default="none"` to prevent competing animations.
|
|
16
|
+
|
|
17
|
+
Do **not** install `react@canary` — see SKILL.md "Availability" for details.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Next.js Implementation Additions
|
|
22
|
+
|
|
23
|
+
When following `implementation.md`, apply these additions:
|
|
24
|
+
|
|
25
|
+
**After Step 2:** Enable the experimental flag above.
|
|
26
|
+
|
|
27
|
+
**Step 4:** Use `transitionTypes` on `<Link>` — see "The `transitionTypes` Prop" section below for usage and availability.
|
|
28
|
+
|
|
29
|
+
**After Step 6:** For same-route dynamic segments (e.g., `/collection/[slug]`), use the `key` + `name` + `share` pattern — see Same-Route Dynamic Segment Transitions below.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Layout-Level ViewTransition
|
|
34
|
+
|
|
35
|
+
**Do NOT add a layout-level VT wrapping `{children}` if pages have their own VTs.** Nested VTs never fire enter/exit when inside a parent VT — page-level enter/exit will silently not work. Remove the layout VT entirely.
|
|
36
|
+
|
|
37
|
+
A bare `<ViewTransition>` in layout works only if pages have **no** VTs of their own.
|
|
38
|
+
|
|
39
|
+
**Layouts persist across navigations** — `enter`/`exit` only fire on initial mount, not on route changes. Don't use type-keyed maps in layouts.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## The `transitionTypes` Prop on `next/link`
|
|
44
|
+
|
|
45
|
+
No wrapper component needed, works in Server Components:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<Link href="/products/1" transitionTypes={['transition-to-detail']}>View Product</Link>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Replaces the manual pattern of `onNavigate` + `startTransition` + `addTransitionType` + `router.push()`. Reserve manual `startTransition` for non-link interactions (buttons, forms).
|
|
52
|
+
|
|
53
|
+
**Availability:** `transitionTypes` requires `experimental.viewTransition: true` and is available in Next.js 15+ canary builds and Next.js 16+. If unavailable, use `startTransition` + `addTransitionType` + `router.push()` (see Programmatic Navigation below). To check: `grep -r "transitionTypes" node_modules/next/dist/` — if no results, fall back to programmatic navigation.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Programmatic Navigation
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
'use client';
|
|
61
|
+
|
|
62
|
+
import { useRouter } from 'next/navigation';
|
|
63
|
+
import { startTransition, addTransitionType } from 'react';
|
|
64
|
+
|
|
65
|
+
function handleNavigate(href: string) {
|
|
66
|
+
const router = useRouter();
|
|
67
|
+
startTransition(() => {
|
|
68
|
+
addTransitionType('nav-forward');
|
|
69
|
+
router.push(href);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Server-Side Filtering with `router.replace`
|
|
77
|
+
|
|
78
|
+
For search/sort/filter that re-renders on the server (via URL params), use `startTransition` + `router.replace`. VTs activate because the state update is inside `startTransition`:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
'use client';
|
|
82
|
+
|
|
83
|
+
import { useRouter } from 'next/navigation';
|
|
84
|
+
import { startTransition } from 'react';
|
|
85
|
+
|
|
86
|
+
function handleSort(sort: string) {
|
|
87
|
+
const router = useRouter();
|
|
88
|
+
startTransition(() => {
|
|
89
|
+
router.replace(`?sort=${sort}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
List items wrapped in `<ViewTransition key={item.id}>` will animate reorder. This is the server-component alternative to the client-side `useDeferredValue` pattern in `patterns.md`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Two-Layer Pattern (Directional + Suspense)
|
|
99
|
+
|
|
100
|
+
Directional slides + Suspense reveals coexist because they fire at different moments. Place the directional VT in the **page component** (not layout):
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<ViewTransition
|
|
104
|
+
enter={{ "nav-forward": "slide-from-right", default: "none" }}
|
|
105
|
+
exit={{ "nav-forward": "slide-to-left", default: "none" }}
|
|
106
|
+
default="none"
|
|
107
|
+
>
|
|
108
|
+
<div>
|
|
109
|
+
<Suspense fallback={<ViewTransition exit="slide-down"><Skeleton /></ViewTransition>}>
|
|
110
|
+
<ViewTransition enter="slide-up" default="none"><Content /></ViewTransition>
|
|
111
|
+
</Suspense>
|
|
112
|
+
</div>
|
|
113
|
+
</ViewTransition>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## `loading.tsx` as Suspense Boundary
|
|
119
|
+
|
|
120
|
+
Next.js `loading.tsx` is an implicit `<Suspense>` boundary. Wrap the skeleton in `<ViewTransition exit="...">` in `loading.tsx`, and the content in `<ViewTransition enter="..." default="none">` in the page:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// loading.tsx
|
|
124
|
+
<ViewTransition exit="slide-down"><PhotoGridSkeleton /></ViewTransition>
|
|
125
|
+
|
|
126
|
+
// page.tsx
|
|
127
|
+
<ViewTransition enter="slide-up" default="none"><PhotoGrid photos={photos} /></ViewTransition>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Same rules as explicit `<Suspense>`: use simple string props (not type maps) since Suspense reveals fire without transition types.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Shared Elements Across Routes
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// List page
|
|
138
|
+
{products.map((product) => (
|
|
139
|
+
<Link key={product.id} href={`/products/${product.id}`} transitionTypes={['nav-forward']}>
|
|
140
|
+
<ViewTransition name={`product-${product.id}`}>
|
|
141
|
+
<Image src={product.image} alt={product.name} width={400} height={300} />
|
|
142
|
+
</ViewTransition>
|
|
143
|
+
</Link>
|
|
144
|
+
))}
|
|
145
|
+
|
|
146
|
+
// Detail page — same name
|
|
147
|
+
<ViewTransition name={`product-${product.id}`}>
|
|
148
|
+
<Image src={product.image} alt={product.name} width={800} height={600} />
|
|
149
|
+
</ViewTransition>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Same-Route Dynamic Segment Transitions
|
|
155
|
+
|
|
156
|
+
When navigating between dynamic segments of the same route (e.g., `/collection/[slug]`), the page stays mounted — enter/exit never fire. Use `key` + `name` + `share`:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<Suspense fallback={<Skeleton />}>
|
|
160
|
+
<ViewTransition key={slug} name={`collection-${slug}`} share="auto" default="none">
|
|
161
|
+
<Content slug={slug} />
|
|
162
|
+
</ViewTransition>
|
|
163
|
+
</Suspense>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- `key={slug}` forces unmount/remount on change
|
|
167
|
+
- `name` + `share="auto"` creates a shared element crossfade
|
|
168
|
+
- VT inside `<Suspense>` (without keying Suspense) keeps old content visible during loading
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Server Components
|
|
173
|
+
|
|
174
|
+
- `<ViewTransition>` works in both Server and Client Components
|
|
175
|
+
- `<Link transitionTypes>` works in Server Components — no `'use client'` needed
|
|
176
|
+
- `addTransitionType` and `startTransition` for programmatic nav require Client Components
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Patterns and Guidelines
|
|
2
|
+
|
|
3
|
+
## Searchable Grid with `useDeferredValue`
|
|
4
|
+
|
|
5
|
+
`useDeferredValue` makes filter updates a transition, activating `<ViewTransition>`:
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { useDeferredValue, useState, ViewTransition, Suspense } from 'react';
|
|
11
|
+
|
|
12
|
+
export default function SearchableGrid({ itemsPromise }) {
|
|
13
|
+
const [search, setSearch] = useState('');
|
|
14
|
+
const deferredSearch = useDeferredValue(search);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<input value={search} onChange={(e) => setSearch(e.currentTarget.value)} />
|
|
19
|
+
<ViewTransition>
|
|
20
|
+
<Suspense fallback={<GridSkeleton />}>
|
|
21
|
+
<ItemGrid itemsPromise={itemsPromise} search={deferredSearch} />
|
|
22
|
+
</Suspense>
|
|
23
|
+
</ViewTransition>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Per-item `<ViewTransition name={...}>` inside a deferred list triggers cross-fades on every keystroke. Fix with `default="none"`:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
{filteredItems.map(item => (
|
|
33
|
+
<ViewTransition key={item.id} name={`item-${item.id}`} share="morph" default="none">
|
|
34
|
+
<ItemCard item={item} />
|
|
35
|
+
</ViewTransition>
|
|
36
|
+
))}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Card Expand/Collapse with `startTransition`
|
|
40
|
+
|
|
41
|
+
Toggle between grid and detail view with shared element morph:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
'use client';
|
|
45
|
+
|
|
46
|
+
import { useState, useRef, startTransition, ViewTransition } from 'react';
|
|
47
|
+
|
|
48
|
+
export default function ItemGrid({ items }) {
|
|
49
|
+
const [expandedId, setExpandedId] = useState(null);
|
|
50
|
+
const scrollRef = useRef(0);
|
|
51
|
+
|
|
52
|
+
return expandedId ? (
|
|
53
|
+
<ViewTransition enter="slide-in" name={`item-${expandedId}`}>
|
|
54
|
+
<ItemDetail
|
|
55
|
+
item={items.find(i => i.id === expandedId)}
|
|
56
|
+
onClose={() => {
|
|
57
|
+
startTransition(() => {
|
|
58
|
+
setExpandedId(null);
|
|
59
|
+
setTimeout(() => window.scrollTo({ behavior: 'smooth', top: scrollRef.current }), 100);
|
|
60
|
+
});
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
</ViewTransition>
|
|
64
|
+
) : (
|
|
65
|
+
<div className="grid grid-cols-3 gap-4">
|
|
66
|
+
{items.map(item => (
|
|
67
|
+
<ViewTransition key={item.id} name={`item-${item.id}`}>
|
|
68
|
+
<ItemCard
|
|
69
|
+
item={item}
|
|
70
|
+
onSelect={() => {
|
|
71
|
+
scrollRef.current = window.scrollY;
|
|
72
|
+
startTransition(() => setExpandedId(item.id));
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
</ViewTransition>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Type-Safe Transition Helpers
|
|
83
|
+
|
|
84
|
+
Use `as const` arrays and derived types to prevent ID clashes:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
const transitionTypes = ['default', 'transition-to-detail', 'transition-to-list'] as const;
|
|
88
|
+
const animationTypes = ['auto', 'none', 'animate-slide-from-left', 'animate-slide-from-right'] as const;
|
|
89
|
+
|
|
90
|
+
type TransitionType = (typeof transitionTypes)[number];
|
|
91
|
+
type AnimationType = (typeof animationTypes)[number];
|
|
92
|
+
type TransitionMap = { default: AnimationType } & Partial<Record<Exclude<TransitionType, 'default'>, AnimationType>>;
|
|
93
|
+
|
|
94
|
+
export function HorizontalTransition({ children, enter, exit }: {
|
|
95
|
+
children: React.ReactNode;
|
|
96
|
+
enter: TransitionMap;
|
|
97
|
+
exit: TransitionMap;
|
|
98
|
+
}) {
|
|
99
|
+
return <ViewTransition enter={enter} exit={exit}>{children}</ViewTransition>;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Cross-Fade Without Remount
|
|
104
|
+
|
|
105
|
+
Omit `key` to trigger an update (cross-fade) instead of exit + enter. Avoids Suspense remount/refetch:
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
<ViewTransition>
|
|
109
|
+
<TabPanel tab={activeTab} />
|
|
110
|
+
</ViewTransition>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use `key` when content identity changes (state resets). Omit for cross-fades (tabs, panels, carousel).
|
|
114
|
+
|
|
115
|
+
## Isolate Elements from Parent Animations
|
|
116
|
+
|
|
117
|
+
### Persistent Layout Elements
|
|
118
|
+
|
|
119
|
+
Persistent elements (headers, navbars, sidebars) get captured in the page's transition snapshot. Fix with `viewTransitionName`:
|
|
120
|
+
|
|
121
|
+
```jsx
|
|
122
|
+
<nav style={{ viewTransitionName: "persistent-nav" }}>{/* ... */}</nav>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Then add the persistent element isolation CSS from `css-recipes.md`. For `backdrop-blur`/`backdrop-filter`, use the backdrop-blur workaround from `css-recipes.md`.
|
|
126
|
+
|
|
127
|
+
### Floating Elements
|
|
128
|
+
|
|
129
|
+
Give popovers/tooltips their own `viewTransitionName`:
|
|
130
|
+
|
|
131
|
+
```jsx
|
|
132
|
+
<SelectPopover style={{ viewTransitionName: 'popover' }}>{options}</SelectPopover>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Global fix: see persistent element isolation in `css-recipes.md`.
|
|
136
|
+
|
|
137
|
+
## Shared Controls Between Skeleton and Content
|
|
138
|
+
|
|
139
|
+
Give matching controls in fallback and content the same `viewTransitionName`:
|
|
140
|
+
|
|
141
|
+
```jsx
|
|
142
|
+
// Fallback
|
|
143
|
+
<input disabled placeholder="Search..." style={{ viewTransitionName: 'search-input' }} />
|
|
144
|
+
// Content
|
|
145
|
+
<input placeholder="Search..." style={{ viewTransitionName: 'search-input' }} />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Don't put manual `viewTransitionName` on the root DOM node inside `<ViewTransition>` — React's auto-generated name overrides it.
|
|
149
|
+
|
|
150
|
+
## Reusable Animated Collapse
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
function AnimatedCollapse({ open, children }) {
|
|
154
|
+
if (!open) return null;
|
|
155
|
+
return (
|
|
156
|
+
<ViewTransition enter="expand-in" exit="collapse-out">
|
|
157
|
+
{children}
|
|
158
|
+
</ViewTransition>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Usage: toggle with startTransition
|
|
163
|
+
<button onClick={() => startTransition(() => setOpen(o => !o))}>Toggle</button>
|
|
164
|
+
<AnimatedCollapse open={open}><SectionContent /></AnimatedCollapse>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Preserve State with Activity
|
|
168
|
+
|
|
169
|
+
```jsx
|
|
170
|
+
<Activity mode={isVisible ? 'visible' : 'hidden'}>
|
|
171
|
+
<ViewTransition enter="slide-in" exit="slide-out">
|
|
172
|
+
<Sidebar />
|
|
173
|
+
</ViewTransition>
|
|
174
|
+
</Activity>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Exclude Elements with `useOptimistic`
|
|
178
|
+
|
|
179
|
+
`useOptimistic` values update before the transition snapshot, excluding them from animation. Use for controls (labels); use committed state for animated content:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
const [sort, setSort] = useState('newest');
|
|
183
|
+
const [optimisticSort, setOptimisticSort] = useOptimistic(sort);
|
|
184
|
+
|
|
185
|
+
function cycleSort() {
|
|
186
|
+
const nextSort = getNextSort(optimisticSort);
|
|
187
|
+
startTransition(() => {
|
|
188
|
+
setOptimisticSort(nextSort); // before snapshot — no animation
|
|
189
|
+
setSort(nextSort); // between snapshots — animates
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
<button>Sort: {LABELS[optimisticSort]}</button>
|
|
194
|
+
{items.sort(comparators[sort]).map(item => (
|
|
195
|
+
<ViewTransition key={item.id}><ItemCard item={item} /></ViewTransition>
|
|
196
|
+
))}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## View Transition Events
|
|
202
|
+
|
|
203
|
+
Imperative control via `onEnter`, `onExit`, `onUpdate`, `onShare`. Always return a cleanup function. `onShare` takes precedence over `onEnter`/`onExit`.
|
|
204
|
+
|
|
205
|
+
```jsx
|
|
206
|
+
<ViewTransition
|
|
207
|
+
onEnter={(instance, types) => {
|
|
208
|
+
const anim = instance.new.animate(
|
|
209
|
+
[{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }],
|
|
210
|
+
{ duration: 300, easing: 'ease-out' }
|
|
211
|
+
);
|
|
212
|
+
return () => anim.cancel();
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<Component />
|
|
216
|
+
</ViewTransition>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The `instance` object: `instance.old`, `instance.new`, `instance.group`, `instance.imagePair`, `instance.name`.
|
|
220
|
+
|
|
221
|
+
The `types` array (second argument) lets you vary animation based on transition type.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Animation Timing
|
|
226
|
+
|
|
227
|
+
| Interaction | Duration |
|
|
228
|
+
|------------|----------|
|
|
229
|
+
| Direct toggle (expand/collapse) | 100–200ms |
|
|
230
|
+
| Route transition (slide) | 150–250ms |
|
|
231
|
+
| Suspense reveal (skeleton → content) | 200–400ms |
|
|
232
|
+
| Shared element morph | 300–500ms |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Troubleshooting
|
|
237
|
+
|
|
238
|
+
**VT not activating:** Ensure `<ViewTransition>` comes before any DOM node. Ensure state update is inside `startTransition`.
|
|
239
|
+
|
|
240
|
+
**"Two ViewTransition components with the same name":** Names must be globally unique. Use IDs: `name={`hero-${item.id}`}`.
|
|
241
|
+
|
|
242
|
+
**`router.back()` and browser back/forward skip animation:** Use `router.push()` with an explicit URL instead. See SKILL.md "router.back() and Browser Back Button."
|
|
243
|
+
|
|
244
|
+
**`flushSync` skips animations:** Use `startTransition` instead.
|
|
245
|
+
|
|
246
|
+
**Only updates animate (no enter/exit):** Without `<Suspense>`, React treats swaps as updates. Conditionally render the VT itself, or wrap in `<Suspense>`.
|
|
247
|
+
|
|
248
|
+
**Layout VT prevents page VTs from animating:** Nested VTs never fire enter/exit inside a parent VT. If your layout has a VT wrapping `{children}`, page-level enter/exit will silently not work. Remove the layout VT.
|
|
249
|
+
|
|
250
|
+
**List reorder not animating with `useOptimistic`:** Optimistic values resolve before snapshot. Use committed state for list order.
|
|
251
|
+
|
|
252
|
+
**TS error "Property 'default' is missing":** Type-keyed objects require a `default` key.
|
|
253
|
+
|
|
254
|
+
**Hash fragments cause scroll jumps:** Navigate without hash; scroll programmatically after navigation.
|
|
255
|
+
|
|
256
|
+
**Backdrop-blur flickers:** Use the backdrop-blur workaround from `css-recipes.md`.
|
|
257
|
+
|
|
258
|
+
**`border-radius` lost during transitions:** Apply `border-radius` directly to the captured element.
|
|
259
|
+
|
|
260
|
+
**Skeleton controls slide away:** Give matching controls the same `viewTransitionName`.
|
|
261
|
+
|
|
262
|
+
**Batching:** Multiple updates during animation are batched. A→B→C→D becomes B→D.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-skills-collection",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.37",
|
|
4
4
|
"description": "OpenCode CLI plugin that automatically downloads and keeps skills up to date.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@opencode-ai/sdk": "^1.15.5",
|
|
42
|
-
"@types/bun": "
|
|
42
|
+
"@types/bun": "*",
|
|
43
43
|
"@types/node": "^25.9.1",
|
|
44
44
|
"@types/strip-json-comments": "^3.0.0",
|
|
45
45
|
"typescript": "^6.0.2"
|