feat: update UI components and styles with Radix and Tailwind integration

- Added new UI components including Button, Command, Dialog, Popover, and Dropzone using Radix UI.
- Introduced global styles with Tailwind CSS for consistent theming and design.
- Updated existing components to utilize new utility functions for class name management.
- Enhanced Tooltip component with inline-block display for better layout.
- Updated package dependencies in package.json and yarn.lock to include new Radix and Tailwind packages.
This commit is contained in:
MyPrototypeWhat 2025-10-14 14:12:48 +08:00
parent bab1a5445c
commit 534c2ce485
12 changed files with 844 additions and 13 deletions

View File

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"ui": "@/components/ui",
"utils": "@/utils"
},
"iconLibrary": "lucide",
"rsc": false,
"style": "new-york",
"tailwind": {
"baseColor": "zinc",
"config": "",
"css": "src/styles/globals.css",
"cssVariables": true,
"prefix": ""
},
"tsx": true
}

View File

@ -47,8 +47,16 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.525.0"
"cmdk": "^1.1.1",
"lucide-react": "^0.545.0",
"react-dropzone": "^14.3.8",
"tailwind-merge": "^2.5.5"
},
"devDependencies": {
"@heroui/react": "^2.8.4",

View File

@ -28,7 +28,7 @@ export const Tooltip = ({
}}
showArrow={showArrow ?? true}
{...rest}>
<div className={cn('relative z-10', classNames?.placeholder)}>{children}</div>
<div className={cn('relative z-10 inline-block', classNames?.placeholder)}>{children}</div>
</HeroUITooltip>
)
}

View File

@ -0,0 +1,52 @@
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { cn } from '@/utils/index'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'button'
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />
}
export { Button, buttonVariants }

View File

@ -0,0 +1,135 @@
import { Command as CommandPrimitive } from 'cmdk'
import { SearchIcon } from 'lucide-react'
import * as React from 'react'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { cn } from '@/utils/index'
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
className
)}
{...props}
/>
)
}
function CommandDialog({
title = 'Command Palette',
description = 'Search for a command to run...',
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className={cn('overflow-hidden p-0', className)} showCloseButton={showCloseButton}>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
</div>
)
}
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn('max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', className)}
{...props}
/>
)
}
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />
}
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
className
)}
{...props}
/>
)
}
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
)
}
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="command-shortcut"
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut
}

View File

@ -0,0 +1,119 @@
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { XIcon } from 'lucide-react'
import * as React from 'react'
import { cn } from '@/utils/index'
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className
)}
{...props}>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-footer"
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...props}
/>
)
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('text-lg leading-none font-semibold', className)}
{...props}
/>
)
}
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger
}

View File

@ -0,0 +1,42 @@
'use client'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import * as React from 'react'
import { cn } from '@/utils/index'
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
}
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }

View File

@ -0,0 +1,179 @@
'use client'
import { UploadIcon } from 'lucide-react'
import type { ReactNode } from 'react'
import { createContext, use } from 'react'
import type { DropEvent, DropzoneOptions, FileRejection } from 'react-dropzone'
import { useDropzone } from 'react-dropzone'
import { Button } from '@/components/ui/button'
import { cn } from '@/utils/index'
type DropzoneContextType = {
src?: File[]
accept?: DropzoneOptions['accept']
maxSize?: DropzoneOptions['maxSize']
minSize?: DropzoneOptions['minSize']
maxFiles?: DropzoneOptions['maxFiles']
}
const renderBytes = (bytes: number) => {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `${size.toFixed(2)}${units[unitIndex]}`
}
const DropzoneContext = createContext<DropzoneContextType | undefined>(undefined)
export type DropzoneProps = Omit<DropzoneOptions, 'onDrop'> & {
src?: File[]
className?: string
onDrop?: (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => void
children?: ReactNode
}
export const Dropzone = ({
accept,
maxFiles = 1,
maxSize,
minSize,
onDrop,
onError,
disabled,
src,
className,
children,
...props
}: DropzoneProps) => {
const { getRootProps, getInputProps, isDragActive } = useDropzone({
accept,
maxFiles,
maxSize,
minSize,
onError,
disabled,
onDrop: (acceptedFiles, fileRejections, event) => {
if (fileRejections.length > 0) {
const message = fileRejections.at(0)?.errors.at(0)?.message
onError?.(new Error(message))
return
}
onDrop?.(acceptedFiles, fileRejections, event)
},
...props
})
return (
<DropzoneContext key={JSON.stringify(src)} value={{ src, accept, maxSize, minSize, maxFiles }}>
<Button
className={cn(
'relative h-auto w-full flex-col overflow-hidden p-8',
isDragActive && 'outline-none ring-1 ring-ring',
className
)}
disabled={disabled}
type="button"
variant="outline"
{...getRootProps()}>
<input {...getInputProps()} disabled={disabled} />
{children}
</Button>
</DropzoneContext>
)
}
const useDropzoneContext = () => {
const context = use(DropzoneContext)
if (!context) {
throw new Error('useDropzoneContext must be used within a Dropzone')
}
return context
}
export type DropzoneContentProps = {
children?: ReactNode
className?: string
}
const maxLabelItems = 3
export const DropzoneContent = ({ children, className }: DropzoneContentProps) => {
const { src } = useDropzoneContext()
if (!src) {
return null
}
if (children) {
return children
}
return (
<div className={cn('flex flex-col items-center justify-center', className)}>
<div className="flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground">
<UploadIcon size={16} />
</div>
<p className="my-2 w-full truncate font-medium text-sm">
{src.length > maxLabelItems
? `${new Intl.ListFormat('en').format(
src.slice(0, maxLabelItems).map((file) => file.name)
)} and ${src.length - maxLabelItems} more`
: new Intl.ListFormat('en').format(src.map((file) => file.name))}
</p>
<p className="w-full text-wrap text-muted-foreground text-xs">Drag and drop or click to replace</p>
</div>
)
}
export type DropzoneEmptyStateProps = {
children?: ReactNode
className?: string
}
export const DropzoneEmptyState = ({ children, className }: DropzoneEmptyStateProps) => {
const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext()
if (src) {
return null
}
if (children) {
return children
}
let caption = ''
if (accept) {
caption += 'Accepts '
caption += new Intl.ListFormat('en').format(Object.keys(accept))
}
if (minSize && maxSize) {
caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`
} else if (minSize) {
caption += ` at least ${renderBytes(minSize)}`
} else if (maxSize) {
caption += ` less than ${renderBytes(maxSize)}`
}
return (
<div className={cn('flex flex-col items-center justify-center', className)}>
<div className="flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground">
<UploadIcon size={16} />
</div>
<p className="my-2 w-full truncate text-wrap font-medium text-sm">Upload {maxFiles === 1 ? 'a file' : 'files'}</p>
<p className="w-full truncate text-wrap text-muted-foreground text-xs">Drag and drop or click to upload</p>
{caption && <p className="text-wrap text-muted-foreground text-xs">{caption}.</p>}
</div>
)
}

View File

@ -0,0 +1,123 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -1 +1,10 @@
export { cn } from '@heroui/react'
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
/**
* Merge class names with tailwind-merge
* This utility combines clsx and tailwind-merge for optimal class name handling
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -7,14 +7,15 @@
"incremental": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"lib": ["DOM", "DOM.Iterable", "ES2021"],
"module": "ESNext",
"moduleResolution": "bundler",
"noFallthroughCasesInSwitch": true,
"outDir": "./dist",
"paths": {
"@types": ["src/types"],
"@utils": ["src/utils"]
"@/*": ["src/*"],
"@/types": ["src/types"],
"@/utils": ["src/utils"]
},
"resolveJsonModule": true,
"rootDir": ".",

156
yarn.lock
View File

@ -2781,6 +2781,10 @@ __metadata:
"@dnd-kit/sortable": "npm:^10.0.0"
"@dnd-kit/utilities": "npm:^3.2.2"
"@heroui/react": "npm:^2.8.4"
"@radix-ui/react-dialog": "npm:^1.1.15"
"@radix-ui/react-popover": "npm:^1.1.15"
"@radix-ui/react-slot": "npm:^1.2.3"
"@radix-ui/react-use-controllable-state": "npm:^1.2.2"
"@storybook/addon-docs": "npm:^9.1.6"
"@storybook/addon-themes": "npm:^9.1.6"
"@storybook/react-vite": "npm:^9.1.6"
@ -2791,15 +2795,19 @@ __metadata:
"@uiw/codemirror-themes-all": "npm:^4.25.1"
"@uiw/react-codemirror": "npm:^4.25.1"
antd: "npm:^5.22.5"
class-variance-authority: "npm:^0.7.1"
clsx: "npm:^2.1.1"
cmdk: "npm:^1.1.1"
eslint-plugin-storybook: "npm:9.1.6"
framer-motion: "npm:^12.23.12"
linguist-languages: "npm:^9.0.0"
lucide-react: "npm:^0.525.0"
lucide-react: "npm:^0.545.0"
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
react-dropzone: "npm:^14.3.8"
storybook: "npm:^9.1.6"
styled-components: "npm:^6.1.15"
tailwind-merge: "npm:^2.5.5"
tsdown: "npm:^0.15.5"
tsx: "npm:^4.20.5"
typescript: "npm:^5.6.2"
@ -8819,7 +8827,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-compose-refs@npm:1.1.2":
"@radix-ui/react-compose-refs@npm:1.1.2, @radix-ui/react-compose-refs@npm:^1.1.1":
version: 1.1.2
resolution: "@radix-ui/react-compose-refs@npm:1.1.2"
peerDependencies:
@ -8869,6 +8877,38 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-dialog@npm:^1.1.15, @radix-ui/react-dialog@npm:^1.1.6":
version: 1.1.15
resolution: "@radix-ui/react-dialog@npm:1.1.15"
dependencies:
"@radix-ui/primitive": "npm:1.1.3"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-dismissable-layer": "npm:1.1.11"
"@radix-ui/react-focus-guards": "npm:1.1.3"
"@radix-ui/react-focus-scope": "npm:1.1.7"
"@radix-ui/react-id": "npm:1.1.1"
"@radix-ui/react-portal": "npm:1.1.9"
"@radix-ui/react-presence": "npm:1.1.5"
"@radix-ui/react-primitive": "npm:2.1.3"
"@radix-ui/react-slot": "npm:1.2.3"
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
aria-hidden: "npm:^1.2.4"
react-remove-scroll: "npm:^2.6.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/2f2c88e3c281acaea2fd9b96fa82132d59177d3aa5da2e7c045596fd4028e84e44ac52ac28f4f236910605dd7d9338c2858ba44a9ced2af2e3e523abbfd33014
languageName: node
linkType: hard
"@radix-ui/react-direction@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-direction@npm:1.1.1"
@ -8939,7 +8979,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-id@npm:1.1.1":
"@radix-ui/react-id@npm:1.1.1, @radix-ui/react-id@npm:^1.1.0":
version: 1.1.1
resolution: "@radix-ui/react-id@npm:1.1.1"
dependencies:
@ -8990,6 +9030,39 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-popover@npm:^1.1.15":
version: 1.1.15
resolution: "@radix-ui/react-popover@npm:1.1.15"
dependencies:
"@radix-ui/primitive": "npm:1.1.3"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-dismissable-layer": "npm:1.1.11"
"@radix-ui/react-focus-guards": "npm:1.1.3"
"@radix-ui/react-focus-scope": "npm:1.1.7"
"@radix-ui/react-id": "npm:1.1.1"
"@radix-ui/react-popper": "npm:1.2.8"
"@radix-ui/react-portal": "npm:1.1.9"
"@radix-ui/react-presence": "npm:1.1.5"
"@radix-ui/react-primitive": "npm:2.1.3"
"@radix-ui/react-slot": "npm:1.2.3"
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
aria-hidden: "npm:^1.2.4"
react-remove-scroll: "npm:^2.6.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/c1c76b5e5985b128d03b621424fb453f769931d497759a1977734d303007da9f970570cf3ea1f6968ab609ab4a97f384168bff056197bd2d3d422abea0e3614b
languageName: node
linkType: hard
"@radix-ui/react-popper@npm:1.2.8":
version: 1.2.8
resolution: "@radix-ui/react-popper@npm:1.2.8"
@ -9058,7 +9131,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-primitive@npm:2.1.3":
"@radix-ui/react-primitive@npm:2.1.3, @radix-ui/react-primitive@npm:^2.0.2":
version: 2.1.3
resolution: "@radix-ui/react-primitive@npm:2.1.3"
dependencies:
@ -9104,7 +9177,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-slot@npm:1.2.3":
"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
version: 1.2.3
resolution: "@radix-ui/react-slot@npm:1.2.3"
dependencies:
@ -9132,7 +9205,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-use-controllable-state@npm:1.2.2":
"@radix-ui/react-use-controllable-state@npm:1.2.2, @radix-ui/react-use-controllable-state@npm:^1.2.2":
version: 1.2.2
resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2"
dependencies:
@ -16467,6 +16540,13 @@ __metadata:
languageName: node
linkType: hard
"attr-accept@npm:^2.2.4":
version: 2.2.5
resolution: "attr-accept@npm:2.2.5"
checksum: 10c0/9b4cb82213925cab2d568f71b3f1c7a7778f9192829aac39a281e5418cd00c04a88f873eb89f187e0bf786fa34f8d52936f178e62cbefb9254d57ecd88ada99b
languageName: node
linkType: hard
"aws4fetch@npm:^1.0.20":
version: 1.0.20
resolution: "aws4fetch@npm:1.0.20"
@ -17297,6 +17377,15 @@ __metadata:
languageName: node
linkType: hard
"class-variance-authority@npm:^0.7.1":
version: 0.7.1
resolution: "class-variance-authority@npm:0.7.1"
dependencies:
clsx: "npm:^2.1.1"
checksum: 10c0/0f438cea22131808b99272de0fa933c2532d5659773bfec0c583de7b3f038378996d3350683426b8e9c74a6286699382106d71fbec52f0dd5fbb191792cccb5b
languageName: node
linkType: hard
"classcat@npm:^5.0.3":
version: 5.0.5
resolution: "classcat@npm:5.0.5"
@ -17420,6 +17509,21 @@ __metadata:
languageName: node
linkType: hard
"cmdk@npm:^1.1.1":
version: 1.1.1
resolution: "cmdk@npm:1.1.1"
dependencies:
"@radix-ui/react-compose-refs": "npm:^1.1.1"
"@radix-ui/react-dialog": "npm:^1.1.6"
"@radix-ui/react-id": "npm:^1.1.0"
"@radix-ui/react-primitive": "npm:^2.0.2"
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
checksum: 10c0/5605ac4396ec9bc65c82f954da19dd89a0636a54026df72780e2470da1381f9d57434a80a53f2d57eaa4e759660a3ebba9232b74258dc09970576591eae03116
languageName: node
linkType: hard
"code-inspector-core@npm:0.20.14":
version: 0.20.14
resolution: "code-inspector-core@npm:0.20.14"
@ -20578,6 +20682,15 @@ __metadata:
languageName: node
linkType: hard
"file-selector@npm:^2.1.0":
version: 2.1.2
resolution: "file-selector@npm:2.1.2"
dependencies:
tslib: "npm:^2.7.0"
checksum: 10c0/fe827e0e95410aacfcc3eabc38c29cc36055257f03c1c06b631a2b5af9730c142ad2c52f5d64724d02231709617bda984701f52bd1f4b7aca50fb6585a27c1d2
languageName: node
linkType: hard
"file-stream-rotator@npm:0.6.1":
version: 0.6.1
resolution: "file-stream-rotator@npm:0.6.1"
@ -23600,6 +23713,15 @@ __metadata:
languageName: node
linkType: hard
"lucide-react@npm:^0.545.0":
version: 0.545.0
resolution: "lucide-react@npm:0.545.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
checksum: 10c0/942622c8e5d113e6fc6f66407703dc06474cf1a36ee9998b9f562f2e64737ea098c0db5a4e5ba2a8eb14abecb9da05be4584e9400c4a192ba5b2781f21928c21
languageName: node
linkType: hard
"lz-string@npm:^1.5.0":
version: 1.5.0
resolution: "lz-string@npm:1.5.0"
@ -27697,6 +27819,19 @@ __metadata:
languageName: node
linkType: hard
"react-dropzone@npm:^14.3.8":
version: 14.3.8
resolution: "react-dropzone@npm:14.3.8"
dependencies:
attr-accept: "npm:^2.2.4"
file-selector: "npm:^2.1.0"
prop-types: "npm:^15.8.1"
peerDependencies:
react: ">= 16.8 || 18.0.0"
checksum: 10c0/e17b1832783cda7b8824fe9370e99185d1abbdd5e4980b2985d6321c5768c8de18ff7b9ad550c809ee9743269dea608ff74d5208062754ce8377ad022897b278
languageName: node
linkType: hard
"react-error-boundary@npm:^6.0.0":
version: 6.0.0
resolution: "react-error-boundary@npm:6.0.0"
@ -30066,6 +30201,13 @@ __metadata:
languageName: node
linkType: hard
"tailwind-merge@npm:^2.5.5":
version: 2.6.0
resolution: "tailwind-merge@npm:2.6.0"
checksum: 10c0/fc8a5535524de9f4dacf1c16ab298581c7bb757d68a95faaf28942b1c555a619bba9d4c6726fe83986e44973b315410c1a5226e5354c30ba82353bd6d2288fa5
languageName: node
linkType: hard
"tailwind-variants@npm:3.1.0":
version: 3.1.0
resolution: "tailwind-variants@npm:3.1.0"
@ -30776,7 +30918,7 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0":
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.7.0, tslib@npm:^2.8.0":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62