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'),
|
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
|
'@next/next/no-img-element': 'off',
|
||||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { redirect } from 'next/navigation'
|
|||||||
import { LoginForm } from '@/components/login-form'
|
import { LoginForm } from '@/components/login-form'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
type Props = {}
|
const LoginPage = async () => {
|
||||||
const LoginPage = async (props: Props) => {
|
|
||||||
const payload = await getPayload({ config: configPromise })
|
const payload = await getPayload({ config: configPromise })
|
||||||
const headers = await nextHeaders()
|
const headers = await nextHeaders()
|
||||||
const userResult = await payload.auth({ headers })
|
const userResult = await payload.auth({ headers })
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import config from '@/payload.config'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import UserFeed from '@/components/Feed/UserFeed'
|
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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { TextShimmer } from '@/components/ui/text-shimmer'
|
import { TextShimmer } from '@/components/ui/text-shimmer'
|
||||||
import { LoginForm } from '@/components/login-form'
|
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 { getPayload, PaginatedDocs } from 'payload'
|
||||||
import config from '@/payload.config'
|
import config from '@/payload.config'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card'
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card'
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const HoldRequestNotifications = (props: Props) => {
|
|||||||
|
|
||||||
const holdRequestsByRepoElements = repos?.docs.map((r) => {
|
const holdRequestsByRepoElements = repos?.docs.map((r) => {
|
||||||
return (
|
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) => {
|
{r.holdRequests?.docs?.map((h) => {
|
||||||
const hold = h as HoldRequest
|
const hold = h as HoldRequest
|
||||||
const book = hold.book as Book
|
const book = hold.book as Book
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const SearchBooksInlineForm = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
const searchResults = await searchBooks(values)
|
const searchResults = await searchBooks(values)
|
||||||
if (searchResults && onSearchResult) onSearchResult(searchResults)
|
if (searchResults && onSearchResult) onSearchResult(searchResults)
|
||||||
} catch (err) {
|
} catch (_) {
|
||||||
toast('There was an issue with that search')
|
toast('There was an issue with that search')
|
||||||
} finally {
|
} finally {
|
||||||
setIsSearching(false)
|
setIsSearching(false)
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export default function SiteNavigation(props: { children: React.ReactNode }) {
|
|||||||
setUser(userRequest.user)
|
setUser(userRequest.user)
|
||||||
console.log(userRequest.user)
|
console.log(userRequest.user)
|
||||||
})
|
})
|
||||||
}, [user?.id])
|
}, [user, setUser])
|
||||||
|
|
||||||
const profilePicture = user?.profilePicture as Media | undefined
|
const profilePicture = user?.profilePicture as Media | undefined
|
||||||
const initials = user?.firstName
|
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',
|
'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',
|
'outline -outline-offset-1 outline-black/10 dark:outline-white/10',
|
||||||
// Border radius
|
// 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 && (
|
{initials && (
|
||||||
@ -40,7 +42,14 @@ export function Avatar({
|
|||||||
aria-hidden={alt ? undefined : 'true'}
|
aria-hidden={alt ? undefined : 'true'}
|
||||||
>
|
>
|
||||||
{alt && <title>{alt}</title>}
|
{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}
|
{initials}
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
@ -59,13 +68,16 @@ export const AvatarButton = forwardRef(function AvatarButton(
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: AvatarProps &
|
}: 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,
|
className,
|
||||||
square ? 'rounded-[20%]' : 'rounded-full',
|
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 ? (
|
return 'href' in props ? (
|
||||||
|
|||||||
@ -36,14 +36,18 @@ const colors = {
|
|||||||
|
|
||||||
type BadgeProps = { color?: keyof typeof 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 (
|
return (
|
||||||
<span
|
<span
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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',
|
'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<Headless.ButtonProps, 'as' | 'className'>
|
||||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
||||||
),
|
),
|
||||||
ref: React.ForwardedRef<HTMLElement>
|
ref: React.ForwardedRef<HTMLElement>,
|
||||||
) {
|
) {
|
||||||
let classes = clsx(
|
const classes = clsx(
|
||||||
className,
|
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 ? (
|
return 'href' in props ? (
|
||||||
|
|||||||
@ -169,12 +169,16 @@ type ButtonProps = (
|
|||||||
|
|
||||||
export const Button = forwardRef(function Button(
|
export const Button = forwardRef(function Button(
|
||||||
{ color, outline, plain, className, children, ...props }: ButtonProps,
|
{ color, outline, plain, className, children, ...props }: ButtonProps,
|
||||||
ref: React.ForwardedRef<HTMLElement>
|
ref: React.ForwardedRef<HTMLElement>,
|
||||||
) {
|
) {
|
||||||
let classes = clsx(
|
const classes = clsx(
|
||||||
className,
|
className,
|
||||||
styles.base,
|
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 ? (
|
return 'href' in props ? (
|
||||||
|
|||||||
@ -24,18 +24,27 @@ export function Combobox<T>({
|
|||||||
autoFocus?: boolean
|
autoFocus?: boolean
|
||||||
'aria-label'?: string
|
'aria-label'?: string
|
||||||
children: (value: NonNullable<T>) => React.ReactElement
|
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 [query, setQuery] = useState('')
|
||||||
|
|
||||||
const filteredOptions =
|
const filteredOptions =
|
||||||
query === ''
|
query === ''
|
||||||
? options
|
? options
|
||||||
: options.filter((option) =>
|
: options.filter((option) =>
|
||||||
filter ? filter(option, query) : displayValue(option)?.toLowerCase().includes(query.toLowerCase())
|
filter
|
||||||
|
? filter(option, query)
|
||||||
|
: displayValue(option)?.toLowerCase().includes(query.toLowerCase()),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Headless.Combobox {...props} multiple={false} virtual={{ options: filteredOptions }} onClose={() => setQuery('')}>
|
<Headless.Combobox
|
||||||
|
{...props}
|
||||||
|
multiple={false}
|
||||||
|
virtual={{ options: filteredOptions }}
|
||||||
|
onClose={() => setQuery('')}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
data-slot="control"
|
data-slot="control"
|
||||||
className={clsx([
|
className={clsx([
|
||||||
@ -90,8 +99,18 @@ export function Combobox<T>({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
fill="none"
|
fill="none"
|
||||||
>
|
>
|
||||||
<path d="M5.75 10.75L8 13L10.25 10.75" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
<path
|
||||||
<path d="M10.25 5.25L8 3L5.75 5.25" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
|
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>
|
</svg>
|
||||||
</Headless.ComboboxButton>
|
</Headless.ComboboxButton>
|
||||||
</span>
|
</span>
|
||||||
@ -112,7 +131,7 @@ export function Combobox<T>({
|
|||||||
// Shadows
|
// Shadows
|
||||||
'shadow-lg ring-1 ring-zinc-950/10 dark:ring-white/10 dark:ring-inset',
|
'shadow-lg ring-1 ring-zinc-950/10 dark:ring-white/10 dark:ring-inset',
|
||||||
// Transitions
|
// 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)}
|
{({ option }) => children(option)}
|
||||||
@ -129,7 +148,7 @@ export function ComboboxOption<T>({
|
|||||||
Headless.ComboboxOptionProps<'div', T>,
|
Headless.ComboboxOptionProps<'div', T>,
|
||||||
'as' | 'className'
|
'as' | 'className'
|
||||||
>) {
|
>) {
|
||||||
let sharedClasses = clsx(
|
const sharedClasses = clsx(
|
||||||
// Base
|
// Base
|
||||||
'flex min-w-0 items-center',
|
'flex min-w-0 items-center',
|
||||||
// Icons
|
// 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',
|
'*: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]',
|
'forced-colors:*:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focus/option:*:data-[slot=icon]:text-[Canvas]',
|
||||||
// Avatars
|
// 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 (
|
return (
|
||||||
@ -153,7 +172,7 @@ export function ComboboxOption<T>({
|
|||||||
// Forced colors mode
|
// Forced colors mode
|
||||||
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
|
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
|
||||||
// Disabled
|
// Disabled
|
||||||
'data-disabled:opacity-50'
|
'data-disabled:opacity-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className={clsx(className, sharedClasses)}>{children}</span>
|
<span className={clsx(className, sharedClasses)}>{children}</span>
|
||||||
@ -170,16 +189,25 @@ export function ComboboxOption<T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ComboboxLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
|
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 (
|
return (
|
||||||
<span
|
<span
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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>
|
<span className="flex-1 truncate">{children}</span>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export function DropdownMenu({
|
|||||||
// Define grid at the menu level if subgrid is supported
|
// 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]',
|
'supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[auto_1fr_1.5rem_0.5rem_auto]',
|
||||||
// Transitions
|
// 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<'button'>, 'as' | 'className'>
|
||||||
| Omit<Headless.MenuItemProps<typeof Link>, 'as' | 'className'>
|
| Omit<Headless.MenuItemProps<typeof Link>, 'as' | 'className'>
|
||||||
)) {
|
)) {
|
||||||
let classes = clsx(
|
const classes = clsx(
|
||||||
className,
|
className,
|
||||||
// Base styles
|
// Base styles
|
||||||
'group cursor-default rounded-lg px-3.5 py-2.5 focus:outline-hidden sm:px-3 sm:py-1.5',
|
'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]: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',
|
'*: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
|
// 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 ? (
|
return 'href' in props ? (
|
||||||
@ -99,7 +99,7 @@ export function DropdownSection({
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
className,
|
||||||
// Define grid at the section level instead of the item level if subgrid is supported
|
// 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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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
|
...props
|
||||||
}: { className?: string } & Omit<Headless.LabelProps, 'as' | 'className'>) {
|
}: { className?: string } & Omit<Headless.LabelProps, 'as' | 'className'>) {
|
||||||
return (
|
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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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,
|
keys,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: { keys: string | string[]; className?: string } & Omit<Headless.DescriptionProps<'kbd'>, 'as' | 'className'>) {
|
}: { keys: string | string[]; className?: string } & Omit<
|
||||||
|
Headless.DescriptionProps<'kbd'>,
|
||||||
|
'as' | 'className'
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<Headless.Description
|
<Headless.Description
|
||||||
as="kbd"
|
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'>) {
|
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'>) {
|
export function NavbarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||||
let id = useId()
|
const id = useId()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutGroup id={id}>
|
<LayoutGroup id={id}>
|
||||||
@ -39,9 +45,9 @@ export const NavbarItem = forwardRef(function NavbarItem(
|
|||||||
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
| Omit<Headless.ButtonProps, 'as' | 'className'>
|
||||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
|
||||||
),
|
),
|
||||||
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
|
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
let classes = clsx(
|
const classes = clsx(
|
||||||
// Base
|
// 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',
|
'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
|
// Leading icon/icon-only
|
||||||
@ -57,7 +63,7 @@ export const NavbarItem = forwardRef(function NavbarItem(
|
|||||||
// Dark mode
|
// Dark mode
|
||||||
'dark:text-white dark:*:data-[slot=icon]:fill-zinc-400',
|
'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-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 (
|
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 (
|
return (
|
||||||
<Headless.Dialog open={open} onClose={close} className="lg:hidden">
|
<Headless.Dialog open={open} onClose={close} className="lg:hidden">
|
||||||
<Headless.DialogBackdrop
|
<Headless.DialogBackdrop
|
||||||
@ -49,7 +53,7 @@ export function SidebarLayout({
|
|||||||
sidebar,
|
sidebar,
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
||||||
let [showSidebar, setShowSidebar] = useState(false)
|
const [showSidebar, setShowSidebar] = useState(false)
|
||||||
|
|
||||||
return (
|
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">
|
<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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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}
|
{...props}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
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'>) {
|
export function SidebarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||||
let id = useId()
|
const id = useId()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutGroup id={id}>
|
<LayoutGroup id={id}>
|
||||||
@ -58,7 +58,12 @@ export function SidebarSection({ className, ...props }: React.ComponentPropsWith
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarDivider({ className, ...props }: React.ComponentPropsWithoutRef<'hr'>) {
|
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'>) {
|
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'>) {
|
export function SidebarHeading({ className, ...props }: React.ComponentPropsWithoutRef<'h3'>) {
|
||||||
return (
|
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, 'as' | 'className'>
|
||||||
| Omit<Headless.ButtonProps<typeof Link>, '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
|
// 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',
|
'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
|
// 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: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-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',
|
||||||
'dark:data-current:*:data-[slot=icon]:fill-white'
|
'dark:data-current:*:data-[slot=icon]:fill-white',
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export function StackedLayout({
|
|||||||
sidebar,
|
sidebar,
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
|
||||||
let [showSidebar, setShowSidebar] = useState(false)
|
const [showSidebar, setShowSidebar] = useState(false)
|
||||||
|
|
||||||
return (
|
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">
|
<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 { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|||||||
@ -13,7 +13,7 @@ type GlobalState = {
|
|||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
user: undefined,
|
user: undefined,
|
||||||
setUser: (user?: User) => {},
|
setUser: (_?: User) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalContext = createContext<GlobalState>(defaultState)
|
const GlobalContext = createContext<GlobalState>(defaultState)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user