diff --git a/README.md b/README.md new file mode 100644 index 0000000..f059dcd --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# next-view-transitions + +Use [CSS View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) in Next.js App Router. + +[**Demo**](https://next-view-transitions.vercel.app). + +## Installation + +Use your favorite package manager to install the `next-view-transitions` package. For example: + +```bash +pnpm install next-view-transitions +``` + +## Usage + +Wrap your content with the `` component inside the layout file: + +```jsx +import { ViewTransitions } from 'next-view-transitions' + +export default function Layout({ children }) { + return ( + + + + {children} + + + + ) +} +``` + +Then, use the `` component for links that need to trigger a view transition: + +```jsx +import { Link } from 'next-view-transitions' + +export default function Component() { + return ( +
+ Go to /about +
+ ) +} +``` + +That's it! + +## License + +MIT. diff --git a/example/app/demo/page.js b/example/app/demo/page.js new file mode 100644 index 0000000..efa4b58 --- /dev/null +++ b/example/app/demo/page.js @@ -0,0 +1,13 @@ +import { Link } from 'next-view-transitions' + +export default function Page() { + return ( +
+

+ This is the Demo +

+

OK you just saw the demo :)

+ Go back → +
+ ) +} diff --git a/example/app/icon.svg b/example/app/icon.svg new file mode 100644 index 0000000..41477fc --- /dev/null +++ b/example/app/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/app/layout.js b/example/app/layout.js new file mode 100644 index 0000000..6c10439 --- /dev/null +++ b/example/app/layout.js @@ -0,0 +1,57 @@ +import { GeistSans } from 'geist/font/sans' +import { GeistMono } from 'geist/font/mono' + +import { ViewTransitions } from 'next-view-transitions' +import './style.css' + +export const metadata = { + title: 'Next.js View Transitions', + description: 'Using native CSS View Transitions API in Next.js App Router', + metadataBase: new URL('https://next-view-transitions.vercel.app'), +} + +export default function RootLayout({ children }) { + return ( + + + +

Next.js View Transitions

+

+ Use{' '} + + CSS View Transitions API + {' '} + in Next.js App Router.{' '} + + Source Code ↗ + +

+

+
{children}
+ + + +
+ ) +} diff --git a/example/app/opengraph-image.png b/example/app/opengraph-image.png new file mode 100644 index 0000000..3157582 Binary files /dev/null and b/example/app/opengraph-image.png differ diff --git a/example/app/page.js b/example/app/page.js new file mode 100644 index 0000000..425d60b --- /dev/null +++ b/example/app/page.js @@ -0,0 +1,64 @@ +import { Link } from 'next-view-transitions' + +export default function Page() { + return ( +
+

+ Demo +

+

+ Go to /demo → +

+

Installation

+

+ Use your favorite package manager to install the{' '} + next-view-transitions package. For example: +

+

+ pnpm install next-view-transitions +

+

Usage

+

+ Wrap your content with the <ViewTransitions>{' '} + component inside the layout file: +

+
+        
+          {`\
+import { ViewTransitions } from 'next-view-transitions'
+
+export default function Layout({ children }) {
+  return (
+    
+      
+        
+          {children}
+        
+      
+    
+  )
+}`}
+        
+      
+

+ Then, use the <Link> component for links that need to + trigger a view transition: +

+
+        
+          {`\
+import { Link } from 'next-view-transitions'
+
+export default function Component() {
+  return (
+    
+ Go to /about +
+ ) +}`} +
+
+

That's it!

+
+ ) +} diff --git a/example/app/style.css b/example/app/style.css new file mode 100644 index 0000000..b7023d0 --- /dev/null +++ b/example/app/style.css @@ -0,0 +1,133 @@ +/** CSS Reset from https://www.joshwcomeau.com/css/custom-css-reset <3 */ +*, +*::before, +*::after { + box-sizing: border-box; +} +* { + margin: 0; +} + +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +/** Styles */ +body { + font-family: var(--font-geist-sans), system-ui, -apple-system, + BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, + sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, + Noto Color Emoji; + font-feature-settings: 'liga', 'clig', 'calt'; + font-variant: common-ligatures contextual; + letter-spacing: -0.01em; + font-size: 15px; + color: #111; + background-color: #f9f9f9; + padding: 2rem; + line-height: 1.5; +} + +h1 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +h2 { + font-size: 1.15rem; + margin-top: 1.5rem; + margin-bottom: 1rem; +} + +p, +pre { + margin-bottom: 0.75rem; +} + +a { + color: #888; + text-decoration: underline solid currentColor; + text-underline-position: from-font; + text-decoration-thickness: from-font; +} + +a:hover { + color: #333; +} + +code { + font-family: var(--font-geist-mono), menlo, 'Courier New', Courier, monospace; + letter-spacing: 0; + background-color: #eee; + padding: 0.2em 0.3em; + border-radius: 3px; + font-size: 0.95em; + color: #565656; +} + +pre { + display: inline-block; + background-color: #eee; + padding: 0.3em 0.5em; + border-radius: 3px; + width: 600px; + max-width: 100%; + white-space: pre-wrap; +} + +pre code { + padding: 0; + background: none; + border-radius: 0; +} + +footer { + margin-top: 2rem; + padding-top: 1rem; + border-top: 1px solid #ddd; + width: 600px; + max-width: 100%; +} + +.demo { + view-transition-name: demo-title; + width: fit-content; +} + +.demo-box { + padding: 1rem; + border: 1px solid #ddd; + width: 600px; + max-width: 100%; +} + +.demo-box h2:first-child { + margin-top: 0; +} diff --git a/example/package.json b/example/package.json index 51cba4a..875fb47 100644 --- a/example/package.json +++ b/example/package.json @@ -2,5 +2,16 @@ "name": "next-view-transitions-example", "version": "0.0.0", "private": true, - "scripts": {} + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "geist": "^1.3.0", + "next": "^14.2.1", + "next-view-transitions": "workspace:*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f92c8b7..bb4e811 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,7 +27,23 @@ importers: specifier: ^5.4.5 version: 5.4.5 - example: {} + example: + dependencies: + geist: + specifier: ^1.3.0 + version: 1.3.0(next@14.2.1) + next: + specifier: ^14.2.1 + version: 14.2.1(react-dom@18.2.0)(react@18.2.0) + next-view-transitions: + specifier: workspace:* + version: link:.. + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) packages: @@ -70,7 +86,6 @@ packages: /@next/env@14.2.1: resolution: {integrity: sha512-qsHJle3GU3CmVx7pUoXcghX4sRN+vINkbLdH611T8ZlsP//grzqVW87BSUgOZeSAD4q7ZdZicdwNe/20U2janA==} - dev: true /@next/swc-darwin-arm64@14.2.1: resolution: {integrity: sha512-kGjnjcIJehEcd3rT/3NAATJQndAEELk0J9GmGMXHSC75TMnvpOhONcjNHbjtcWE5HUQnIHy5JVkatrnYm1QhVw==} @@ -78,7 +93,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@next/swc-darwin-x64@14.2.1: @@ -87,7 +101,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@next/swc-linux-arm64-gnu@14.2.1: @@ -96,7 +109,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@next/swc-linux-arm64-musl@14.2.1: @@ -105,7 +117,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@next/swc-linux-x64-gnu@14.2.1: @@ -114,7 +125,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@next/swc-linux-x64-musl@14.2.1: @@ -123,7 +133,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@next/swc-win32-arm64-msvc@14.2.1: @@ -132,7 +141,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@next/swc-win32-ia32-msvc@14.2.1: @@ -141,7 +149,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@next/swc-win32-x64-msvc@14.2.1: @@ -150,7 +157,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/plugin-commonjs@25.0.7(rollup@4.14.2): @@ -482,14 +488,12 @@ packages: /@swc/counter@0.1.3: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - dev: true /@swc/helpers@0.5.5: resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} dependencies: '@swc/counter': 0.1.3 tslib: 2.6.2 - dev: true /@swc/helpers@0.5.9: resolution: {integrity: sha512-XI76sLwMJoLjJTOK5RblBZkouOJG3X3hjxLCzLnyN1ifAiKQc6Hck3uvnU4Z/dV/Dyk36Ffj8FLvDLV2oWvKTw==} @@ -585,11 +589,9 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: true /caniuse-lite@1.0.30001609: resolution: {integrity: sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==} - dev: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -611,7 +613,6 @@ packages: /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: true /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -667,6 +668,14 @@ packages: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: true + /geist@1.3.0(next@14.2.1): + resolution: {integrity: sha512-IoGBfcqVEYB4bEwsfHd35jF4+X9LHRPYZymHL4YOltHSs9LJa24DYs1Z7rEMQ/lsEvaAIc61Y9aUxgcJaQ8lrg==} + peerDependencies: + next: '>=13.2.0 <15.0.0-0' + dependencies: + next: 14.2.1(react-dom@18.2.0)(react@18.2.0) + dev: false + /get-tsconfig@4.7.3: resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} dependencies: @@ -686,7 +695,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -738,14 +746,12 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true dependencies: js-tokens: 4.0.0 - dev: true /magic-string@0.30.9: resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} @@ -765,7 +771,6 @@ packages: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true /next@14.2.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SF3TJnKdH43PMkCcErLPv+x/DY1YCklslk3ZmwaVoyUfDgHKexuKlf9sEfBQ69w+ue8jQ3msLb+hSj1T19hGag==} @@ -807,7 +812,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: true /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -821,7 +825,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -835,7 +838,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: true /pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} @@ -850,14 +852,12 @@ packages: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.0 - dev: true /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: true /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -939,12 +939,10 @@ packages: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 - dev: true /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} - dev: true /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -954,7 +952,6 @@ packages: /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: true /styled-jsx@5.1.1(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} @@ -971,7 +968,6 @@ packages: dependencies: client-only: 0.0.1 react: 18.2.0 - dev: true /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} @@ -989,7 +985,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}