1
0
Fork 0
This repository has been archived on 2025-09-02. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
mareshq-midgard/src/components/ui/sidebar.tsx

138 lines
4.8 KiB
TypeScript

'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')} />
}