feat: initialize tailwindui/catalyst
This commit is contained in:
parent
00aecab225
commit
57c688c4e7
30 changed files with 2671 additions and 139 deletions
138
src/components/ui/sidebar.tsx
Normal file
138
src/components/ui/sidebar.tsx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
'use client'
|
||||
|
||||
import * as Headless from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { LayoutGroup, motion } from 'framer-motion'
|
||||
import React, { Fragment, forwardRef, useId } from 'react'
|
||||
import { TouchTarget } from './button'
|
||||
import { Link } from './link'
|
||||
|
||||
export function Sidebar({ className, ...props }: React.ComponentPropsWithoutRef<'nav'>) {
|
||||
return <nav {...props} className={clsx(className, 'flex h-full min-h-0 flex-col')} />
|
||||
}
|
||||
|
||||
export function SidebarHeader({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-col border-b border-zinc-950/5 p-4 dark:border-white/5 [&>[data-slot=section]+[data-slot=section]]:mt-2.5'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarBody({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-1 flex-col overflow-y-auto p-4 [&>[data-slot=section]+[data-slot=section]]:mt-8'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarFooter({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-col border-t border-zinc-950/5 p-4 dark:border-white/5 [&>[data-slot=section]+[data-slot=section]]:mt-2.5'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<LayoutGroup id={id}>
|
||||
<div {...props} data-slot="section" className={clsx(className, 'flex flex-col gap-0.5')} />
|
||||
</LayoutGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarDivider({ className, ...props }: React.ComponentPropsWithoutRef<'hr'>) {
|
||||
return <hr {...props} className={clsx(className, 'my-4 border-t border-zinc-950/5 lg:-mx-4 dark:border-white/5')} />
|
||||
}
|
||||
|
||||
export function SidebarSpacer({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return <div aria-hidden="true" {...props} className={clsx(className, 'mt-8 flex-1')} />
|
||||
}
|
||||
|
||||
export function SidebarHeading({ className, ...props }: React.ComponentPropsWithoutRef<'h3'>) {
|
||||
return (
|
||||
<h3 {...props} className={clsx(className, 'mb-1 px-2 text-xs/6 font-medium text-zinc-500 dark:text-zinc-400')} />
|
||||
)
|
||||
}
|
||||
|
||||
export const SidebarItem = forwardRef(function SidebarItem(
|
||||
{
|
||||
current,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: { current?: boolean; className?: string; children: React.ReactNode } & (
|
||||
| Omit<Headless.ButtonProps, 'className'>
|
||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'type' | 'className'>
|
||||
),
|
||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
|
||||
) {
|
||||
let classes = clsx(
|
||||
// Base
|
||||
'flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium text-zinc-950 sm:py-2 sm:text-sm/5',
|
||||
// Leading icon/icon-only
|
||||
'data-[slot=icon]:*:size-6 data-[slot=icon]:*:shrink-0 data-[slot=icon]:*:fill-zinc-500 sm:data-[slot=icon]:*:size-5',
|
||||
// Trailing icon (down chevron or similar)
|
||||
'data-[slot=icon]:last:*:ml-auto data-[slot=icon]:last:*:size-5 sm:data-[slot=icon]:last:*:size-4',
|
||||
// Avatar
|
||||
'data-[slot=avatar]:*:-m-0.5 data-[slot=avatar]:*:size-7 data-[slot=avatar]:*:[--ring-opacity:10%] sm:data-[slot=avatar]:*:size-6',
|
||||
// Hover
|
||||
'data-[hover]:bg-zinc-950/5 data-[slot=icon]:*:data-[hover]:fill-zinc-950',
|
||||
// Active
|
||||
'data-[active]:bg-zinc-950/5 data-[slot=icon]:*:data-[active]:fill-zinc-950',
|
||||
// Current
|
||||
'data-[slot=icon]:*:data-[current]:fill-zinc-950',
|
||||
// Dark mode
|
||||
'dark:text-white dark:data-[slot=icon]:*:fill-zinc-400',
|
||||
'dark:data-[hover]:bg-white/5 dark:data-[slot=icon]:*:data-[hover]:fill-white',
|
||||
'dark:data-[active]:bg-white/5 dark:data-[slot=icon]:*:data-[active]:fill-white',
|
||||
'dark:data-[slot=icon]:*:data-[current]:fill-white'
|
||||
)
|
||||
|
||||
return (
|
||||
<span className={clsx(className, 'relative')}>
|
||||
{current && (
|
||||
<motion.span
|
||||
layoutId="current-indicator"
|
||||
className="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-zinc-950 dark:bg-white"
|
||||
/>
|
||||
)}
|
||||
{'href' in props ? (
|
||||
<Headless.CloseButton as={Fragment} ref={ref}>
|
||||
<Link className={classes} {...props} data-current={current ? 'true' : undefined}>
|
||||
<TouchTarget>{children}</TouchTarget>
|
||||
</Link>
|
||||
</Headless.CloseButton>
|
||||
) : (
|
||||
<Headless.Button
|
||||
{...props}
|
||||
className={clsx('cursor-default', classes)}
|
||||
data-current={current ? 'true' : undefined}
|
||||
ref={ref}
|
||||
>
|
||||
<TouchTarget>{children}</TouchTarget>
|
||||
</Headless.Button>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
|
||||
export function SidebarLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return <span {...props} className={clsx(className, 'truncate')} />
|
||||
}
|
||||
Reference in a new issue