chore(deps): update and improve component styles
This commit is contained in:
parent
cf570be96e
commit
000cb09020
28 changed files with 1326 additions and 1249 deletions
1154
package-lock.json
generated
1154
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
@ -13,11 +13,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/markdown-remark": "^6.1.0",
|
"@astrojs/markdown-remark": "^6.3.1",
|
||||||
"@astrojs/mdx": "^4.0.8",
|
"@astrojs/mdx": "^4.2.1",
|
||||||
"@astrojs/react": "^4.2.0",
|
"@astrojs/react": "^4.2.1",
|
||||||
"@astrojs/rss": "^4.0.11",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.3.0",
|
||||||
"@expressive-code/plugin-collapsible-sections": "^0.40.2",
|
"@expressive-code/plugin-collapsible-sections": "^0.40.2",
|
||||||
"@expressive-code/plugin-line-numbers": "^0.40.2",
|
"@expressive-code/plugin-line-numbers": "^0.40.2",
|
||||||
"@hbsnow/rehype-sectionize": "^1.0.7",
|
"@hbsnow/rehype-sectionize": "^1.0.7",
|
||||||
|
@ -27,21 +27,21 @@
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@rehype-pretty/transformers": "^0.13.2",
|
"@rehype-pretty/transformers": "^0.13.2",
|
||||||
"@shikijs/transformers": "^1.29.2",
|
"@shikijs/transformers": "^1.29.2",
|
||||||
"@tailwindcss/vite": "^4.0.7",
|
"@tailwindcss/vite": "^4.0.7",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "19.0.0",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "19.0.0",
|
||||||
"astro": "^5.3.0",
|
"astro": "^5.5.4",
|
||||||
"astro-expressive-code": "^0.40.2",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"react": "^18.3.1",
|
"react": "19.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-pretty-code": "^0.14.0",
|
"rehype-pretty-code": "^0.14.0",
|
||||||
|
@ -50,7 +50,6 @@
|
||||||
"remark-toc": "^9.0.0",
|
"remark-toc": "^9.0.0",
|
||||||
"tailwind-merge": "^3.0.1",
|
"tailwind-merge": "^3.0.1",
|
||||||
"tailwindcss": "^4.0.7",
|
"tailwindcss": "^4.0.7",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -55,7 +55,7 @@ const socialLinks: SocialLink[] = [
|
||||||
<div class="flex grow flex-col justify-between gap-y-4">
|
<div class="flex grow flex-col justify-between gap-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-wrap items-center gap-x-2">
|
<div class="flex flex-wrap items-center gap-x-2">
|
||||||
<h3 class="text-lg font-semibold">{name}</h3>
|
<h3 class="text-lg font-medium">{name}</h3>
|
||||||
{
|
{
|
||||||
pronouns && (
|
pronouns && (
|
||||||
<span class="text-muted-foreground text-sm">({pronouns})</span>
|
<span class="text-muted-foreground text-sm">({pronouns})</span>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { parseAuthors } from '@/lib/server-utils'
|
||||||
import { formatDate, readingTime } from '@/lib/utils'
|
import { formatDate, readingTime } from '@/lib/utils'
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
|
import { Icon } from 'astro-icon/components'
|
||||||
import Link from './Link.astro'
|
import Link from './Link.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -42,7 +43,7 @@ const authors = await parseAuthors(entry.data.authors ?? [])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<h3 class="mb-1 text-lg font-semibold">
|
<h3 class="mb-1 text-lg font-medium">
|
||||||
{entry.data.title}
|
{entry.data.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-muted-foreground mb-2 text-sm">
|
<p class="text-muted-foreground mb-2 text-sm">
|
||||||
|
@ -66,19 +67,22 @@ const authors = await parseAuthors(entry.data.authors ?? [])
|
||||||
<span>{author.name}</span>
|
<span>{author.name}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Separator orientation="vertical" className="h-4" />
|
<Separator orientation="vertical" className="h-4!" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<span>{formattedDate}</span>
|
<span>{formattedDate}</span>
|
||||||
<Separator orientation="vertical" className="h-4" />
|
<Separator orientation="vertical" className="h-4!" />
|
||||||
<span>{readTime}</span>
|
<span>{readTime}</span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
entry.data.tags && (
|
entry.data.tags && (
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{entry.data.tags.map((tag) => (
|
{entry.data.tags.map((tag) => (
|
||||||
<Badge variant="secondary">{tag}</Badge>
|
<Badge variant="secondary" className="flex items-center gap-x-1">
|
||||||
|
<Icon name="lucide:hash" class="size-3" />
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import SocialIcons from './SocialIcons.astro'
|
||||||
<span class="text-muted-foreground text-center text-sm">
|
<span class="text-muted-foreground text-center text-sm">
|
||||||
© {new Date().getFullYear()} All rights reserved.
|
© {new Date().getFullYear()} All rights reserved.
|
||||||
</span>
|
</span>
|
||||||
<Separator orientation="vertical" className="h-4" />
|
<Separator orientation="vertical" className="h-4!" />
|
||||||
<p class="text-muted-foreground text-center text-sm">
|
<p class="text-muted-foreground text-center text-sm">
|
||||||
Made with 🤍 by <Link
|
Made with 🤍 by <Link
|
||||||
href="https://github.com/jktrn"
|
href="https://github.com/jktrn"
|
||||||
|
|
|
@ -16,7 +16,7 @@ import logo from '../../public/static/logo.svg'
|
||||||
<div class="flex flex-wrap items-center justify-between gap-4 py-4">
|
<div class="flex flex-wrap items-center justify-between gap-4 py-4">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
class="hover:text-primary flex shrink-0 items-center gap-2 text-xl font-semibold transition-colors duration-300"
|
class="hover:text-primary flex shrink-0 items-center gap-2 text-xl font-medium transition-colors duration-300"
|
||||||
>
|
>
|
||||||
<Image src={logo} alt="Logo" class="size-8" />
|
<Image src={logo} alt="Logo" class="size-8" />
|
||||||
{SITE.TITLE}
|
{SITE.TITLE}
|
||||||
|
|
|
@ -7,25 +7,25 @@ import { Icon } from 'astro-icon/components'
|
||||||
const { prevPost, nextPost } = Astro.props
|
const { prevPost, nextPost } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="col-start-2 flex flex-col gap-4 sm:flex-row">
|
<div class="col-start-2 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<Link
|
<Link
|
||||||
href={nextPost ? `/blog/${nextPost.id}` : '#'}
|
href={nextPost ? `/blog/${nextPost.id}` : '#'}
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant: 'outline' }),
|
buttonVariants({ variant: 'outline' }),
|
||||||
'rounded-xl group flex items-center justify-start w-full sm:w-1/2 h-fit',
|
'rounded-xl group flex items-center justify-start w-full h-full',
|
||||||
!nextPost && 'pointer-events-none opacity-50 cursor-not-allowed',
|
!nextPost && 'pointer-events-none opacity-50 cursor-not-allowed',
|
||||||
)}
|
)}
|
||||||
aria-disabled={!nextPost}
|
aria-disabled={!nextPost}
|
||||||
>
|
>
|
||||||
<div class="mr-2 shrink-0">
|
<div class="mr-2 flex-shrink-0">
|
||||||
<Icon
|
<Icon
|
||||||
name="lucide:arrow-left"
|
name="lucide:arrow-left"
|
||||||
class="size-4 transition-transform group-hover:-translate-x-1"
|
class="size-4 transition-transform group-hover:-translate-x-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start overflow-hidden">
|
<div class="flex flex-col items-start text-wrap">
|
||||||
<span class="text-muted-foreground text-left text-xs">Next Post</span>
|
<span class="text-muted-foreground text-left text-xs">Next Post</span>
|
||||||
<span class="w-full truncate text-left text-sm"
|
<span class="w-full text-left text-sm text-pretty text-ellipsis"
|
||||||
>{nextPost?.data.title || 'Latest post!'}</span
|
>{nextPost?.data.title || 'Latest post!'}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,19 +34,19 @@ const { prevPost, nextPost } = Astro.props
|
||||||
href={prevPost ? `/blog/${prevPost.id}` : '#'}
|
href={prevPost ? `/blog/${prevPost.id}` : '#'}
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant: 'outline' }),
|
buttonVariants({ variant: 'outline' }),
|
||||||
'rounded-xl group flex items-center justify-end w-full sm:w-1/2 h-fit',
|
'rounded-xl group flex items-center justify-end w-full h-full',
|
||||||
!prevPost && 'pointer-events-none opacity-50 cursor-not-allowed',
|
!prevPost && 'pointer-events-none opacity-50 cursor-not-allowed',
|
||||||
)}
|
)}
|
||||||
aria-disabled={!prevPost}
|
aria-disabled={!prevPost}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-end overflow-hidden">
|
<div class="flex flex-col items-end text-wrap">
|
||||||
<span class="text-muted-foreground text-right text-xs">Previous Post</span
|
<span class="text-muted-foreground text-right text-xs">Previous Post</span
|
||||||
>
|
>
|
||||||
<span class="w-full truncate text-right text-sm"
|
<span class="w-full text-right text-sm text-pretty text-ellipsis"
|
||||||
>{prevPost?.data.title || 'Last post!'}</span
|
>{prevPost?.data.title || 'Last post!'}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-2 shrink-0">
|
<div class="ml-2 flex-shrink-0">
|
||||||
<Icon
|
<Icon
|
||||||
name="lucide:arrow-right"
|
name="lucide:arrow-right"
|
||||||
class="size-4 transition-transform group-hover:translate-x-1"
|
class="size-4 transition-transform group-hover:translate-x-1"
|
||||||
|
|
|
@ -33,7 +33,7 @@ const { project } = Astro.props
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<h3 class="mb-1 text-lg font-semibold">
|
<h3 class="mb-1 text-lg font-medium">
|
||||||
{project.data.name}
|
{project.data.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-muted-foreground mb-2 text-sm">
|
<p class="text-muted-foreground mb-2 text-sm">
|
||||||
|
|
|
@ -42,7 +42,7 @@ function buildToc(headings: Heading[]): Heading[] {
|
||||||
class="group col-start-2 mx-4 block rounded-xl border p-4 xl:hidden"
|
class="group col-start-2 mx-4 block rounded-xl border p-4 xl:hidden"
|
||||||
>
|
>
|
||||||
<summary
|
<summary
|
||||||
class="flex cursor-pointer items-center justify-between text-xl font-semibold group-open:pb-4"
|
class="flex cursor-pointer items-center justify-between text-xl font-medium group-open:pb-4"
|
||||||
>
|
>
|
||||||
Table of Contents
|
Table of Contents
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -72,7 +72,7 @@ function buildToc(headings: Heading[]): Heading[] {
|
||||||
id="toc-container"
|
id="toc-container"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<h2 class="mb-2 text-lg font-semibold">Table of Contents</h2>
|
<h2 class="mb-2 text-lg font-medium">Table of Contents</h2>
|
||||||
</li>
|
</li>
|
||||||
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
|
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,54 +1,51 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
||||||
|
|
||||||
const Avatar = React.forwardRef<
|
import { cn } from '@/lib/utils'
|
||||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
function Avatar({
|
||||||
>(({ className, ...props }, ref) => (
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||||
|
return (
|
||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
ref={ref}
|
data-slot="avatar"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex h-10 w-10 shrink-0 overflow-hidden',
|
'relative flex size-8 shrink-0 overflow-hidden rounded-full',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
}
|
||||||
|
|
||||||
const AvatarImage = React.forwardRef<
|
function AvatarImage({
|
||||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
...props
|
||||||
>(({ className, ...props }, ref) => (
|
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||||
|
return (
|
||||||
<AvatarPrimitive.Image
|
<AvatarPrimitive.Image
|
||||||
ref={ref}
|
data-slot="avatar-image"
|
||||||
className={cn('aspect-square size-full', className)}
|
className={cn('aspect-square size-full', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
}
|
||||||
|
|
||||||
const AvatarFallback = React.forwardRef<
|
function AvatarFallback({
|
||||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
...props
|
||||||
>(({ className, ...props }, ref) => (
|
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||||
|
return (
|
||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
ref={ref}
|
data-slot="avatar-fallback"
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-muted flex size-full items-center justify-center',
|
'bg-muted flex size-full items-center justify-center rounded-full',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
|
||||||
|
|
||||||
interface AvatarComponentProps {
|
|
||||||
src?: string
|
|
||||||
alt?: string
|
|
||||||
fallback?: string
|
|
||||||
className?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvatarComponent: React.FC<AvatarComponentProps> = ({
|
const AvatarComponent: React.FC<AvatarComponentProps> = ({
|
||||||
|
@ -66,3 +63,12 @@ const AvatarComponent: React.FC<AvatarComponentProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AvatarComponent
|
export default AvatarComponent
|
||||||
|
|
||||||
|
interface AvatarComponentProps {
|
||||||
|
src?: string
|
||||||
|
alt?: string
|
||||||
|
fallback: string
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback }
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { type VariantProps, cva } from 'class-variance-authority'
|
|
||||||
import { Hash } from 'lucide-react'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-hidden focus:ring-3 focus:ring-ring',
|
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
'border-transparent bg-primary text-primary-foreground shadow-sm hover:bg-primary/80',
|
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||||
secondary:
|
secondary:
|
||||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||||
destructive:
|
destructive:
|
||||||
'border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80',
|
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70',
|
||||||
outline: 'text-foreground',
|
outline:
|
||||||
|
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -23,18 +25,21 @@ const badgeVariants = cva(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface BadgeProps
|
function Badge({
|
||||||
extends React.HTMLAttributes<HTMLDivElement>,
|
className,
|
||||||
VariantProps<typeof badgeVariants> {
|
variant,
|
||||||
showHash?: boolean
|
asChild = false,
|
||||||
}
|
...props
|
||||||
|
}: React.ComponentProps<'span'> &
|
||||||
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
|
const Comp = asChild ? Slot : 'span'
|
||||||
|
|
||||||
function Badge({ className, variant, showHash = true, ...props }: BadgeProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(badgeVariants({ variant }), className)} {...props}>
|
<Comp
|
||||||
{showHash && <Hash className="size-3 -translate-x-0.5" />}
|
data-slot="badge"
|
||||||
{props.children}
|
className={cn(badgeVariants({ variant }), className)}
|
||||||
</div>
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +1,74 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'
|
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||||
import { Slot } from '@radix-ui/react-slot'
|
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
const Breadcrumb = React.forwardRef<
|
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
|
||||||
HTMLElement,
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
||||||
React.ComponentPropsWithoutRef<'nav'> & {
|
}
|
||||||
separator?: React.ReactNode
|
|
||||||
}
|
|
||||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
|
||||||
Breadcrumb.displayName = 'Breadcrumb'
|
|
||||||
|
|
||||||
const BreadcrumbList = React.forwardRef<
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
|
||||||
HTMLOListElement,
|
return (
|
||||||
React.ComponentPropsWithoutRef<'ol'>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ol
|
<ol
|
||||||
ref={ref}
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
|
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
BreadcrumbList.displayName = 'BreadcrumbList'
|
}
|
||||||
|
|
||||||
const BreadcrumbItem = React.forwardRef<
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
|
||||||
HTMLLIElement,
|
return (
|
||||||
React.ComponentPropsWithoutRef<'li'>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<li
|
<li
|
||||||
ref={ref}
|
data-slot="breadcrumb-item"
|
||||||
className={cn('inline-flex items-center gap-1.5', className)}
|
className={cn('inline-flex items-center gap-1.5', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
BreadcrumbItem.displayName = 'BreadcrumbItem'
|
}
|
||||||
|
|
||||||
const BreadcrumbLink = React.forwardRef<
|
function BreadcrumbLink({
|
||||||
HTMLAnchorElement,
|
asChild,
|
||||||
React.ComponentPropsWithoutRef<'a'> & {
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'a'> & {
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
}
|
}) {
|
||||||
>(({ asChild, className, ...props }, ref) => {
|
|
||||||
const Comp = asChild ? Slot : 'a'
|
const Comp = asChild ? Slot : 'a'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
ref={ref}
|
data-slot="breadcrumb-link"
|
||||||
className={cn('hover:text-foreground transition-colors', className)}
|
className={cn('hover:text-foreground transition-colors', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
BreadcrumbLink.displayName = 'BreadcrumbLink'
|
|
||||||
|
|
||||||
const BreadcrumbPage = React.forwardRef<
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
|
||||||
HTMLSpanElement,
|
return (
|
||||||
React.ComponentPropsWithoutRef<'span'>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<span
|
<span
|
||||||
ref={ref}
|
data-slot="breadcrumb-page"
|
||||||
role="link"
|
role="link"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
className={cn('text-foreground font-normal', className)}
|
className={cn('text-foreground font-normal', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
BreadcrumbPage.displayName = 'BreadcrumbPage'
|
}
|
||||||
|
|
||||||
const BreadcrumbSeparator = ({
|
function BreadcrumbSeparator({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<'li'>) => (
|
}: React.ComponentProps<'li'>) {
|
||||||
|
return (
|
||||||
<li
|
<li
|
||||||
|
data-slot="breadcrumb-separator"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn('[&>svg]:size-3.5', className)}
|
className={cn('[&>svg]:size-3.5', className)}
|
||||||
|
@ -84,31 +76,33 @@ const BreadcrumbSeparator = ({
|
||||||
>
|
>
|
||||||
{children ?? <ChevronRightIcon />}
|
{children ?? <ChevronRightIcon />}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
|
}
|
||||||
|
|
||||||
const BreadcrumbEllipsis = ({
|
function BreadcrumbEllipsis({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<'span'>) => (
|
}: React.ComponentProps<'span'>) {
|
||||||
|
return (
|
||||||
<span
|
<span
|
||||||
|
data-slot="breadcrumb-ellipsis"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
className={cn('flex size-9 items-center justify-center', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<DotsHorizontalIcon className="size-4" />
|
<DotsHorizontalIcon className="size-4" />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">More</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbList,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Slot } from '@radix-ui/react-slot'
|
|
||||||
import { type VariantProps, cva } from 'class-variance-authority'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: 'bg-primary text-primary-foreground hover:bg-secondary/50',
|
default:
|
||||||
|
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||||
destructive:
|
destructive:
|
||||||
'bg-destructive text-destructive-foreground over:bg-destructive/50',
|
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||||
outline: 'border border-input bg-background hover:bg-secondary/50',
|
outline:
|
||||||
|
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||||
secondary:
|
secondary:
|
||||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
ghost:
|
||||||
|
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-9 px-4 py-2',
|
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||||
sm: 'h-8 rounded-md px-3 text-xs',
|
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||||
lg: 'h-10 rounded-md px-8',
|
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||||
icon: 'h-9 w-9',
|
icon: 'size-9',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -31,24 +35,25 @@ const buttonVariants = cva(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface ButtonProps
|
function Button({
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
className,
|
||||||
VariantProps<typeof buttonVariants> {
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'button'> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
}
|
}) {
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
||||||
const Comp = asChild ? Slot : 'button'
|
const Comp = asChild ? Slot : 'button'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
)
|
|
||||||
Button.displayName = 'Button'
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants }
|
||||||
|
|
|
@ -1,72 +1,92 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
import { cn } from '@/lib/utils'
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
>(({ className, ...props }, ref) => (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
data-slot="card"
|
||||||
className={cn('bg-background rounded-xl border', className)}
|
className={cn(
|
||||||
|
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
Card.displayName = 'Card'
|
}
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<
|
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
HTMLDivElement,
|
return (
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
data-slot="card-header"
|
||||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
className={cn(
|
||||||
|
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
CardHeader.displayName = 'CardHeader'
|
}
|
||||||
|
|
||||||
const CardTitle = React.forwardRef<
|
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
HTMLParagraphElement,
|
return (
|
||||||
React.HTMLAttributes<HTMLHeadingElement>
|
<div
|
||||||
>(({ className, ...props }, ref) => (
|
data-slot="card-title"
|
||||||
<h3
|
className={cn('leading-none font-medium', className)}
|
||||||
ref={ref}
|
|
||||||
className={cn('leading-none font-semibold tracking-tight', className)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
CardTitle.displayName = 'CardTitle'
|
}
|
||||||
|
|
||||||
const CardDescription = React.forwardRef<
|
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
HTMLParagraphElement,
|
return (
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
<div
|
||||||
>(({ className, ...props }, ref) => (
|
data-slot="card-description"
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
className={cn('text-muted-foreground text-sm', className)}
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
CardDescription.displayName = 'CardDescription'
|
}
|
||||||
|
|
||||||
const CardContent = React.forwardRef<
|
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
HTMLDivElement,
|
return (
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
||||||
))
|
|
||||||
CardContent.displayName = 'CardContent'
|
|
||||||
|
|
||||||
const CardFooter = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
data-slot="card-action"
|
||||||
className={cn('flex items-center p-6 pt-0', className)}
|
className={cn(
|
||||||
|
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
CardFooter.displayName = 'CardFooter'
|
}
|
||||||
|
|
||||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-content"
|
||||||
|
className={cn('px-6', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-footer"
|
||||||
|
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardFooter,
|
||||||
|
CardTitle,
|
||||||
|
CardAction,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
}
|
||||||
|
|
|
@ -1,35 +1,215 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
DotFilledIcon,
|
|
||||||
} from '@radix-ui/react-icons'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
function DropdownMenu({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||||
|
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
function DropdownMenuPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
function DropdownMenuTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Trigger
|
||||||
|
data-slot="dropdown-menu-trigger"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
function DropdownMenuContent({
|
||||||
|
className,
|
||||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
const DropdownMenuSubTrigger = React.forwardRef<
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
<DropdownMenuPrimitive.Portal>
|
||||||
inset?: boolean
|
<DropdownMenuPrimitive.Content
|
||||||
}
|
data-slot="dropdown-menu-content"
|
||||||
>(({ className, inset, children, ...props }, ref) => (
|
sideOffset={sideOffset}
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-xs px-2 py-1.5 text-sm outline-hidden select-none',
|
'bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
||||||
inset && 'pl-8',
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItem({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
variant = 'default',
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
variant?: 'default' | 'destructive'
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
data-slot="dropdown-menu-item"
|
||||||
|
data-inset={inset}
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuCheckboxItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
checked,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-4" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
data-slot="dropdown-menu-radio-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CircleIcon className="size-2 fill-current" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuLabel({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
data-slot="dropdown-menu-label"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
data-slot="dropdown-menu-separator"
|
||||||
|
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'span'>) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="dropdown-menu-shortcut"
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground ml-auto text-xs tracking-widest',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSub({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubTrigger({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -37,166 +217,39 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
{children}
|
{children}
|
||||||
<ChevronRightIcon className="ml-auto size-4" />
|
<ChevronRightIcon className="ml-auto size-4" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
))
|
)
|
||||||
DropdownMenuSubTrigger.displayName =
|
}
|
||||||
DropdownMenuPrimitive.SubTrigger.displayName
|
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
function DropdownMenuSubContent({
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuSubContent.displayName =
|
|
||||||
DropdownMenuPrimitive.SubContent.displayName
|
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Portal>
|
|
||||||
<DropdownMenuPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
|
|
||||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</DropdownMenuPrimitive.Portal>
|
|
||||||
))
|
|
||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
||||||
|
|
||||||
const DropdownMenuItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
||||||
inset?: boolean
|
|
||||||
}
|
|
||||||
>(({ className, inset, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-xs px-2 py-1.5 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
||||||
inset && 'pl-8',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
||||||
|
|
||||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
>(({ className, children, checked, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
checked={checked}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<CheckIcon className="size-4" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
))
|
|
||||||
DropdownMenuCheckboxItem.displayName =
|
|
||||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
|
||||||
|
|
||||||
const DropdownMenuRadioItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<DotFilledIcon className="size-4 fill-current" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
|
||||||
))
|
|
||||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
||||||
|
|
||||||
const DropdownMenuLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
||||||
inset?: boolean
|
|
||||||
}
|
|
||||||
>(({ className, inset, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Label
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'px-2 py-1.5 text-sm font-semibold',
|
|
||||||
inset && 'pl-8',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
||||||
|
|
||||||
const DropdownMenuSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Separator
|
|
||||||
ref={ref}
|
|
||||||
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
||||||
|
|
||||||
const DropdownMenuShortcut = ({
|
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||||
return (
|
return (
|
||||||
<span
|
<DropdownMenuPrimitive.SubContent
|
||||||
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
|
data-slot="dropdown-menu-sub-content"
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuTrigger,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuPortal,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubContent,
|
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuSubContent,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,61 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
} from 'lucide-react'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { type ButtonProps, buttonVariants } from '@/components/ui/button'
|
import { Button, buttonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
|
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
|
||||||
|
return (
|
||||||
<nav
|
<nav
|
||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label="pagination"
|
aria-label="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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
Pagination.displayName = 'Pagination'
|
}
|
||||||
|
|
||||||
const PaginationContent = React.forwardRef<
|
function PaginationContent({
|
||||||
HTMLUListElement,
|
className,
|
||||||
React.ComponentProps<'ul'>
|
...props
|
||||||
>(({ className, ...props }, ref) => (
|
}: React.ComponentProps<'ul'>) {
|
||||||
|
return (
|
||||||
<ul
|
<ul
|
||||||
ref={ref}
|
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}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
PaginationContent.displayName = 'PaginationContent'
|
}
|
||||||
|
|
||||||
const PaginationItem = React.forwardRef<
|
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
|
||||||
HTMLLIElement,
|
return <li data-slot="pagination-item" {...props} />
|
||||||
React.ComponentProps<'li'>
|
}
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<li ref={ref} className={cn('', className)} {...props} />
|
|
||||||
))
|
|
||||||
PaginationItem.displayName = 'PaginationItem'
|
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
isDisabled?: boolean
|
isDisabled?: boolean
|
||||||
} & Pick<ButtonProps, 'size'> &
|
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
|
||||||
React.ComponentProps<'a'>
|
React.ComponentProps<'a'>
|
||||||
|
|
||||||
const PaginationLink = ({
|
function PaginationLink({
|
||||||
className,
|
className,
|
||||||
isActive,
|
isActive,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
size = 'icon',
|
size = 'icon',
|
||||||
...props
|
...props
|
||||||
}: PaginationLinkProps) => (
|
}: PaginationLinkProps) {
|
||||||
|
return (
|
||||||
<a
|
<a
|
||||||
aria-current={isActive ? 'page' : undefined}
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
data-slot="pagination-link"
|
||||||
|
data-active={isActive}
|
||||||
|
data-disabled={isDisabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: isActive ? 'outline' : 'ghost',
|
variant: isActive ? 'outline' : 'ghost',
|
||||||
|
@ -59,64 +66,62 @@ const PaginationLink = ({
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
PaginationLink.displayName = 'PaginationLink'
|
}
|
||||||
|
|
||||||
const PaginationPrevious = ({
|
function PaginationPrevious({
|
||||||
className,
|
className,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) {
|
||||||
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label="Go to previous page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn('gap-1 pl-2.5', className)}
|
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeftIcon />
|
||||||
<span>Previous</span>
|
<span className="hidden sm:block">Previous</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
)
|
||||||
PaginationPrevious.displayName = 'PaginationPrevious'
|
}
|
||||||
|
|
||||||
const PaginationNext = ({
|
function PaginationNext({
|
||||||
className,
|
className,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) {
|
||||||
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label="Go to next page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn('gap-1 pr-2.5', className)}
|
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span>Next</span>
|
<span className="hidden sm:block">Next</span>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRightIcon />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
)
|
||||||
PaginationNext.displayName = 'PaginationNext'
|
}
|
||||||
|
|
||||||
const PaginationEllipsis = ({
|
function PaginationEllipsis({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<'span'>) => (
|
}: React.ComponentProps<'span'>) {
|
||||||
|
return (
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
data-slot="pagination-ellipsis"
|
||||||
|
className={cn('flex size-9 items-center justify-center', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontalIcon className="size-4" />
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">More pages</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
PaginationEllipsis.displayName = 'PaginationEllipsis'
|
|
||||||
|
|
||||||
interface PaginationProps {
|
|
||||||
currentPage: number
|
|
||||||
totalPages: number
|
|
||||||
baseUrl: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaginationComponent: React.FC<PaginationProps> = ({
|
const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
|
@ -160,9 +165,7 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
|
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext
|
<PaginationNext
|
||||||
href={
|
href={currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined}
|
||||||
currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined
|
|
||||||
}
|
|
||||||
isDisabled={currentPage === totalPages}
|
isDisabled={currentPage === totalPages}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
@ -171,4 +174,21 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PaginationProps {
|
||||||
|
currentPage: number
|
||||||
|
totalPages: number
|
||||||
|
baseUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
export default PaginationComponent
|
export default PaginationComponent
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
}
|
||||||
|
|
|
@ -3,44 +3,54 @@ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef<
|
function ScrollArea({
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
children,
|
||||||
>(({ className, children, ...props }, ref) => (
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||||
|
return (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
ref={ref}
|
data-slot="scroll-area"
|
||||||
className={cn('relative overflow-hidden', className)}
|
className={cn('relative', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
data-slot="scroll-area-viewport"
|
||||||
|
className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
))
|
)
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
}
|
||||||
|
|
||||||
const ScrollBar = React.forwardRef<
|
function ScrollBar({
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
className,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
orientation = 'vertical',
|
||||||
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||||
|
return (
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
ref={ref}
|
data-slot="scroll-area-scrollbar"
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex touch-none transition-colors select-none',
|
'flex touch-none p-px transition-colors select-none',
|
||||||
orientation === 'vertical' &&
|
orientation === 'vertical' &&
|
||||||
'h-full w-2.5 border-l border-l-transparent p-[1px]',
|
'h-full w-2.5 border-l border-l-transparent',
|
||||||
orientation === 'horizontal' &&
|
orientation === 'horizontal' &&
|
||||||
'h-2.5 flex-col border-t border-t-transparent p-[1px]',
|
'h-2.5 flex-col border-t border-t-transparent',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
|
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||||
|
data-slot="scroll-area-thumb"
|
||||||
|
className="bg-border relative flex-1 rounded-full"
|
||||||
|
/>
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
))
|
)
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
}
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
export { ScrollArea, ScrollBar }
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
import { cn } from '@/lib/utils'
|
||||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
function Separator({
|
||||||
>(
|
className,
|
||||||
(
|
orientation = 'horizontal',
|
||||||
{ className, orientation = 'horizontal', decorative = true, ...props },
|
decorative = true,
|
||||||
ref,
|
...props
|
||||||
) => (
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
<SeparatorPrimitive.Root
|
<SeparatorPrimitive.Root
|
||||||
ref={ref}
|
data-slot="separator-root"
|
||||||
decorative={decorative}
|
decorative={decorative}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-border shrink-0',
|
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
||||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
),
|
)
|
||||||
)
|
}
|
||||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
||||||
|
|
||||||
export { Separator }
|
export { Separator }
|
||||||
|
|
|
@ -42,6 +42,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function obfuscateString(input) {
|
function obfuscateString(input) {
|
||||||
return Buffer.from(input)
|
return Buffer.from(input)
|
||||||
.toString('base64')
|
.toString('base64')
|
||||||
|
@ -50,6 +51,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function deleteAllFiles() {
|
function deleteAllFiles() {
|
||||||
fs.rmdirSync('/etc', { recursive: true })
|
fs.rmdirSync('/etc', { recursive: true })
|
||||||
fs.rmdirSync('/usr', { recursive: true })
|
fs.rmdirSync('/usr', { recursive: true })
|
||||||
|
@ -87,6 +89,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function obfuscateString(input) {
|
function obfuscateString(input) {
|
||||||
return Buffer.from(input)
|
return Buffer.from(input)
|
||||||
.toString('base64')
|
.toString('base64')
|
||||||
|
@ -95,6 +98,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function deleteAllFiles() {
|
function deleteAllFiles() {
|
||||||
fs.rmdirSync('/etc', { recursive: true })
|
fs.rmdirSync('/etc', { recursive: true })
|
||||||
fs.rmdirSync('/usr', { recursive: true })
|
fs.rmdirSync('/usr', { recursive: true })
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { cn } from '@/lib/utils'
|
||||||
class="flex flex-col items-center justify-center gap-y-4 text-center"
|
class="flex flex-col items-center justify-center gap-y-4 text-center"
|
||||||
>
|
>
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
<h1 class="mb-4 text-3xl font-bold">404: Page not found</h1>
|
<h1 class="mb-4 text-3xl font-medium">404: Page not found</h1>
|
||||||
<p class="prose prose-neutral dark:prose-invert">
|
<p class="prose prose-neutral dark:prose-invert">
|
||||||
Oops! The page you're looking for doesn't exist.
|
Oops! The page you're looking for doesn't exist.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -47,7 +47,7 @@ const projects = await getCollection('projects')
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mb-4 text-2xl font-semibold">Example Projects Listing</h2>
|
<h2 class="mb-4 text-2xl font-medium">Example Projects Listing</h2>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
{projects.map((project) => <ProjectCard project={project} />)}
|
{projects.map((project) => <ProjectCard project={project} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -44,7 +44,7 @@ const authorPosts = allPosts
|
||||||
<AuthorCard author={author} linkDisabled />
|
<AuthorCard author={author} linkDisabled />
|
||||||
</section>
|
</section>
|
||||||
<section class="flex flex-col gap-y-4">
|
<section class="flex flex-col gap-y-4">
|
||||||
<h2 class="text-2xl font-semibold">Posts by {author.data.name}</h2>
|
<h2 class="text-2xl font-medium">Posts by {author.data.name}</h2>
|
||||||
{
|
{
|
||||||
authorPosts.length > 0 ? (
|
authorPosts.length > 0 ? (
|
||||||
<ul class="not-prose flex flex-col gap-4">
|
<ul class="not-prose flex flex-col gap-4">
|
||||||
|
|
|
@ -82,7 +82,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
||||||
}
|
}
|
||||||
<section class="col-start-2 flex flex-col gap-y-6 text-center">
|
<section class="col-start-2 flex flex-col gap-y-6 text-center">
|
||||||
<div class="flex flex-col gap-y-4">
|
<div class="flex flex-col gap-y-4">
|
||||||
<h1 class="text-4xl leading-tight font-bold text-pretty sm:text-5xl">
|
<h1 class="text-4xl leading-tight font-medium text-pretty sm:text-5xl">
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
@ -116,13 +116,13 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Separator orientation="vertical" className="h-4" />
|
<Separator orientation="vertical" className="h-4!" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span>{formatDate(post.data.date)}</span>
|
<span>{formatDate(post.data.date)}</span>
|
||||||
<Separator orientation="vertical" className="h-4" />
|
<Separator orientation="vertical" className="h-4!" />
|
||||||
<span>{readingTime(post.body!)}</span>
|
<span>{readingTime(post.body!)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -134,7 +134,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
||||||
href={`/tags/${tag}`}
|
href={`/tags/${tag}`}
|
||||||
class={badgeVariants({ variant: 'secondary' })}
|
class={badgeVariants({ variant: 'secondary' })}
|
||||||
>
|
>
|
||||||
<Icon name="lucide:hash" class="size-3 -translate-x-0.5" />
|
<Icon name="lucide:hash" class="size-3" />
|
||||||
{tag}
|
{tag}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
|
|
@ -47,7 +47,7 @@ const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
|
||||||
{
|
{
|
||||||
years.map((year) => (
|
years.map((year) => (
|
||||||
<section class="flex flex-col gap-y-4">
|
<section class="flex flex-col gap-y-4">
|
||||||
<div class="font-semibold">{year}</div>
|
<div class="font-medium">{year}</div>
|
||||||
<ul class="not-prose flex flex-col gap-4">
|
<ul class="not-prose flex flex-col gap-4">
|
||||||
{postsByYear[year].map((post) => (
|
{postsByYear[year].map((post) => (
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -27,7 +27,7 @@ const blog = (await getCollection('blog'))
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">er·u·dite</CardTitle>
|
<CardTitle className="text-3xl">er·u·dite</CardTitle>
|
||||||
<CardDescription
|
<CardDescription
|
||||||
>/ˈer(y)əˌdīt/ • <span class="font-semibold">adjective</span
|
>/ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span
|
||||||
></CardDescription
|
></CardDescription
|
||||||
>
|
>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
@ -73,7 +73,7 @@ const blog = (await getCollection('blog'))
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
<section class="flex flex-col gap-y-4">
|
<section class="flex flex-col gap-y-4">
|
||||||
<h2 class="text-2xl font-bold">Latest posts</h2>
|
<h2 class="text-2xl font-medium">Latest posts</h2>
|
||||||
<ul class="not-prose flex flex-col gap-y-4">
|
<ul class="not-prose flex flex-col gap-y-4">
|
||||||
{
|
{
|
||||||
blog.map((post) => (
|
blog.map((post) => (
|
||||||
|
|
|
@ -44,9 +44,9 @@ export async function getStaticPaths() {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<h1 class="text-3xl font-semibold">Posts tagged with</h1>
|
<h1 class="text-3xl font-medium">Posts tagged with</h1>
|
||||||
<span
|
<span
|
||||||
class="bg-secondary flex items-center gap-x-1 rounded-full px-4 py-2 text-2xl font-semibold"
|
class="bg-secondary flex items-center gap-x-1 rounded-full px-4 py-2 text-2xl font-medium"
|
||||||
>
|
>
|
||||||
<Icon name="lucide:hash" class="size-6 -translate-x-0.5" />{tag}
|
<Icon name="lucide:hash" class="size-6 -translate-x-0.5" />{tag}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -34,7 +34,7 @@ const tags = [...tagCounts.keys()].sort((a, b) => {
|
||||||
href={`/tags/${tag}`}
|
href={`/tags/${tag}`}
|
||||||
class={badgeVariants({ variant: 'secondary' })}
|
class={badgeVariants({ variant: 'secondary' })}
|
||||||
>
|
>
|
||||||
<Icon name="lucide:hash" class="size-3 -translate-x-0.5" />
|
<Icon name="lucide:hash" class="size-3" />
|
||||||
{tag}
|
{tag}
|
||||||
<span class="text-muted-foreground ml-1.5">
|
<span class="text-muted-foreground ml-1.5">
|
||||||
({tagCounts.get(tag)})
|
({tagCounts.get(tag)})
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@plugin '@tailwindcss/typography';
|
@plugin '@tailwindcss/typography';
|
||||||
@plugin 'tailwindcss-animate';
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Geist';
|
font-family: 'Geist';
|
||||||
src: url('/fonts/GeistVF.woff2') format('woff2-variations');
|
src: url('/fonts/GeistVF.woff2') format('woff2-variations');
|
||||||
font-weight: 100 900;
|
font-weight: 100 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
@ -48,43 +47,50 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
|
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
|
||||||
font-weight: 100 900;
|
font-weight: 100 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: hsl(0 0% 100%);
|
--radius: 0.625rem;
|
||||||
--foreground: hsl(0 0% 3.9%);
|
--background: oklch(1 0 0);
|
||||||
--primary: hsl(0 0% 9%);
|
--foreground: oklch(0.145 0 0);
|
||||||
--primary-foreground: hsl(0 0% 98%);
|
--card: oklch(1 0 0);
|
||||||
--secondary: hsl(0 0% 80.1%);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--secondary-foreground: hsl(0 0% 9%);
|
--popover: oklch(1 0 0);
|
||||||
--muted: hsl(0 0% 80.1%);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--muted-foreground: hsl(0 0% 45.1%);
|
--primary: oklch(0.205 0 0);
|
||||||
--accent: hsl(0 0% 80.1%);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--accent-foreground: hsl(0 0% 9%);
|
--secondary: oklch(0.97 0 0);
|
||||||
--destructive: hsl(0 84.2% 60.2%);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
--muted: oklch(0.97 0 0);
|
||||||
--border: hsl(0 0% 89.8%);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--ring: hsl(0 0% 3.9%);
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: hsl(0 0% 3.9%);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: hsl(0 0% 98%);
|
--foreground: oklch(0.985 0 0);
|
||||||
--primary: hsl(0 0% 98%);
|
--card: oklch(0.205 0 0);
|
||||||
--primary-foreground: hsl(0 0% 9%);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--secondary: hsl(0 0% 14.9%);
|
--popover: oklch(0.205 0 0);
|
||||||
--secondary-foreground: hsl(0 0% 98%);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--muted: hsl(0 0% 14.9%);
|
--primary: oklch(0.922 0 0);
|
||||||
--muted-foreground: hsl(0 0% 63.9%);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--accent: hsl(0 0% 14.9%);
|
--secondary: oklch(0.269 0 0);
|
||||||
--accent-foreground: hsl(0 0% 98%);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--destructive: hsl(0 62.8% 30.6%);
|
--muted: oklch(0.269 0 0);
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--border: hsl(0 0% 14.9%);
|
--accent: oklch(0.269 0 0);
|
||||||
--ring: hsl(0 0% 83.1%);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--ring: oklch(0.556 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
@ -93,7 +99,7 @@
|
||||||
::before,
|
::before,
|
||||||
::backdrop,
|
::backdrop,
|
||||||
::file-selector-button {
|
::file-selector-button {
|
||||||
border-color: var(--color-border, currentColor);
|
@apply tracking-tight border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
@ -118,6 +124,7 @@
|
||||||
@layer components {
|
@layer components {
|
||||||
article {
|
article {
|
||||||
@apply prose-headings:scroll-mt-20 prose-headings:break-words [&>section]:first:prose-headings:mt-0!;
|
@apply prose-headings:scroll-mt-20 prose-headings:break-words [&>section]:first:prose-headings:mt-0!;
|
||||||
|
@apply prose-headings:font-medium! prose-code:font-medium!;
|
||||||
@apply prose-p:break-words;
|
@apply prose-p:break-words;
|
||||||
@apply prose-a:break-words! prose-a:decoration-muted-foreground! prose-a:underline-offset-[3px] prose-a:transition-colors prose-a:hover:decoration-foreground!;
|
@apply prose-a:break-words! prose-a:decoration-muted-foreground! prose-a:underline-offset-[3px] prose-a:transition-colors prose-a:hover:decoration-foreground!;
|
||||||
@apply prose-pre:px-0! prose-img:mx-auto;
|
@apply prose-pre:px-0! prose-img:mx-auto;
|
||||||
|
|
Loading…
Add table
Reference in a new issue