Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canary #1991

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

Canary #1991

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions apps/live-next/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,112 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

/* @view-transition {
navigation: auto;
} */

@keyframes count-exit {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-100%);
}
}

@keyframes count-enter {
from {
opacity: 0;
transform: translateY(100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}

/* Put new transition name on each card to morph each */
.card-1 {
view-transition-name: card-1;
}

.card-2 {
view-transition-name: card-2;
}

.card-3 {
view-transition-name: card-3;
}

.card-4 {
view-transition-name: card-4;
}

/* Etc. */

.cards {
padding: 0;
display: flex;
justify-content: center;
width: 100%;
gap: 2rem;
max-width: 1000px;
}

.card {
width: 100%;
aspect-ratio: 2/3;
display: block;
position: relative;
border-radius: 1rem;
max-width: 220px;
}

.card-1 {
background-color: tan;
}

.card-2 {
background-color: khaki;
}

.card-3 {
background-color: thistle;
}

.card-4 {
background-color: wheat;
}

.delete-btn {
position: absolute;
bottom: -0.75rem;
right: -0.75rem;
width: 3rem;
height: 3rem;
padding: 0.5rem;
border: 4px solid;
border-radius: 100%;
background: steelblue;
color: white;

& img {
filter: invert();
}
}

.sr-only {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'

import {useEffect, useState} from 'react'
import {flushSync} from 'react-dom'

export default function NextLiveTransitions(props: {
children: React.ReactNode
selector?: unknown
}) {
const [[children, selector], setChildren] = useState(() => [props.children, props.selector])

useEffect(() => {
if (props.selector !== undefined) {
console.log('we have a selector')
// @ts-ignore
if (props.selector === selector) {
console.log('selector has not changed, skipping')
return
} else {
console.log('selector has changed', props.selector, selector)
}
}
if (props.children !== children) {
console.log('children have changed', props.children, children)
const transition = document.startViewTransition(() => {
flushSync(() => setChildren([props.children, props.selector]))
})
console.log({transition})
return () => {
transition.skipTransition()
}
} else {
console.log('children have not changed')
}
}, [children, props.children, props.selector, selector])

return <>{children}</>
}
Empty file.
118 changes: 118 additions & 0 deletions apps/live-next/app/live-view-transitions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {cookies} from 'next/headers'
import NextLiveTransitions from './NextLiveTransitions'

export default async function LiveViewTransitionsPage() {
const cookieStore = await cookies()
const count = cookieStore.get('count')?.value || '0'
const count2 = cookieStore.get('count2')?.value || '0'
async function getVisibleCards() {
'use server'
const cookieStore = await cookies()
return new Set((cookieStore.get('cards')?.value ?? '1,2,3,4').split(',').filter(Boolean))
}
const visibleCards = await getVisibleCards()
const cards = [
{id: '1', color: 'tan'},
{id: '2', color: 'khaki'},
{id: '3', color: 'thistle'},
{id: '4', color: 'wheat'},
]

async function handleIncrement() {
'use server'
const cookieStore = await cookies()
cookieStore.set('count', (parseInt(cookieStore.get('count')?.value || '0') + 1).toString())
}
async function handleIncrement2() {
'use server'
const cookieStore = await cookies()
cookieStore.set('count2', (parseInt(cookieStore.get('count2')?.value || '0') + 1).toString())
}
async function handleDeleteCard(id: string) {
'use server'
const visibleCards = await getVisibleCards()
const cookieStore = await cookies()
visibleCards.delete(id)
cookieStore.set('cards', Array.from(visibleCards).join(','))
}
async function handleResetCards() {
'use server'
const cookieStore = await cookies()
cookieStore.set('cards', '1,2,3,4')
}

return (
<>
<NextLiveTransitions selector={{count}}>
<h1>
Count (transition): <span style={{viewTransitionName: 'count'}}>{count}</span>
</h1>
<h1>Count (real): {count}</h1>
<h1>Count2: {count2}</h1>
</NextLiveTransitions>
<h1>Count (real): {count}</h1>
<form action={handleIncrement}>
<button type="submit">Increment</button>
</form>
<form action={handleIncrement2}>
<button type="submit">Increment 2</button>
</form>
<h1>Count2: {count2}</h1>
<section
style={{
display: 'grid',
height: '90dvh',
placeItems: 'center',
padding: '2rem',
}}
>
<NextLiveTransitions>
<code style={{viewTransitionName: 'fit-content-test', width: 'fit-content'}}>
{JSON.stringify({visibleCards: Array.from(visibleCards)})}
</code>
<span style={{viewTransitionName: 'vis-label'}}>visibleCards:</span>
<code>
<span style={{viewTransitionName: 'v-card-start'}}>{'['}</span>
{Array.from(visibleCards).map((id, i) => (
<span key={id} style={{viewTransitionName: `v-card-s-${id}`}}>
{JSON.stringify(id)}
{visibleCards.size - 1 > i ? ',' : null}
</span>
))}
<span style={{viewTransitionName: 'v-card-end'}}>{']'}</span>
</code>

{visibleCards.size === 0 && (
<form action={handleResetCards}>
<button className="rounded-md bg-theme-inverse px-4 py-2 text-sm font-semibold text-theme-inverse transition ease-in-out focus:outline-none focus:ring-2 focus:ring-opacity-50 disabled:cursor-not-allowed disabled:opacity-50">
Reset cards
</button>
</form>
)}
<ul className="cards">
{cards.map(
({id, color}) =>
visibleCards.has(id) && (
<li
key={id}
className="card"
style={{backgroundColor: color, viewTransitionName: `cards-${id}`}}
>
<form action={handleDeleteCard.bind(null, id)}>
<button type="submit" className="delete-btn">
<img
src="https://upload.wikimedia.org/wikipedia/commons/7/7d/Trash_font_awesome.svg"
alt="close button"
/>
<span className="sr-only">Delete</span>
</button>
</form>
</li>
),
)}
</ul>
</NextLiveTransitions>
</section>
</>
)
}
79 changes: 46 additions & 33 deletions apps/live-next/app/more-stories.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
import type {MoreStoriesQueryResult} from '@/sanity.types'
import {SanityLiveStream} from '@/sanity/lib/live'
import {moreStoriesQuery} from '@/sanity/lib/queries'
import {TransitionLayoutShift} from 'next-live-transitions'
import Link from 'next/link'
import Avatar from './avatar'
import CoverImage from './cover-image'
import DateComponent from './date'

export default async function MoreStories(params: {skip: string; limit: number}) {
// export default async function MoreStories(params: {skip: string; limit: number}) {
export default async function MoreStories({data}: {data: MoreStoriesQueryResult}) {
// const {data} = await sanityFetch({query: moreStoriesQuery, params})

return (
<SanityLiveStream query={moreStoriesQuery} params={params}>
{async ({data}: {data: MoreStoriesQueryResult}) => {
'use server'
// return (
// <SanityLiveStream query={moreStoriesQuery} params={params}>
// {async ({data}: {data: MoreStoriesQueryResult}) => {
// 'use server'

return (
<div className="mb-32 grid grid-cols-1 gap-y-20 md:grid-cols-2 md:gap-x-16 md:gap-y-32 lg:gap-x-32">
{data?.map((post) => {
const {_id, title, slug, coverImage, excerpt, author} = post
return (
<article key={_id}>
<Link href={`/posts/${slug}`} className="group mb-5 block">
<CoverImage image={coverImage} priority={false} />
</Link>
<h3 className="mb-3 text-balance text-3xl leading-snug">
<Link href={`/posts/${slug}`} className="hover:underline">
{title}
</Link>
</h3>
<div className="mb-4 text-lg">
<DateComponent dateString={post.date} />
</div>
{excerpt && <p className="mb-4 text-pretty text-lg leading-relaxed">{excerpt}</p>}
{author && <Avatar name={author.name} picture={author.picture} />}
</article>
)
})}
</div>
)
}}
</SanityLiveStream>
return (
<div className="mb-32 grid grid-cols-1 gap-y-20 md:grid-cols-2 md:gap-x-16 md:gap-y-32 lg:gap-x-32">
<TransitionLayoutShift>
{data?.map((post) => {
const {_id, title, slug, coverImage, excerpt, author} = post
return (
<article key={_id} style={{viewTransitionName: `post-${_id}`}}>
<Link
href={`/posts/${slug}`}
className="group mb-5 block"
style={{viewTransitionName: `post-${_id}-cover-image`}}
>
<CoverImage image={coverImage} priority={false} />
</Link>
<h3
className="mb-3 text-balance text-3xl leading-snug"
style={{viewTransitionName: `post-${_id}-heading`}}
>
<Link href={`/posts/${slug}`} className="hover:underline">
{title}
</Link>
</h3>
<div className="mb-4 text-lg" style={{viewTransitionName: `post-${_id}-date`}}>
<DateComponent dateString={post.date} />
</div>
<div style={{viewTransitionName: `post-${_id}-excerpt`}}>
{excerpt && <p className="mb-4 text-pretty text-lg leading-relaxed">{excerpt}</p>}
</div>
<div style={{viewTransitionName: `post-${_id}-author`}}>
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
</article>
)
})}
</TransitionLayoutShift>
</div>
)
// }}
// </SanityLiveStream>
// )
}
Loading