Skip to content

Commit 2352041

Browse files
committed
feat: view transitions
1 parent ca88889 commit 2352041

File tree

3 files changed

+169
-152
lines changed

3 files changed

+169
-152
lines changed

components/borrows/ListCardBorrow.tsx

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '@/components/ui/card'
2020
import Link from 'next/link'
2121
import { Route } from 'next'
22+
import { ViewTransition } from 'react'
2223

2324
export const ListCardBorrow: React.FC<
2425
React.PropsWithChildren<{ borrow: Borrow; idx: number }>
@@ -27,67 +28,66 @@ export const ListCardBorrow: React.FC<
2728
const isDue = isBorrowDue(borrow)
2829

2930
return (
30-
<Card
31-
key={borrow.id}
32-
className={cn('relative', status === 'lost' && 'bg-destructive/5')}
33-
>
34-
<CardHeader>
35-
<Link
36-
// FIXME
37-
href={`./borrows/${borrow.id}` as Route}
38-
className="flex justify-between items-start min-h-20"
39-
>
40-
<div>
41-
<CardTitle className="text-lg line-clamp-2">
42-
<abbr title={borrow.book.title} className="no-underline">
43-
{borrow.book.title}
44-
</abbr>
45-
</CardTitle>
46-
<CardDescription>
47-
<span className="text-muted-foreground/80 font-bold tracking-wider">
48-
#&nbsp;{idx.toString().padStart(4, '0')}
49-
</span>
50-
</CardDescription>
51-
</div>
52-
<Badge
53-
variant={
54-
getBorrowStatus(borrow) === 'overdue'
55-
? 'destructive'
56-
: getBorrowStatus(borrow) === 'active'
57-
? 'default'
58-
: 'secondary'
59-
}
60-
className="capitalize"
31+
<ViewTransition name={borrow.id} key={borrow.id}>
32+
<Card className={cn('relative', status === 'lost' && 'bg-destructive/5')}>
33+
<CardHeader>
34+
<Link
35+
// FIXME
36+
href={`./borrows/${borrow.id}` as Route}
37+
className="flex justify-between items-start min-h-20"
6138
>
62-
{getBorrowStatus(borrow)}
63-
</Badge>
64-
</Link>
65-
</CardHeader>
66-
<CardContent className="space-y-3">
67-
<div className="flex items-center gap-2 text-sm">
68-
<User className="size-4 text-muted-foreground" />
69-
<span>{borrow.subscription.user.name}</span>
70-
</div>
71-
<div className="flex items-center gap-2 text-sm">
72-
<LibraryIcon className="size-4 text-muted-foreground" />
73-
<span>{borrow.subscription.membership.library.name}</span>
74-
</div>
75-
<div className="flex items-center gap-2 text-sm">
76-
<Calendar className="size-4 text-muted-foreground" />
77-
<span>Borrowed: {formatDate(borrow.borrowed_at)}</span>
78-
</div>
79-
<div className="flex items-center gap-2 text-sm">
80-
{isDue ? (
81-
<CalendarX className="size-4 text-destructive" />
82-
) : (
83-
<CalendarClock className="size-4 text-muted-foreground" />
84-
)}
85-
<span className={cn(isDue && 'text-destructive')}>
86-
Due: {formatDate(borrow.due_at)}
87-
</span>
88-
</div>
89-
</CardContent>
90-
<CardFooter>{children}</CardFooter>
91-
</Card>
39+
<div>
40+
<CardTitle className="text-lg line-clamp-2">
41+
<abbr title={borrow.book.title} className="no-underline">
42+
{borrow.book.title}
43+
</abbr>
44+
</CardTitle>
45+
<CardDescription>
46+
<span className="text-muted-foreground/80 font-bold tracking-wider">
47+
#&nbsp;{idx.toString().padStart(4, '0')}
48+
</span>
49+
</CardDescription>
50+
</div>
51+
<Badge
52+
variant={
53+
getBorrowStatus(borrow) === 'overdue'
54+
? 'destructive'
55+
: getBorrowStatus(borrow) === 'active'
56+
? 'default'
57+
: 'secondary'
58+
}
59+
className="capitalize"
60+
>
61+
{getBorrowStatus(borrow)}
62+
</Badge>
63+
</Link>
64+
</CardHeader>
65+
<CardContent className="space-y-3">
66+
<div className="flex items-center gap-2 text-sm">
67+
<User className="size-4 text-muted-foreground" />
68+
<span>{borrow.subscription.user.name}</span>
69+
</div>
70+
<div className="flex items-center gap-2 text-sm">
71+
<LibraryIcon className="size-4 text-muted-foreground" />
72+
<span>{borrow.subscription.membership.library.name}</span>
73+
</div>
74+
<div className="flex items-center gap-2 text-sm">
75+
<Calendar className="size-4 text-muted-foreground" />
76+
<span>Borrowed: {formatDate(borrow.borrowed_at)}</span>
77+
</div>
78+
<div className="flex items-center gap-2 text-sm">
79+
{isDue ? (
80+
<CalendarX className="size-4 text-destructive" />
81+
) : (
82+
<CalendarClock className="size-4 text-muted-foreground" />
83+
)}
84+
<span className={cn(isDue && 'text-destructive')}>
85+
Due: {formatDate(borrow.due_at)}
86+
</span>
87+
</div>
88+
</CardContent>
89+
<CardFooter>{children}</CardFooter>
90+
</Card>
91+
</ViewTransition>
9292
)
9393
}

components/collections/FormManageCollectionBooks.tsx

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import {
88
CardHeader,
99
CardTitle,
1010
} from '@/components/ui/card'
11-
import { useEffect, useState, useRef } from 'react'
11+
import {
12+
useEffect,
13+
useState,
14+
useRef,
15+
ViewTransition,
16+
useTransition,
17+
} from 'react'
1218
import { getListBooks } from '@/lib/api/book'
1319
import { toast } from 'sonner'
1420
import { Input } from '../ui/input'
@@ -23,9 +29,16 @@ export const FormManageCollectionBooks: React.FC<{
2329
onSubmitAction: (v: string[]) => Promise<string>
2430
}> = ({ initialBooks, libraryID, initialBookIDs, onSubmitAction }) => {
2531
const [books, setBooks] = useState<Book[]>([])
26-
const [selectedBooks, setSelectedBooks] = useState<Book[]>(initialBooks)
32+
const [, startTransition] = useTransition()
33+
const [selectedBooks, _setSelectedBooks] = useState<Book[]>(initialBooks)
2734
const selectedBookIDs = selectedBooks.map((b) => b.id)
2835

36+
function setSelectedBooks(books: Parameters<typeof _setSelectedBooks>[0]) {
37+
startTransition(() => {
38+
_setSelectedBooks(books)
39+
})
40+
}
41+
2942
const [bookQ, setBookQ] = useState<
3043
Pick<Parameters<typeof getListBooks>[0], 'title' | 'library_id'>
3144
>({
@@ -106,46 +119,47 @@ export const FormManageCollectionBooks: React.FC<{
106119
{selectedBooks
107120
.concat(books.filter((book) => !selectedBookIDs.includes(book.id)))
108121
.map((book) => (
109-
<Card
110-
key={book.id}
111-
className={`cursor-pointer transition-all duration-200 hover:shadow-md ${
112-
selectedBookIDs.includes(book.id)
113-
? 'ring-1 ring-primary bg-primary/5'
114-
: ''
115-
}`}
116-
onClick={() => toggleBookSelection(book)}
117-
>
118-
<CardHeader className="pb-3">
119-
<div className="relative mx-auto mb-4 flex justify-center">
120-
<Image
121-
src={book.cover ?? '/book-placeholder.svg'}
122-
alt={`${book.title} cover`}
123-
width={100}
124-
height={150}
125-
/>
126-
{selectedBookIDs.includes(book.id) && (
127-
<div className="absolute -top-2 -right-2 w-6 h-6 bg-primary rounded-full flex items-center justify-center">
128-
<Check className="h-3 w-3 text-white" />
122+
<ViewTransition name={book.id} key={book.id}>
123+
<Card
124+
className={`cursor-pointer transition-all duration-200 hover:shadow-md ${
125+
selectedBookIDs.includes(book.id)
126+
? 'ring-1 ring-primary bg-primary/5'
127+
: ''
128+
}`}
129+
onClick={() => toggleBookSelection(book)}
130+
>
131+
<CardHeader className="pb-3">
132+
<div className="relative mx-auto mb-4 flex justify-center">
133+
<Image
134+
src={book.cover ?? '/book-placeholder.svg'}
135+
alt={`${book.title} cover`}
136+
width={100}
137+
height={150}
138+
/>
139+
{selectedBookIDs.includes(book.id) && (
140+
<div className="absolute -top-2 -right-2 w-6 h-6 bg-primary rounded-full flex items-center justify-center">
141+
<Check className="h-3 w-3 text-white" />
142+
</div>
143+
)}
144+
</div>
145+
<CardTitle className="text-base line-clamp-2">
146+
{book.title}
147+
</CardTitle>
148+
<CardDescription className="line-clamp-1">
149+
{book.author}
150+
</CardDescription>
151+
</CardHeader>
152+
<CardContent className="pt-0">
153+
<div className="space-y-2">
154+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
155+
<span>{book.year}</span>
156+
<span></span>
157+
<span>{book.code}</span>
129158
</div>
130-
)}
131-
</div>
132-
<CardTitle className="text-base line-clamp-2">
133-
{book.title}
134-
</CardTitle>
135-
<CardDescription className="line-clamp-1">
136-
{book.author}
137-
</CardDescription>
138-
</CardHeader>
139-
<CardContent className="pt-0">
140-
<div className="space-y-2">
141-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
142-
<span>{book.year}</span>
143-
<span></span>
144-
<span>{book.code}</span>
145159
</div>
146-
</div>
147-
</CardContent>
148-
</Card>
160+
</CardContent>
161+
</Card>
162+
</ViewTransition>
149163
))}
150164
</div>
151165
</div>

components/subscriptions/ListCardSubscription.tsx

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,66 +23,69 @@ import {
2323
} from '@/components/ui/card'
2424
import { Subscription } from '@/lib/types/subscription'
2525
import clsx from 'clsx'
26+
import { ViewTransition } from 'react'
2627

2728
export const ListCardSubscription: React.FC<{ subscription: Subscription }> = ({
2829
subscription,
2930
}) => {
3031
const isActive = isSubscriptionActive(subscription)
3132

3233
return (
33-
<Card key={subscription.id}>
34-
<CardHeader>
35-
<div className="flex justify-between items-start min-h-20">
36-
<div>
37-
<CardTitle className="text-lg line-clamp-2">
38-
{subscription.membership.name}
39-
</CardTitle>
40-
<CardDescription>
41-
<div className="flex items-center gap-2">
42-
<User className="size-4 text-muted-foreground" />
43-
{subscription.user.name}
44-
</div>
45-
</CardDescription>
34+
<ViewTransition name={subscription.id} key={subscription.id}>
35+
<Card>
36+
<CardHeader>
37+
<div className="flex justify-between items-start min-h-20">
38+
<div>
39+
<CardTitle className="text-lg line-clamp-2">
40+
{subscription.membership.name}
41+
</CardTitle>
42+
<CardDescription>
43+
<div className="flex items-center gap-2">
44+
<User className="size-4 text-muted-foreground" />
45+
{subscription.user.name}
46+
</div>
47+
</CardDescription>
48+
</div>
49+
<Badge
50+
variant={
51+
isSubscriptionActive(subscription) ? 'default' : 'secondary'
52+
}
53+
className="capitalize"
54+
>
55+
{getSubscriptionStatus(subscription)}
56+
</Badge>
4657
</div>
47-
<Badge
48-
variant={
49-
isSubscriptionActive(subscription) ? 'default' : 'secondary'
50-
}
51-
className="capitalize"
52-
>
53-
{getSubscriptionStatus(subscription)}
54-
</Badge>
55-
</div>
56-
</CardHeader>
57-
<CardContent className="space-y-3">
58-
<div className="flex items-center gap-2 text-sm">
59-
<LibraryIcon className="size-4 text-muted-foreground" />
60-
<span>{subscription.membership.library.name}</span>
61-
</div>
62-
<div className="flex items-center gap-2 text-sm">
63-
<Calendar className="size-4 text-muted-foreground" />
64-
<span>Purchased: {formatDate(subscription.created_at)}</span>
65-
</div>
66-
<div className="flex items-center gap-2 text-sm">
67-
{isActive ? (
68-
<CalendarClock className="size-4 text-primary" />
69-
) : (
70-
<CalendarX className="size-4 text-muted-foreground" />
71-
)}
72-
<span className={clsx({ 'text-primary': isActive })}>
73-
Expire: {formatDate(subscription.expires_at)}
74-
</span>
75-
</div>
76-
<div className="flex items-center gap-2 text-sm">
77-
<CircleDollarSign className="size-4 text-muted-foreground" />
78-
<span>Amount: {subscription.amount ?? '-'} Pts</span>
79-
</div>
80-
</CardContent>
81-
<CardFooter>
82-
{/* <BtnReturnBook variant="outline" className="w-full" borrow={borrow}>
58+
</CardHeader>
59+
<CardContent className="space-y-3">
60+
<div className="flex items-center gap-2 text-sm">
61+
<LibraryIcon className="size-4 text-muted-foreground" />
62+
<span>{subscription.membership.library.name}</span>
63+
</div>
64+
<div className="flex items-center gap-2 text-sm">
65+
<Calendar className="size-4 text-muted-foreground" />
66+
<span>Purchased: {formatDate(subscription.created_at)}</span>
67+
</div>
68+
<div className="flex items-center gap-2 text-sm">
69+
{isActive ? (
70+
<CalendarClock className="size-4 text-primary" />
71+
) : (
72+
<CalendarX className="size-4 text-muted-foreground" />
73+
)}
74+
<span className={clsx({ 'text-primary': isActive })}>
75+
Expire: {formatDate(subscription.expires_at)}
76+
</span>
77+
</div>
78+
<div className="flex items-center gap-2 text-sm">
79+
<CircleDollarSign className="size-4 text-muted-foreground" />
80+
<span>Amount: {subscription.amount ?? '-'} Pts</span>
81+
</div>
82+
</CardContent>
83+
<CardFooter>
84+
{/* <BtnReturnBook variant="outline" className="w-full" borrow={borrow}>
8385
Return Book
8486
</BtnReturnBook> */}
85-
</CardFooter>
86-
</Card>
87+
</CardFooter>
88+
</Card>
89+
</ViewTransition>
8790
)
8891
}

0 commit comments

Comments
 (0)