feat: revamped author card
This commit is contained in:
parent
c410c499e1
commit
2211c4bbf3
46 changed files with 566 additions and 426 deletions
|
@ -1,30 +1,110 @@
|
|||
---
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import Link from '@components/Link.astro'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Link from '@components/Link.astro'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import { Github, Globe, Linkedin, Twitter } from 'lucide-react'
|
||||
|
||||
type Props = {
|
||||
author: CollectionEntry<'authors'>
|
||||
linkDisabled?: boolean
|
||||
}
|
||||
|
||||
const { author } = Astro.props
|
||||
const { name, avatar, bio } = author.data
|
||||
const { author, linkDisabled = false } = Astro.props
|
||||
const { name, avatar, bio, pronouns, github, twitter, linkedin, website } =
|
||||
author.data
|
||||
---
|
||||
|
||||
<div
|
||||
class="rounded-xl border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
|
||||
class="rounded-xl border p-4 transition-colors duration-300 ease-in-out has-[a:hover]:bg-secondary/50"
|
||||
>
|
||||
<Link href={`/authors/${author.slug}`} class="flex flex-wrap gap-4">
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={avatar}
|
||||
alt={`Avatar of ${name}`}
|
||||
fallback={name[0]}
|
||||
className="size-32 rounded-md"
|
||||
/>
|
||||
<div class="flex-grow">
|
||||
<h3 class="mb-1 text-lg font-semibold">{name}</h3>
|
||||
<p class="text-sm text-muted-foreground">{bio}</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href={`/authors/${author.slug}`}
|
||||
class={cn('block', linkDisabled && 'pointer-events-none')}
|
||||
>
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={avatar}
|
||||
alt={`Avatar of ${name}`}
|
||||
fallback={name[0]}
|
||||
className={cn(
|
||||
'size-32 rounded-md',
|
||||
!linkDisabled &&
|
||||
'transition-shadow duration-300 hover:cursor-pointer hover:ring-2 hover:ring-primary',
|
||||
)}
|
||||
/>
|
||||
</Link>
|
||||
<div class="flex flex-grow flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold">{name}</h3>
|
||||
{
|
||||
pronouns && (
|
||||
<span class="text-sm text-muted-foreground">({pronouns})</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{bio}</p>
|
||||
</div>
|
||||
<ul class="flex gap-2">
|
||||
{
|
||||
github && (
|
||||
<li>
|
||||
<Link
|
||||
href={`https://github.com/${github}`}
|
||||
aria-label="GitHub"
|
||||
title="GitHub"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Github className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
twitter && (
|
||||
<li>
|
||||
<Link
|
||||
href={`https://twitter.com/${twitter}`}
|
||||
aria-label="Twitter"
|
||||
title="Twitter"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Twitter className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
linkedin && (
|
||||
<li>
|
||||
<Link
|
||||
href={`https://linkedin.com/in/${linkedin}`}
|
||||
aria-label="LinkedIn"
|
||||
title="LinkedIn"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Linkedin className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
website && (
|
||||
<li>
|
||||
<Link
|
||||
href={website}
|
||||
aria-label="Website"
|
||||
title="Website"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Globe className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import { formatDate, readingTime, parseAuthors } from '@lib/utils'
|
||||
import { Image } from 'astro:assets'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import { formatDate, parseAuthors, readingTime } from '@lib/utils'
|
||||
import { Image } from 'astro:assets'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import Link from './Link.astro'
|
||||
|
||||
type Props = {
|
||||
|
|
53
src/components/Breadcrumbs.astro
Normal file
53
src/components/Breadcrumbs.astro
Normal file
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
import { HomeIcon } from 'lucide-react'
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
href?: string
|
||||
label: string
|
||||
icon?: any
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: BreadcrumbItem[]
|
||||
}
|
||||
|
||||
const { items } = Astro.props
|
||||
---
|
||||
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">
|
||||
<HomeIcon className="size-4" />
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
{
|
||||
items.map((item, index) => (
|
||||
<>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
{index === items.length - 1 ? (
|
||||
<BreadcrumbPage>
|
||||
{item.icon && <item.icon className="size-4 mr-1" />}
|
||||
{item.label}
|
||||
</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink href={item.href}>
|
||||
{item.icon && <item.icon className="size-4 mr-1" />}
|
||||
{item.label}
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
|
@ -2,8 +2,8 @@
|
|||
import '../styles/global.css'
|
||||
import '../styles/katex.css'
|
||||
|
||||
import '@fontsource/geist-sans'
|
||||
import '@fontsource/geist-mono'
|
||||
import '@fontsource/geist-sans'
|
||||
|
||||
import { ViewTransitions } from 'astro:transitions'
|
||||
|
||||
|
@ -28,28 +28,14 @@ const { title, description, image = '/static/twitter-card.png' } = Astro.props
|
|||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicons/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/favicons/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<link rel="shortcut icon" href="/favicons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="msapplication-config" content="/favicons/browserconfig.xml" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#121212" />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
---
|
||||
import { ModeToggle } from '@/components/ui/mode-toggle'
|
||||
import Container from '@components/Container.astro'
|
||||
import Link from '@components/Link.astro'
|
||||
import { SITE } from '@consts'
|
||||
import { ModeToggle } from '@/components/ui/mode-toggle'
|
||||
import { NAV_LINKS, SITE } from '@consts'
|
||||
import { Image } from 'astro:assets'
|
||||
import logo from '../../public/static/logo.svg'
|
||||
|
||||
const items = [
|
||||
{ href: '/blog', label: 'blog' },
|
||||
{ href: '/authors', label: 'authors' },
|
||||
{ href: '/about', label: 'about' },
|
||||
]
|
||||
---
|
||||
|
||||
<header
|
||||
|
@ -29,7 +23,7 @@ const items = [
|
|||
<div class="flex items-center gap-4">
|
||||
<nav class="flex items-center gap-4 text-sm sm:gap-6">
|
||||
{
|
||||
items.map((item) => (
|
||||
NAV_LINKS.map((item) => (
|
||||
<Link
|
||||
href={item.href}
|
||||
class="capitalize text-foreground/60 transition-colors hover:text-foreground/80"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
import Link from '@components/Link.astro'
|
||||
import { ArrowLeft, ArrowRight } from 'lucide-react'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Link from '@components/Link.astro'
|
||||
import { ArrowLeft, ArrowRight } from 'lucide-react'
|
||||
|
||||
const { prevPost, nextPost } = Astro.props
|
||||
---
|
||||
|
||||
<div class="mt-8 flex justify-between gap-4">
|
||||
<div class="flex justify-between gap-4">
|
||||
<Link
|
||||
href={prevPost ? `/blog/${prevPost.slug}` : '#'}
|
||||
class={cn(
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
import { Twitter, Github, Linkedin, Mail, Rss } from 'lucide-react'
|
||||
import { SITE } from '@consts'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
import Link from '@components/Link.astro'
|
||||
import { SITE } from '@consts'
|
||||
import { Github, Linkedin, Mail, Rss, Twitter } from 'lucide-react'
|
||||
---
|
||||
|
||||
<ul class="not-prose flex flex-wrap gap-2" role="list">
|
||||
|
|
|
@ -27,7 +27,7 @@ function buildToc(headings: Heading[]) {
|
|||
}
|
||||
---
|
||||
|
||||
<details open class="group mb-8 block rounded-xl border p-4 xl:hidden">
|
||||
<details open class="group block rounded-xl border p-4 xl:hidden">
|
||||
<summary
|
||||
class="flex cursor-pointer items-center justify-between text-xl font-semibold"
|
||||
>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
import type { Heading } from './TableOfContents.astro'
|
||||
import Link from './Link.astro'
|
||||
import type { Heading } from './TableOfContents.astro'
|
||||
|
||||
const { heading } = Astro.props
|
||||
---
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { type VariantProps, cva } from 'class-variance-authority'
|
||||
import { Hash } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring focus:ring-ring',
|
||||
{
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<'nav'> & {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import * as React from 'react'
|
||||
|
||||
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'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import * as React from 'react'
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
import {
|
||||
CheckIcon,
|
||||
|
@ -6,8 +7,6 @@ import {
|
|||
} from '@radix-ui/react-icons'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
@ -49,7 +48,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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',
|
||||
'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg 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}
|
||||
|
@ -67,7 +66,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
'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,
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { Laptop, Moon, Sun } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
@ -8,6 +5,8 @@ import {
|
|||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Laptop, Moon, Sun } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
export function ModeToggle() {
|
||||
const [theme, setThemeState] = React.useState<
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue