lint: fix linting and build
This commit is contained in:
parent
f291efcc18
commit
ebe686b47e
@ -13,6 +13,7 @@ const eslintConfig = [
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
|
||||
@ -5,8 +5,7 @@ import { redirect } from 'next/navigation'
|
||||
import { LoginForm } from '@/components/login-form'
|
||||
import Image from 'next/image'
|
||||
|
||||
type Props = {}
|
||||
const LoginPage = async (props: Props) => {
|
||||
const LoginPage = async () => {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const headers = await nextHeaders()
|
||||
const userResult = await payload.auth({ headers })
|
||||
|
||||
@ -4,7 +4,7 @@ import config from '@/payload.config'
|
||||
import React from 'react'
|
||||
|
||||
import UserFeed from '@/components/Feed/UserFeed'
|
||||
import { Book, HoldRequest, Repository } from '@/payload-types'
|
||||
import { Book, Repository } from '@/payload-types'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { TextShimmer } from '@/components/ui/text-shimmer'
|
||||
import { LoginForm } from '@/components/login-form'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Book, Copy, HoldRequest, Repository, User } from '@/payload-types'
|
||||
import type { Book, HoldRequest, Repository, User } from '@/payload-types'
|
||||
import { getPayload, PaginatedDocs } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card'
|
||||
|
||||
@ -13,7 +13,7 @@ const HoldRequestNotifications = (props: Props) => {
|
||||
|
||||
const holdRequestsByRepoElements = repos?.docs.map((r) => {
|
||||
return (
|
||||
<ul role="list" className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<ul key={r.id} role="list" className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{r.holdRequests?.docs?.map((h) => {
|
||||
const hold = h as HoldRequest
|
||||
const book = hold.book as Book
|
||||
|
||||
@ -51,7 +51,7 @@ const SearchBooksInlineForm = (props: Props) => {
|
||||
try {
|
||||
const searchResults = await searchBooks(values)
|
||||
if (searchResults && onSearchResult) onSearchResult(searchResults)
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
toast('There was an issue with that search')
|
||||
} finally {
|
||||
setIsSearching(false)
|
||||
|
||||
@ -58,7 +58,7 @@ export default function SiteNavigation(props: { children: React.ReactNode }) {
|
||||
setUser(userRequest.user)
|
||||
console.log(userRequest.user)
|
||||
})
|
||||
}, [user?.id])
|
||||
}, [user, setUser])
|
||||
|
||||
const profilePicture = user?.profilePicture as Media | undefined
|
||||
const initials = user?.firstName
|
||||
|
||||
@ -30,7 +30,9 @@ export function Avatar({
|
||||
'inline-grid shrink-0 align-middle [--avatar-radius:20%] *:col-start-1 *:row-start-1',
|
||||
'outline -outline-offset-1 outline-black/10 dark:outline-white/10',
|
||||
// Border radius
|
||||
square ? 'rounded-(--avatar-radius) *:rounded-(--avatar-radius)' : 'rounded-full *:rounded-full'
|
||||
square
|
||||
? 'rounded-(--avatar-radius) *:rounded-(--avatar-radius)'
|
||||
: 'rounded-full *:rounded-full',
|
||||
)}
|
||||
>
|
||||
{initials && (
|
||||
@ -40,7 +42,14 @@ export function Avatar({
|
||||
aria-hidden={alt ? undefined : 'true'}
|
||||
>
|
||||
{alt && <title>{alt}</title>}
|
||||
<text x="50%" y="50%" alignmentBaseline="middle" dominantBaseline="middle" textAnchor="middle" dy=".125em">
|
||||
<text
|
||||
x="50%"
|
||||
y="50%"
|
||||
alignmentBaseline="middle"
|
||||
dominantBaseline="middle"
|
||||
textAnchor="middle"
|
||||
dy=".125em"
|
||||
>
|
||||
{initials}
|
||||
</text>
|
||||
</svg>
|
||||
@ -59,13 +68,16 @@ export const AvatarButton = forwardRef(function AvatarButton(
|
||||
className,
|
||||
...props
|
||||
}: AvatarProps &
|
||||
(Omit<Headless.ButtonProps, 'as' | 'className'> | Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>),
|
||||
ref: React.ForwardedRef<HTMLElement>
|
||||
(
|
||||
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
||||
),
|
||||
ref: React.ForwardedRef<HTMLElement>,
|
||||
) {
|
||||
let classes = clsx(
|
||||
const classes = clsx(
|
||||
className,
|
||||
square ? 'rounded-[20%]' : 'rounded-full',
|
||||
'relative inline-grid focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
|
||||
'relative inline-grid focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500',
|
||||
)
|
||||
|
||||
return 'href' in props ? (
|
||||
|
||||
@ -36,14 +36,18 @@ const colors = {
|
||||
|
||||
type BadgeProps = { color?: keyof typeof colors }
|
||||
|
||||
export function Badge({ color = 'zinc', className, ...props }: BadgeProps & React.ComponentPropsWithoutRef<'span'>) {
|
||||
export function Badge({
|
||||
color = 'zinc',
|
||||
className,
|
||||
...props
|
||||
}: BadgeProps & React.ComponentPropsWithoutRef<'span'>) {
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'inline-flex items-center gap-x-1.5 rounded-md px-1.5 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline',
|
||||
colors[color]
|
||||
colors[color],
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -59,11 +63,11 @@ export const BadgeButton = forwardRef(function BadgeButton(
|
||||
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
||||
),
|
||||
ref: React.ForwardedRef<HTMLElement>
|
||||
ref: React.ForwardedRef<HTMLElement>,
|
||||
) {
|
||||
let classes = clsx(
|
||||
const classes = clsx(
|
||||
className,
|
||||
'group relative inline-flex rounded-md focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
|
||||
'group relative inline-flex rounded-md focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500',
|
||||
)
|
||||
|
||||
return 'href' in props ? (
|
||||
|
||||
@ -169,12 +169,16 @@ type ButtonProps = (
|
||||
|
||||
export const Button = forwardRef(function Button(
|
||||
{ color, outline, plain, className, children, ...props }: ButtonProps,
|
||||
ref: React.ForwardedRef<HTMLElement>
|
||||
ref: React.ForwardedRef<HTMLElement>,
|
||||
) {
|
||||
let classes = clsx(
|
||||
const classes = clsx(
|
||||
className,
|
||||
styles.base,
|
||||
outline ? styles.outline : plain ? styles.plain : clsx(styles.solid, styles.colors[color ?? 'dark/zinc'])
|
||||
outline
|
||||
? styles.outline
|
||||
: plain
|
||||
? styles.plain
|
||||
: clsx(styles.solid, styles.colors[color ?? 'dark/zinc']),
|
||||
)
|
||||
|
||||
return 'href' in props ? (
|
||||
|
||||
@ -24,18 +24,27 @@ export function Combobox<T>({
|
||||
autoFocus?: boolean
|
||||
'aria-label'?: string
|
||||
children: (value: NonNullable<T>) => React.ReactElement
|
||||
} & Omit<Headless.ComboboxProps<T, false>, 'as' | 'multiple' | 'children'> & { anchor?: 'top' | 'bottom' }) {
|
||||
} & Omit<Headless.ComboboxProps<T, false>, 'as' | 'multiple' | 'children'> & {
|
||||
anchor?: 'top' | 'bottom'
|
||||
}) {
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
const filteredOptions =
|
||||
query === ''
|
||||
? options
|
||||
: options.filter((option) =>
|
||||
filter ? filter(option, query) : displayValue(option)?.toLowerCase().includes(query.toLowerCase())
|
||||
filter
|
||||
? filter(option, query)
|
||||
: displayValue(option)?.toLowerCase().includes(query.toLowerCase()),
|
||||
)
|
||||
|
||||
return (
|
||||
<Headless.Combobox {...props} multiple={false} virtual={{ options: filteredOptions }} onClose={() => setQuery('')}>
|
||||
<Headless.Combobox
|
||||
{...props}
|
||||
multiple={false}
|
||||
virtual={{ options: filteredOptions }}
|
||||
onClose={() => setQuery('')}
|
||||
>
|
||||
<span
|
||||
data-slot="control"
|
||||
className={clsx([
|
||||
@ -90,8 +99,18 @@ export function Combobox<T>({
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M5.75 10.75L8 13L10.25 10.75" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10.25 5.25L8 3L5.75 5.25" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M5.75 10.75L8 13L10.25 10.75"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.25 5.25L8 3L5.75 5.25"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Headless.ComboboxButton>
|
||||
</span>
|
||||
@ -112,7 +131,7 @@ export function Combobox<T>({
|
||||
// Shadows
|
||||
'shadow-lg ring-1 ring-zinc-950/10 dark:ring-white/10 dark:ring-inset',
|
||||
// Transitions
|
||||
'transition-opacity duration-100 ease-in data-closed:data-leave:opacity-0 data-transition:pointer-events-none'
|
||||
'transition-opacity duration-100 ease-in data-closed:data-leave:opacity-0 data-transition:pointer-events-none',
|
||||
)}
|
||||
>
|
||||
{({ option }) => children(option)}
|
||||
@ -129,7 +148,7 @@ export function ComboboxOption<T>({
|
||||
Headless.ComboboxOptionProps<'div', T>,
|
||||
'as' | 'className'
|
||||
>) {
|
||||
let sharedClasses = clsx(
|
||||
const sharedClasses = clsx(
|
||||
// Base
|
||||
'flex min-w-0 items-center',
|
||||
// Icons
|
||||
@ -137,7 +156,7 @@ export function ComboboxOption<T>({
|
||||
'*:data-[slot=icon]:text-zinc-500 group-data-focus/option:*:data-[slot=icon]:text-white dark:*:data-[slot=icon]:text-zinc-400',
|
||||
'forced-colors:*:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focus/option:*:data-[slot=icon]:text-[Canvas]',
|
||||
// Avatars
|
||||
'*:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:size-5'
|
||||
'*:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:size-5',
|
||||
)
|
||||
|
||||
return (
|
||||
@ -153,7 +172,7 @@ export function ComboboxOption<T>({
|
||||
// Forced colors mode
|
||||
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
|
||||
// Disabled
|
||||
'data-disabled:opacity-50'
|
||||
'data-disabled:opacity-50',
|
||||
)}
|
||||
>
|
||||
<span className={clsx(className, sharedClasses)}>{children}</span>
|
||||
@ -170,16 +189,25 @@ export function ComboboxOption<T>({
|
||||
}
|
||||
|
||||
export function ComboboxLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return <span {...props} className={clsx(className, 'ml-2.5 truncate first:ml-0 sm:ml-2 sm:first:ml-0')} />
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
className={clsx(className, 'ml-2.5 truncate first:ml-0 sm:ml-2 sm:first:ml-0')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function ComboboxDescription({ className, children, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
export function ComboboxDescription({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-1 overflow-hidden text-zinc-500 group-data-focus/option:text-white before:w-2 before:min-w-0 before:shrink dark:text-zinc-400'
|
||||
'flex flex-1 overflow-hidden text-zinc-500 group-data-focus/option:text-white before:w-2 before:min-w-0 before:shrink dark:text-zinc-400',
|
||||
)}
|
||||
>
|
||||
<span className="flex-1 truncate">{children}</span>
|
||||
|
||||
@ -44,7 +44,7 @@ export function DropdownMenu({
|
||||
// Define grid at the menu level if subgrid is supported
|
||||
'supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[auto_1fr_1.5rem_0.5rem_auto]',
|
||||
// Transitions
|
||||
'transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0'
|
||||
'transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -57,7 +57,7 @@ export function DropdownItem({
|
||||
| Omit<Headless.MenuItemProps<'button'>, 'as' | 'className'>
|
||||
| Omit<Headless.MenuItemProps<typeof Link>, 'as' | 'className'>
|
||||
)) {
|
||||
let classes = clsx(
|
||||
const classes = clsx(
|
||||
className,
|
||||
// Base styles
|
||||
'group cursor-default rounded-lg px-3.5 py-2.5 focus:outline-hidden sm:px-3 sm:py-1.5',
|
||||
@ -75,7 +75,7 @@ export function DropdownItem({
|
||||
'*:data-[slot=icon]:col-start-1 *:data-[slot=icon]:row-start-1 *:data-[slot=icon]:mr-2.5 *:data-[slot=icon]:-ml-0.5 *:data-[slot=icon]:size-5 sm:*:data-[slot=icon]:mr-2 sm:*:data-[slot=icon]:size-4',
|
||||
'*:data-[slot=icon]:text-zinc-500 data-focus:*:data-[slot=icon]:text-white dark:*:data-[slot=icon]:text-zinc-400 dark:data-focus:*:data-[slot=icon]:text-white',
|
||||
// Avatar
|
||||
'*:data-[slot=avatar]:mr-2.5 *:data-[slot=avatar]:-ml-1 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:mr-2 sm:*:data-[slot=avatar]:size-5'
|
||||
'*:data-[slot=avatar]:mr-2.5 *:data-[slot=avatar]:-ml-1 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:mr-2 sm:*:data-[slot=avatar]:size-5',
|
||||
)
|
||||
|
||||
return 'href' in props ? (
|
||||
@ -99,7 +99,7 @@ export function DropdownSection({
|
||||
className={clsx(
|
||||
className,
|
||||
// Define grid at the section level instead of the item level if subgrid is supported
|
||||
'col-span-full supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[auto_1fr_1.5rem_0.5rem_auto]'
|
||||
'col-span-full supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[auto_1fr_1.5rem_0.5rem_auto]',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -114,7 +114,7 @@ export function DropdownHeading({
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'col-span-full grid grid-cols-[1fr_auto] gap-x-12 px-3.5 pt-2 pb-1 text-sm/5 font-medium text-zinc-500 sm:px-3 sm:text-xs/5 dark:text-zinc-400'
|
||||
'col-span-full grid grid-cols-[1fr_auto] gap-x-12 px-3.5 pt-2 pb-1 text-sm/5 font-medium text-zinc-500 sm:px-3 sm:text-xs/5 dark:text-zinc-400',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -129,7 +129,7 @@ export function DropdownDivider({
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'col-span-full mx-3.5 my-1 h-px border-0 bg-zinc-950/5 sm:mx-3 dark:bg-white/10 forced-colors:bg-[CanvasText]'
|
||||
'col-span-full mx-3.5 my-1 h-px border-0 bg-zinc-950/5 sm:mx-3 dark:bg-white/10 forced-colors:bg-[CanvasText]',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -140,7 +140,12 @@ export function DropdownLabel({
|
||||
...props
|
||||
}: { className?: string } & Omit<Headless.LabelProps, 'as' | 'className'>) {
|
||||
return (
|
||||
<Headless.Label {...props} data-slot="label" className={clsx(className, 'col-start-2 row-start-1')} {...props} />
|
||||
<Headless.Label
|
||||
{...props}
|
||||
data-slot="label"
|
||||
className={clsx(className, 'col-start-2 row-start-1')}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -154,7 +159,7 @@ export function DropdownDescription({
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'col-span-2 col-start-2 row-start-2 text-sm/5 text-zinc-500 group-data-focus:text-white sm:text-xs/5 dark:text-zinc-400 forced-colors:group-data-focus:text-[HighlightText]'
|
||||
'col-span-2 col-start-2 row-start-2 text-sm/5 text-zinc-500 group-data-focus:text-white sm:text-xs/5 dark:text-zinc-400 forced-colors:group-data-focus:text-[HighlightText]',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -164,7 +169,10 @@ export function DropdownShortcut({
|
||||
keys,
|
||||
className,
|
||||
...props
|
||||
}: { keys: string | string[]; className?: string } & Omit<Headless.DescriptionProps<'kbd'>, 'as' | 'className'>) {
|
||||
}: { keys: string | string[]; className?: string } & Omit<
|
||||
Headless.DescriptionProps<'kbd'>,
|
||||
'as' | 'className'
|
||||
>) {
|
||||
return (
|
||||
<Headless.Description
|
||||
as="kbd"
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
type HeadingProps = { level?: 1 | 2 | 3 | 4 | 5 | 6 } & React.ComponentPropsWithoutRef<
|
||||
'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||
>
|
||||
|
||||
export function Heading({ className, level = 1, ...props }: HeadingProps) {
|
||||
let Element: `h${typeof level}` = `h${level}`
|
||||
|
||||
return (
|
||||
<Element
|
||||
{...props}
|
||||
className={clsx(className, 'text-2xl/8 font-semibold text-zinc-950 sm:text-xl/8 dark:text-white')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Subheading({ className, level = 2, ...props }: HeadingProps) {
|
||||
let Element: `h${typeof level}` = `h${level}`
|
||||
|
||||
return (
|
||||
<Element
|
||||
{...props}
|
||||
className={clsx(className, 'text-base/7 font-semibold text-zinc-950 sm:text-sm/6 dark:text-white')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as Headless from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
export function Listbox<T>({
|
||||
className,
|
||||
placeholder,
|
||||
autoFocus,
|
||||
'aria-label': ariaLabel,
|
||||
children: options,
|
||||
...props
|
||||
}: {
|
||||
className?: string
|
||||
placeholder?: React.ReactNode
|
||||
autoFocus?: boolean
|
||||
'aria-label'?: string
|
||||
children?: React.ReactNode
|
||||
} & Omit<Headless.ListboxProps<typeof Fragment, T>, 'as' | 'multiple'>) {
|
||||
return (
|
||||
<Headless.Listbox {...props} multiple={false}>
|
||||
<Headless.ListboxButton
|
||||
autoFocus={autoFocus}
|
||||
data-slot="control"
|
||||
aria-label={ariaLabel}
|
||||
className={clsx([
|
||||
className,
|
||||
// Basic layout
|
||||
'group relative block w-full',
|
||||
// Background color + shadow applied to inset pseudo element, so shadow blends with border in light mode
|
||||
'before:absolute before:inset-px before:rounded-[calc(var(--radius-lg)-1px)] before:bg-white before:shadow-sm',
|
||||
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
|
||||
'dark:before:hidden',
|
||||
// Hide default focus styles
|
||||
'focus:outline-hidden',
|
||||
// Focus ring
|
||||
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset data-focus:after:ring-2 data-focus:after:ring-blue-500',
|
||||
// Disabled state
|
||||
'data-disabled:opacity-50 data-disabled:before:bg-zinc-950/5 data-disabled:before:shadow-none',
|
||||
])}
|
||||
>
|
||||
<Headless.ListboxSelectedOption
|
||||
as="span"
|
||||
options={options}
|
||||
placeholder={placeholder && <span className="block truncate text-zinc-500">{placeholder}</span>}
|
||||
className={clsx([
|
||||
// Basic layout
|
||||
'relative block w-full appearance-none rounded-lg py-[calc(--spacing(2.5)-1px)] sm:py-[calc(--spacing(1.5)-1px)]',
|
||||
// Set minimum height for when no value is selected
|
||||
'min-h-11 sm:min-h-9',
|
||||
// Horizontal padding
|
||||
'pr-[calc(--spacing(7)-1px)] pl-[calc(--spacing(3.5)-1px)] sm:pl-[calc(--spacing(3)-1px)]',
|
||||
// Typography
|
||||
'text-left text-base/6 text-zinc-950 placeholder:text-zinc-500 sm:text-sm/6 dark:text-white forced-colors:text-[CanvasText]',
|
||||
// Border
|
||||
'border border-zinc-950/10 group-data-active:border-zinc-950/20 group-data-hover:border-zinc-950/20 dark:border-white/10 dark:group-data-active:border-white/20 dark:group-data-hover:border-white/20',
|
||||
// Background color
|
||||
'bg-transparent dark:bg-white/5',
|
||||
// Invalid state
|
||||
'group-data-invalid:border-red-500 group-data-hover:group-data-invalid:border-red-500 dark:group-data-invalid:border-red-600 dark:data-hover:group-data-invalid:border-red-600',
|
||||
// Disabled state
|
||||
'group-data-disabled:border-zinc-950/20 group-data-disabled:opacity-100 dark:group-data-disabled:border-white/15 dark:group-data-disabled:bg-white/[2.5%] dark:group-data-disabled:data-hover:border-white/15',
|
||||
])}
|
||||
/>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
className="size-5 stroke-zinc-500 group-data-disabled:stroke-zinc-600 sm:size-4 dark:stroke-zinc-400 forced-colors:stroke-[CanvasText]"
|
||||
viewBox="0 0 16 16"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M5.75 10.75L8 13L10.25 10.75" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10.25 5.25L8 3L5.75 5.25" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
</Headless.ListboxButton>
|
||||
<Headless.ListboxOptions
|
||||
transition
|
||||
anchor="selection start"
|
||||
className={clsx(
|
||||
// Anchor positioning
|
||||
'[--anchor-offset:-1.625rem] [--anchor-padding:--spacing(4)] sm:[--anchor-offset:-1.375rem]',
|
||||
// Base styles
|
||||
'isolate w-max min-w-[calc(var(--button-width)+1.75rem)] scroll-py-1 rounded-xl p-1 select-none',
|
||||
// Invisible border that is only visible in `forced-colors` mode for accessibility purposes
|
||||
'outline outline-transparent focus:outline-hidden',
|
||||
// Handle scrolling when menu won't fit in viewport
|
||||
'overflow-y-scroll overscroll-contain',
|
||||
// Popover background
|
||||
'bg-white/75 backdrop-blur-xl dark:bg-zinc-800/75',
|
||||
// Shadows
|
||||
'shadow-lg ring-1 ring-zinc-950/10 dark:ring-white/10 dark:ring-inset',
|
||||
// Transitions
|
||||
'transition-opacity duration-100 ease-in data-closed:data-leave:opacity-0 data-transition:pointer-events-none'
|
||||
)}
|
||||
>
|
||||
{options}
|
||||
</Headless.ListboxOptions>
|
||||
</Headless.Listbox>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListboxOption<T>({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: { className?: string; children?: React.ReactNode } & Omit<
|
||||
Headless.ListboxOptionProps<'div', T>,
|
||||
'as' | 'className'
|
||||
>) {
|
||||
let sharedClasses = clsx(
|
||||
// Base
|
||||
'flex min-w-0 items-center',
|
||||
// Icons
|
||||
'*:data-[slot=icon]:size-5 *:data-[slot=icon]:shrink-0 sm:*:data-[slot=icon]:size-4',
|
||||
'*:data-[slot=icon]:text-zinc-500 group-data-focus/option:*:data-[slot=icon]:text-white dark:*:data-[slot=icon]:text-zinc-400',
|
||||
'forced-colors:*:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focus/option:*:data-[slot=icon]:text-[Canvas]',
|
||||
// Avatars
|
||||
'*:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:size-5'
|
||||
)
|
||||
|
||||
return (
|
||||
<Headless.ListboxOption as={Fragment} {...props}>
|
||||
{({ selectedOption }) => {
|
||||
if (selectedOption) {
|
||||
return <div className={clsx(className, sharedClasses)}>{children}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
// Basic layout
|
||||
'group/option grid cursor-default grid-cols-[--spacing(5)_1fr] items-baseline gap-x-2 rounded-lg py-2.5 pr-3.5 pl-2 sm:grid-cols-[--spacing(4)_1fr] sm:py-1.5 sm:pr-3 sm:pl-1.5',
|
||||
// Typography
|
||||
'text-base/6 text-zinc-950 sm:text-sm/6 dark:text-white forced-colors:text-[CanvasText]',
|
||||
// Focus
|
||||
'outline-hidden data-focus:bg-blue-500 data-focus:text-white',
|
||||
// Forced colors mode
|
||||
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
|
||||
// Disabled
|
||||
'data-disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="relative hidden size-5 self-center stroke-current group-data-selected/option:inline sm:size-4"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4 8.5l3 3L12 4" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className={clsx(className, sharedClasses, 'col-start-2')}>{children}</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Headless.ListboxOption>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListboxLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return <span {...props} className={clsx(className, 'ml-2.5 truncate first:ml-0 sm:ml-2 sm:first:ml-0')} />
|
||||
}
|
||||
|
||||
export function ListboxDescription({ className, children, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-1 overflow-hidden text-zinc-500 group-data-focus/option:text-white before:w-2 before:min-w-0 before:shrink dark:text-zinc-400'
|
||||
)}
|
||||
>
|
||||
<span className="flex-1 truncate">{children}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -12,11 +12,17 @@ export function Navbar({ className, ...props }: React.ComponentPropsWithoutRef<'
|
||||
}
|
||||
|
||||
export function NavbarDivider({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return <div aria-hidden="true" {...props} className={clsx(className, 'h-6 w-px bg-zinc-950/10 dark:bg-white/10')} />
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
className={clsx(className, 'h-6 w-px bg-zinc-950/10 dark:bg-white/10')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function NavbarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
let id = useId()
|
||||
const id = useId()
|
||||
|
||||
return (
|
||||
<LayoutGroup id={id}>
|
||||
@ -39,9 +45,9 @@ export const NavbarItem = forwardRef(function NavbarItem(
|
||||
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
||||
),
|
||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
|
||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>,
|
||||
) {
|
||||
let classes = clsx(
|
||||
const classes = clsx(
|
||||
// Base
|
||||
'relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 font-medium text-zinc-950 sm:text-sm/5',
|
||||
// Leading icon/icon-only
|
||||
@ -57,7 +63,7 @@ export const NavbarItem = forwardRef(function NavbarItem(
|
||||
// Dark mode
|
||||
'dark:text-white dark:*:data-[slot=icon]:fill-zinc-400',
|
||||
'dark:data-hover:bg-white/5 dark:data-hover:*:data-[slot=icon]:fill-white',
|
||||
'dark:data-active:bg-white/5 dark:data-active:*:data-[slot=icon]:fill-white'
|
||||
'dark:data-active:bg-white/5 dark:data-active:*:data-[slot=icon]:fill-white',
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
import type React from 'react'
|
||||
import { Button } from './button'
|
||||
|
||||
export function Pagination({
|
||||
'aria-label': ariaLabel = 'Page navigation',
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'nav'>) {
|
||||
return <nav aria-label={ariaLabel} {...props} className={clsx(className, 'flex gap-x-2')} />
|
||||
}
|
||||
|
||||
export function PaginationPrevious({
|
||||
href = null,
|
||||
className,
|
||||
children = 'Previous',
|
||||
}: React.PropsWithChildren<{ href?: string | null; className?: string }>) {
|
||||
return (
|
||||
<span className={clsx(className, 'grow basis-0')}>
|
||||
<Button {...(href === null ? { disabled: true } : { href })} plain aria-label="Previous page">
|
||||
<svg className="stroke-current" data-slot="icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M2.75 8H13.25M2.75 8L5.25 5.5M2.75 8L5.25 10.5"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{children}
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function PaginationNext({
|
||||
href = null,
|
||||
className,
|
||||
children = 'Next',
|
||||
}: React.PropsWithChildren<{ href?: string | null; className?: string }>) {
|
||||
return (
|
||||
<span className={clsx(className, 'flex grow basis-0 justify-end')}>
|
||||
<Button {...(href === null ? { disabled: true } : { href })} plain aria-label="Next page">
|
||||
{children}
|
||||
<svg className="stroke-current" data-slot="icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M13.25 8L2.75 8M13.25 8L10.75 10.5M13.25 8L10.75 5.5"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function PaginationList({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return <span {...props} className={clsx(className, 'hidden items-baseline gap-x-2 sm:flex')} />
|
||||
}
|
||||
|
||||
export function PaginationPage({
|
||||
href,
|
||||
className,
|
||||
current = false,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ href: string; className?: string; current?: boolean }>) {
|
||||
return (
|
||||
<Button
|
||||
href={href}
|
||||
plain
|
||||
aria-label={`Page ${children}`}
|
||||
aria-current={current ? 'page' : undefined}
|
||||
className={clsx(
|
||||
className,
|
||||
'min-w-[2.25rem] before:absolute before:-inset-px before:rounded-lg',
|
||||
current && 'before:bg-zinc-950/5 dark:before:bg-white/10'
|
||||
)}
|
||||
>
|
||||
<span className="-mx-0.5">{children}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function PaginationGap({
|
||||
className,
|
||||
children = <>…</>,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'span'>) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'w-[2.25rem] text-center text-sm/6 font-semibold text-zinc-950 select-none dark:text-white'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -20,7 +20,11 @@ function CloseMenuIcon() {
|
||||
)
|
||||
}
|
||||
|
||||
function MobileSidebar({ open, close, children }: React.PropsWithChildren<{ open: boolean; close: () => void }>) {
|
||||
function MobileSidebar({
|
||||
open,
|
||||
close,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ open: boolean; close: () => void }>) {
|
||||
return (
|
||||
<Headless.Dialog open={open} onClose={close} className="lg:hidden">
|
||||
<Headless.DialogBackdrop
|
||||
@ -49,7 +53,7 @@ export function SidebarLayout({
|
||||
sidebar,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
||||
let [showSidebar, setShowSidebar] = useState(false)
|
||||
const [showSidebar, setShowSidebar] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative isolate flex min-h-svh w-full bg-white max-lg:flex-col lg:bg-zinc-100 dark:bg-zinc-900 dark:lg:bg-zinc-950">
|
||||
|
||||
@ -17,7 +17,7 @@ export function SidebarHeader({ className, ...props }: React.ComponentPropsWitho
|
||||
{...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'
|
||||
'flex flex-col border-b border-zinc-950/5 p-4 dark:border-white/5 [&>[data-slot=section]+[data-slot=section]]:mt-2.5',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -29,7 +29,7 @@ export function SidebarBody({ className, ...props }: React.ComponentPropsWithout
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'flex flex-1 flex-col overflow-y-auto p-4 [&>[data-slot=section]+[data-slot=section]]:mt-8'
|
||||
'flex flex-1 flex-col overflow-y-auto p-4 [&>[data-slot=section]+[data-slot=section]]:mt-8',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@ -41,14 +41,14 @@ export function SidebarFooter({ className, ...props }: React.ComponentPropsWitho
|
||||
{...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'
|
||||
'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()
|
||||
const id = useId()
|
||||
|
||||
return (
|
||||
<LayoutGroup id={id}>
|
||||
@ -58,7 +58,12 @@ export function SidebarSection({ className, ...props }: React.ComponentPropsWith
|
||||
}
|
||||
|
||||
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')} />
|
||||
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'>) {
|
||||
@ -67,7 +72,13 @@ export function SidebarSpacer({ className, ...props }: React.ComponentPropsWitho
|
||||
|
||||
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')} />
|
||||
<h3
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'mb-1 px-2 text-xs/6 font-medium text-zinc-500 dark:text-zinc-400',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -81,9 +92,9 @@ export const SidebarItem = forwardRef(function SidebarItem(
|
||||
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
||||
| Omit<Headless.ButtonProps<typeof Link>, 'as' | 'className'>
|
||||
),
|
||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
|
||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>,
|
||||
) {
|
||||
let classes = clsx(
|
||||
const 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
|
||||
@ -102,7 +113,7 @@ export const SidebarItem = forwardRef(function SidebarItem(
|
||||
'dark:text-white dark:*:data-[slot=icon]:fill-zinc-400',
|
||||
'dark:data-hover:bg-white/5 dark:data-hover:*:data-[slot=icon]:fill-white',
|
||||
'dark:data-active:bg-white/5 dark:data-active:*:data-[slot=icon]:fill-white',
|
||||
'dark:data-current:*:data-[slot=icon]:fill-white'
|
||||
'dark:data-current:*:data-[slot=icon]:fill-white',
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@ -53,7 +53,7 @@ export function StackedLayout({
|
||||
sidebar,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
||||
let [showSidebar, setShowSidebar] = useState(false)
|
||||
const [showSidebar, setShowSidebar] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative isolate flex min-h-svh w-full flex-col lg:bg-zinc-100 dark:bg-zinc-900 dark:lg:bg-zinc-950">
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import type React from 'react'
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
import { Link } from './link'
|
||||
|
||||
const TableContext = createContext<{ bleed: boolean; dense: boolean; grid: boolean; striped: boolean }>({
|
||||
bleed: false,
|
||||
dense: false,
|
||||
grid: false,
|
||||
striped: false,
|
||||
})
|
||||
|
||||
export function Table({
|
||||
bleed = false,
|
||||
dense = false,
|
||||
grid = false,
|
||||
striped = false,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: { bleed?: boolean; dense?: boolean; grid?: boolean; striped?: boolean } & React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<TableContext.Provider value={{ bleed, dense, grid, striped } as React.ContextType<typeof TableContext>}>
|
||||
<div className="flow-root">
|
||||
<div {...props} className={clsx(className, '-mx-(--gutter) overflow-x-auto whitespace-nowrap')}>
|
||||
<div className={clsx('inline-block min-w-full align-middle', !bleed && 'sm:px-(--gutter)')}>
|
||||
<table className="min-w-full text-left text-sm/6 text-zinc-950 dark:text-white">{children}</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function TableHead({ className, ...props }: React.ComponentPropsWithoutRef<'thead'>) {
|
||||
return <thead {...props} className={clsx(className, 'text-zinc-500 dark:text-zinc-400')} />
|
||||
}
|
||||
|
||||
export function TableBody(props: React.ComponentPropsWithoutRef<'tbody'>) {
|
||||
return <tbody {...props} />
|
||||
}
|
||||
|
||||
const TableRowContext = createContext<{ href?: string; target?: string; title?: string }>({
|
||||
href: undefined,
|
||||
target: undefined,
|
||||
title: undefined,
|
||||
})
|
||||
|
||||
export function TableRow({
|
||||
href,
|
||||
target,
|
||||
title,
|
||||
className,
|
||||
...props
|
||||
}: { href?: string; target?: string; title?: string } & React.ComponentPropsWithoutRef<'tr'>) {
|
||||
let { striped } = useContext(TableContext)
|
||||
|
||||
return (
|
||||
<TableRowContext.Provider value={{ href, target, title } as React.ContextType<typeof TableRowContext>}>
|
||||
<tr
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
href &&
|
||||
'has-[[data-row-link][data-focus]]:outline-2 has-[[data-row-link][data-focus]]:-outline-offset-2 has-[[data-row-link][data-focus]]:outline-blue-500 dark:focus-within:bg-white/[2.5%]',
|
||||
striped && 'even:bg-zinc-950/[2.5%] dark:even:bg-white/[2.5%]',
|
||||
href && striped && 'hover:bg-zinc-950/5 dark:hover:bg-white/5',
|
||||
href && !striped && 'hover:bg-zinc-950/[2.5%] dark:hover:bg-white/[2.5%]'
|
||||
)}
|
||||
/>
|
||||
</TableRowContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function TableHeader({ className, ...props }: React.ComponentPropsWithoutRef<'th'>) {
|
||||
let { bleed, grid } = useContext(TableContext)
|
||||
|
||||
return (
|
||||
<th
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'border-b border-b-zinc-950/10 px-4 py-2 font-medium first:pl-(--gutter,--spacing(2)) last:pr-(--gutter,--spacing(2)) dark:border-b-white/10',
|
||||
grid && 'border-l border-l-zinc-950/5 first:border-l-0 dark:border-l-white/5',
|
||||
!bleed && 'sm:first:pl-1 sm:last:pr-1'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function TableCell({ className, children, ...props }: React.ComponentPropsWithoutRef<'td'>) {
|
||||
let { bleed, dense, grid, striped } = useContext(TableContext)
|
||||
let { href, target, title } = useContext(TableRowContext)
|
||||
let [cellRef, setCellRef] = useState<HTMLElement | null>(null)
|
||||
|
||||
return (
|
||||
<td
|
||||
ref={href ? setCellRef : undefined}
|
||||
{...props}
|
||||
className={clsx(
|
||||
className,
|
||||
'relative px-4 first:pl-(--gutter,--spacing(2)) last:pr-(--gutter,--spacing(2))',
|
||||
!striped && 'border-b border-zinc-950/5 dark:border-white/5',
|
||||
grid && 'border-l border-l-zinc-950/5 first:border-l-0 dark:border-l-white/5',
|
||||
dense ? 'py-2.5' : 'py-4',
|
||||
!bleed && 'sm:first:pl-1 sm:last:pr-1'
|
||||
)}
|
||||
>
|
||||
{href && (
|
||||
<Link
|
||||
data-row-link
|
||||
href={href}
|
||||
target={target}
|
||||
aria-label={title}
|
||||
tabIndex={cellRef?.previousElementSibling === null ? 0 : -1}
|
||||
className="absolute inset-0 focus:outline-hidden"
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</td>
|
||||
)
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import * as jeact from 'react'
|
||||
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@ -13,7 +13,7 @@ type GlobalState = {
|
||||
|
||||
const defaultState = {
|
||||
user: undefined,
|
||||
setUser: (user?: User) => {},
|
||||
setUser: (_?: User) => {},
|
||||
}
|
||||
|
||||
const GlobalContext = createContext<GlobalState>(defaultState)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user