155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
import * as RadixSelect from '@radix-ui/react-select'
|
|
import { Check, ChevronDown } from 'lucide-react'
|
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
|
|
type Variant = 'default' | 'muted' | 'success' | 'warning' | 'danger' | 'info'
|
|
|
|
function cn(...classes: Array<string | false | null | undefined>) {
|
|
return classes.filter(Boolean).join(' ')
|
|
}
|
|
|
|
const variantClass: Record<Variant, string> = {
|
|
default: 'border-white/10 bg-white/[0.04] text-zinc-100',
|
|
muted: 'border-white/10 bg-zinc-900/70 text-zinc-400',
|
|
success: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-300',
|
|
warning: 'border-amber-400/20 bg-amber-400/10 text-amber-300',
|
|
danger: 'border-red-400/20 bg-red-400/10 text-red-300',
|
|
info: 'border-teal-400/20 bg-teal-400/10 text-teal-300',
|
|
}
|
|
|
|
export function Badge({
|
|
className,
|
|
variant = 'default',
|
|
children,
|
|
...props
|
|
}: ComponentPropsWithoutRef<'span'> & { variant?: Variant }) {
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium leading-none',
|
|
variantClass[variant],
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
export function Card({ className, children, ...props }: ComponentPropsWithoutRef<'div'>) {
|
|
return (
|
|
<div
|
|
className={cn('rounded-2xl border border-white/[0.08] bg-zinc-950/60 shadow-2xl shadow-black/20', className)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function Button({ className, children, ...props }: ComponentPropsWithoutRef<'button'>) {
|
|
return (
|
|
<button
|
|
className={cn(
|
|
'inline-flex items-center justify-center gap-2 rounded-lg border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-medium text-zinc-100 transition-colors hover:border-white/20 hover:bg-white/[0.09] disabled:cursor-not-allowed disabled:opacity-35 disabled:hover:bg-white/[0.05]',
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function Input({ className, ...props }: ComponentPropsWithoutRef<'input'>) {
|
|
return (
|
|
<input
|
|
className={cn(
|
|
'h-10 w-full rounded-lg border border-white/10 bg-zinc-950/80 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 outline-none transition-colors focus:border-teal-400/60 focus:ring-2 focus:ring-teal-400/10',
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function Select({
|
|
value,
|
|
onValueChange,
|
|
children,
|
|
className,
|
|
id,
|
|
}: {
|
|
value?: string | number
|
|
onValueChange?: (value: string) => void
|
|
children: ReactNode
|
|
className?: string
|
|
id?: string
|
|
}) {
|
|
return (
|
|
<RadixSelect.Root value={value?.toString()} onValueChange={onValueChange}>
|
|
<RadixSelect.Trigger
|
|
id={id}
|
|
className={cn(
|
|
'flex h-10 w-full items-center justify-between gap-2 rounded-lg border border-white/10 bg-zinc-950/95 px-3 py-2 text-sm text-zinc-100 outline-none transition-colors focus:border-teal-400/60 focus:ring-2 focus:ring-teal-400/10 data-[placeholder]:text-zinc-500',
|
|
className,
|
|
)}
|
|
>
|
|
<RadixSelect.Value />
|
|
<RadixSelect.Icon asChild>
|
|
<ChevronDown className="size-4 opacity-50" />
|
|
</RadixSelect.Icon>
|
|
</RadixSelect.Trigger>
|
|
<RadixSelect.Portal>
|
|
<RadixSelect.Content
|
|
className="relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-lg border border-white/10 bg-zinc-950 text-zinc-100 shadow-xl shadow-black/40"
|
|
position="popper"
|
|
sideOffset={4}
|
|
>
|
|
<RadixSelect.Viewport className="p-1">{children}</RadixSelect.Viewport>
|
|
</RadixSelect.Content>
|
|
</RadixSelect.Portal>
|
|
</RadixSelect.Root>
|
|
)
|
|
}
|
|
|
|
export function SelectOption({
|
|
value,
|
|
children,
|
|
className,
|
|
}: {
|
|
value: string | number
|
|
children: ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<RadixSelect.Item
|
|
value={value.toString()}
|
|
className={cn(
|
|
'relative flex w-full cursor-default select-none items-center rounded-md py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-white/10 focus:text-zinc-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
className,
|
|
)}
|
|
>
|
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
<RadixSelect.ItemIndicator>
|
|
<Check className="size-4" />
|
|
</RadixSelect.ItemIndicator>
|
|
</span>
|
|
<RadixSelect.ItemText>{children}</RadixSelect.ItemText>
|
|
</RadixSelect.Item>
|
|
)
|
|
}
|
|
|
|
export function SectionTitle({ icon, title, description }: { icon?: ReactNode; title: string; description?: string }) {
|
|
return (
|
|
<div className="flex items-start gap-3">
|
|
{icon && <div className="mt-0.5 rounded-lg border border-white/10 bg-white/[0.04] p-2 text-teal-300">{icon}</div>}
|
|
<div>
|
|
<h3 className="text-lg font-medium text-white">{title}</h3>
|
|
{description && <p className="mt-1 text-sm text-zinc-400">{description}</p>}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|