feat: books page
This commit is contained in:
parent
81229144cc
commit
2c1ae9962c
13
README.md
13
README.md
@ -1,8 +1,15 @@
|
|||||||
# blank
|
|
||||||
|
|
||||||
blank
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
- **Database**: mongodb
|
- **Database**: postgres
|
||||||
- **Storage Adapter**: localDisk
|
- **Storage Adapter**: localDisk
|
||||||
|
|
||||||
|
## external resources
|
||||||
|
|
||||||
|
https://openlibrary.org/dev/docs/api
|
||||||
|
|
||||||
|
## maybe use
|
||||||
|
https://www.reddit.com/r/nextjs/comments/1ej1y32/share_cool_shadcnstyle_components_libraries_you/
|
||||||
|
https://motion-primitives.com/docs
|
||||||
|
27
package-lock.json
generated
27
package-lock.json
generated
@ -25,6 +25,7 @@
|
|||||||
"framer-motion": "^12.7.4",
|
"framer-motion": "^12.7.4",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"lucide-react": "^0.488.0",
|
"lucide-react": "^0.488.0",
|
||||||
|
"motion": "^12.7.4",
|
||||||
"next": "15.2.3",
|
"next": "15.2.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"payload": "3.31.0",
|
"payload": "3.31.0",
|
||||||
@ -10787,6 +10788,32 @@
|
|||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.7.4.tgz",
|
||||||
|
"integrity": "sha512-MBGrMbYageHw4iZJn+pGTr7abq5n53jCxYkhFC1It3vYukQPRWg5zij46MnwYGpLR8KG465MLHSASXot9edYOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.7.4",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/motion-dom": {
|
"node_modules/motion-dom": {
|
||||||
"version": "12.7.4",
|
"version": "12.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.4.tgz",
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"framer-motion": "^12.7.4",
|
"framer-motion": "^12.7.4",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"lucide-react": "^0.488.0",
|
"lucide-react": "^0.488.0",
|
||||||
|
"motion": "^12.7.4",
|
||||||
"next": "15.2.3",
|
"next": "15.2.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"payload": "3.31.0",
|
"payload": "3.31.0",
|
||||||
|
0
src/app/(frontend)/[slug]/page.client.tsx
Normal file
0
src/app/(frontend)/[slug]/page.client.tsx
Normal file
0
src/app/(frontend)/[slug]/page.tsx
Normal file
0
src/app/(frontend)/[slug]/page.tsx
Normal file
23
src/app/(frontend)/books/page.client.tsx
Normal file
23
src/app/(frontend)/books/page.client.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import BookList from '@/components/BookList'
|
||||||
|
import { Book } from '@/payload-types'
|
||||||
|
import { PaginatedDocs } from 'payload'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
initialBooks: PaginatedDocs<Book>
|
||||||
|
}
|
||||||
|
const BooksPageClient = (props: Props) => {
|
||||||
|
const initialBooks = useMemo(() => {
|
||||||
|
return props.initialBooks
|
||||||
|
}, [props.initialBooks])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BookList books={initialBooks} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BooksPageClient
|
44
src/app/(frontend)/books/page.tsx
Normal file
44
src/app/(frontend)/books/page.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Book } from '@/payload-types'
|
||||||
|
import { getPayload, PaginatedDocs } from 'payload'
|
||||||
|
import configPromise from '@payload-config'
|
||||||
|
import BooksPageClient from './page.client'
|
||||||
|
|
||||||
|
type Params = Promise<{ slug: string }>
|
||||||
|
type SearchParams = Promise<{ [key: string]: string | undefined }>
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Params
|
||||||
|
searchParams: SearchParams
|
||||||
|
}
|
||||||
|
const BooksPage = async (props: Props) => {
|
||||||
|
const searchParams = await props.searchParams
|
||||||
|
|
||||||
|
const defaultLimit = 25
|
||||||
|
const defaultPage = 1
|
||||||
|
|
||||||
|
const pageFromUrl = parseInt(searchParams?.page || '', 10)
|
||||||
|
const limitFromUrl = parseInt(searchParams?.limit || '', 10)
|
||||||
|
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
const initialBooks = (await payload.find({
|
||||||
|
collection: 'books',
|
||||||
|
depth: 2,
|
||||||
|
page: !Number.isNaN(pageFromUrl) ? pageFromUrl : defaultPage,
|
||||||
|
limit: !Number.isNaN(limitFromUrl) ? limitFromUrl : defaultLimit,
|
||||||
|
overrideAccess: false,
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
authors: true,
|
||||||
|
publication: true,
|
||||||
|
lcc: true,
|
||||||
|
genre: true,
|
||||||
|
isbn: true,
|
||||||
|
copies: true,
|
||||||
|
},
|
||||||
|
})) as PaginatedDocs<Book>
|
||||||
|
|
||||||
|
return <BooksPageClient initialBooks={initialBooks} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BooksPage
|
@ -1,5 +1,4 @@
|
|||||||
import { headers as getHeaders } from 'next/headers.js'
|
import { headers as getHeaders } from 'next/headers.js'
|
||||||
import Image from 'next/image'
|
|
||||||
import { getPayload, PaginatedDocs } from 'payload'
|
import { getPayload, PaginatedDocs } from 'payload'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@ -8,6 +7,7 @@ import config from '@/payload.config'
|
|||||||
import BookList from '@/components/BookList'
|
import BookList from '@/components/BookList'
|
||||||
import { Book } from '@/payload-types'
|
import { Book } from '@/payload-types'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
import { TextShimmer } from '@/components/ui/text-shimmer'
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const headers = await getHeaders()
|
const headers = await getHeaders()
|
||||||
@ -33,19 +33,21 @@ export default async function HomePage() {
|
|||||||
},
|
},
|
||||||
})) as PaginatedDocs<Book>
|
})) as PaginatedDocs<Book>
|
||||||
|
|
||||||
//let safariaQuote = ''
|
|
||||||
//const randomSefariaQuoteRequest = await fetch('https://www.sefaria.org/api/texts/random?categories=english')
|
|
||||||
//if (randomSefariaQuoteRequest.body) safariaQuote = await randomSefariaQuoteRequest.json()
|
|
||||||
//console.log(safariaQuote)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home">
|
<div className="home">
|
||||||
<div className="py-24 sm:py-32">
|
<div className="py-24 sm:py-32">
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
<div className="mx-auto max-w-2xl lg:mx-0">
|
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||||
<p className="text-base/7 font-semibold text-indigo-600">Get the help you need</p>
|
<p className="text-base/7 font-semibold text-indigo-600">Get the help you need</p>
|
||||||
|
|
||||||
<h2 className="mt-2 text-5xl font-semibold tracking-tight text-foreground sm:text-7xl">
|
<h2 className="mt-2 text-5xl font-semibold tracking-tight text-foreground sm:text-7xl">
|
||||||
Welcome {user && <small>{`user.firstName`}</small>}
|
<TextShimmer
|
||||||
|
duration={1.2}
|
||||||
|
className="[--base-color:var(--color-indigo-600)] [--base-gradient-color:var(--color-blue-200)] dark:[--base-color:var(--color-blue-700)] dark:[--base-gradient-color:var(--color-blue-400)]"
|
||||||
|
>
|
||||||
|
Welcome
|
||||||
|
</TextShimmer>
|
||||||
|
{user && <small>{`user.firstName`}</small>}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-8 text-lg font-medium text-pretty text-muted-foreground sm:text-xl/8">
|
<p className="mt-8 text-lg font-medium text-pretty text-muted-foreground sm:text-xl/8">
|
||||||
Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat
|
Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat
|
||||||
|
@ -121,7 +121,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html[data-theme='dark'],
|
html[data-theme='dark'],
|
||||||
html[data-theme='light'] {
|
html[data-theme='light'] {
|
||||||
opacity: initial;
|
opacity: initial;
|
||||||
|
@ -47,17 +47,17 @@ export default function BookList(props: Props) {
|
|||||||
const { docs, hasNextPage, hasPrevPage, limit, totalPages, page, prevPage, nextPage, totalDocs } =
|
const { docs, hasNextPage, hasPrevPage, limit, totalPages, page, prevPage, nextPage, totalDocs } =
|
||||||
props.books
|
props.books
|
||||||
|
|
||||||
console.log(props.books)
|
|
||||||
|
|
||||||
const currentPage = page || 0
|
const currentPage = page || 0
|
||||||
const books = docs
|
const books = docs
|
||||||
|
|
||||||
|
console.log(props)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="user-repositories">
|
<section id="user-repositories">
|
||||||
<ul role="list" className="divide-y divide-gray-800">
|
<ul role="list" className="divide-y divide-gray-800">
|
||||||
{books?.map((b) => (
|
{books?.map((b) => (
|
||||||
<li key={b.lcc + (b.title || '')}>
|
<li key={b.lcc + (b.title || '')}>
|
||||||
<Link href={`/book/${b.id}`} className="flex justify-between gap-x-6 py-5">
|
<Link href={`/books/${b.id}`} className="flex justify-between gap-x-6 py-5">
|
||||||
<div className="flex max-w-9/12 min-w-0 gap-x-4">
|
<div className="flex max-w-9/12 min-w-0 gap-x-4">
|
||||||
<Avatar
|
<Avatar
|
||||||
square
|
square
|
||||||
@ -101,7 +101,7 @@ export default function BookList(props: Props) {
|
|||||||
<Pagination>
|
<Pagination>
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationPrevious isActive={!hasPrevPage} href="#" />
|
<PaginationPrevious href={prevPage ? `/books?limit=${limit}&page=${prevPage}` : ''} />
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
|
||||||
{hasPrevPage && currentPage > totalPages / 2 && (
|
{hasPrevPage && currentPage > totalPages / 2 && (
|
||||||
@ -112,7 +112,9 @@ export default function BookList(props: Props) {
|
|||||||
|
|
||||||
{prevPage && (
|
{prevPage && (
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink href="#">{prevPage}</PaginationLink>
|
<PaginationLink href={`/books?limit=${limit}&page=${prevPage}`}>
|
||||||
|
{prevPage}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -124,7 +126,9 @@ export default function BookList(props: Props) {
|
|||||||
|
|
||||||
{nextPage && (
|
{nextPage && (
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink href="#">{nextPage}</PaginationLink>
|
<PaginationLink href={`/books?limit=${limit}&page=${nextPage}`}>
|
||||||
|
{nextPage}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -135,7 +139,7 @@ export default function BookList(props: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext isActive={hasNextPage} href="#" />
|
<PaginationNext href={nextPage ? `/books?limit=${limit}&page=${nextPage}` : ''} />
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
</Pagination>
|
</Pagination>
|
||||||
@ -143,7 +147,7 @@ export default function BookList(props: Props) {
|
|||||||
<p className="flex justify-center mt-2 gap-2 mx-auto text-muted-foreground">
|
<p className="flex justify-center mt-2 gap-2 mx-auto text-muted-foreground">
|
||||||
<span>viewing</span>
|
<span>viewing</span>
|
||||||
<span className="text-foreground">
|
<span className="text-foreground">
|
||||||
{currentPage * limit}-{currentPage * limit + (limit - 1)}
|
{currentPage * limit - limit + 1}-{currentPage * limit}
|
||||||
</span>
|
</span>
|
||||||
<span>of</span>
|
<span>of</span>
|
||||||
<span className="text-foreground">{totalDocs}</span>
|
<span className="text-foreground">{totalDocs}</span>
|
||||||
|
@ -1,79 +1,64 @@
|
|||||||
import * as React from "react"
|
import * as jeact from 'react'
|
||||||
import {
|
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
MoreHorizontalIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils'
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label="pagination"
|
aria-label="pagination"
|
||||||
data-slot="pagination"
|
data-slot="pagination"
|
||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn('mx-auto flex w-full justify-center', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationContent({
|
function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"ul">) {
|
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
data-slot="pagination-content"
|
data-slot="pagination-content"
|
||||||
className={cn("flex flex-row items-center gap-1", className)}
|
className={cn('flex flex-row items-center gap-1', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
|
||||||
return <li data-slot="pagination-item" {...props} />
|
return <li data-slot="pagination-item" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
|
||||||
React.ComponentProps<"a">
|
React.ComponentProps<'a'>
|
||||||
|
|
||||||
function PaginationLink({
|
function PaginationLink({ className, isActive, size = 'icon', ...props }: PaginationLinkProps) {
|
||||||
className,
|
|
||||||
isActive,
|
|
||||||
size = "icon",
|
|
||||||
...props
|
|
||||||
}: PaginationLinkProps) {
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
aria-current={isActive ? "page" : undefined}
|
aria-current={isActive ? 'page' : undefined}
|
||||||
data-slot="pagination-link"
|
data-slot="pagination-link"
|
||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: isActive ? "outline" : "ghost",
|
variant: isActive ? 'outline' : 'ghost',
|
||||||
size,
|
size,
|
||||||
}),
|
}),
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationPrevious({
|
function PaginationPrevious({ className, ...props }: React.ComponentProps<typeof PaginationLink>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
|
||||||
return (
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label="Go to previous page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeftIcon />
|
<ChevronLeftIcon />
|
||||||
@ -82,15 +67,12 @@ function PaginationPrevious({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationNext({
|
function PaginationNext({ className, ...props }: React.ComponentProps<typeof PaginationLink>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
|
||||||
return (
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label="Go to next page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="hidden sm:block">Next</span>
|
<span className="hidden sm:block">Next</span>
|
||||||
@ -99,15 +81,12 @@ function PaginationNext({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationEllipsis({
|
function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span">) {
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
data-slot="pagination-ellipsis"
|
data-slot="pagination-ellipsis"
|
||||||
className={cn("flex size-9 items-center justify-center", className)}
|
className={cn('flex size-9 items-center justify-center', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon className="size-4" />
|
<MoreHorizontalIcon className="size-4" />
|
||||||
|
57
src/components/ui/text-shimmer.tsx
Normal file
57
src/components/ui/text-shimmer.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
'use client';
|
||||||
|
import React, { useMemo, type JSX } from 'react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export type TextShimmerProps = {
|
||||||
|
children: string;
|
||||||
|
as?: React.ElementType;
|
||||||
|
className?: string;
|
||||||
|
duration?: number;
|
||||||
|
spread?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TextShimmerComponent({
|
||||||
|
children,
|
||||||
|
as: Component = 'p',
|
||||||
|
className,
|
||||||
|
duration = 2,
|
||||||
|
spread = 2,
|
||||||
|
}: TextShimmerProps) {
|
||||||
|
const MotionComponent = motion.create(
|
||||||
|
Component as keyof JSX.IntrinsicElements
|
||||||
|
);
|
||||||
|
|
||||||
|
const dynamicSpread = useMemo(() => {
|
||||||
|
return children.length * spread;
|
||||||
|
}, [children, spread]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionComponent
|
||||||
|
className={cn(
|
||||||
|
'relative inline-block bg-[length:250%_100%,auto] bg-clip-text',
|
||||||
|
'text-transparent [--base-color:#a1a1aa] [--base-gradient-color:#000]',
|
||||||
|
'[background-repeat:no-repeat,padding-box] [--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]',
|
||||||
|
'dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff] dark:[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
initial={{ backgroundPosition: '100% center' }}
|
||||||
|
animate={{ backgroundPosition: '0% center' }}
|
||||||
|
transition={{
|
||||||
|
repeat: Infinity,
|
||||||
|
duration,
|
||||||
|
ease: 'linear',
|
||||||
|
}}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
'--spread': `${dynamicSpread}px`,
|
||||||
|
backgroundImage: `var(--bg), linear-gradient(var(--base-color), var(--base-color))`,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MotionComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextShimmer = React.memo(TextShimmerComponent);
|
Loading…
x
Reference in New Issue
Block a user