1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-08-05 04:13:44 +05:30

refactor: migrated to shadcn v4

This commit is contained in:
2026-07-10 23:38:02 +05:30
Verified
parent 03d950951e
commit 95665b276d
42 changed files with 5239 additions and 1145 deletions
+7 -3
View File
@@ -1,10 +1,11 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "radix-vega",
"rsc": false, "rsc": false,
"rtl": false,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "",
"css": "src/index.css", "css": "src/index.css",
"baseColor": "zinc", "baseColor": "zinc",
"cssVariables": true, "cssVariables": true,
@@ -17,5 +18,8 @@
"lib": "@/lib", "lib": "@/lib",
"hooks": "@/hooks" "hooks": "@/hooks"
}, },
"iconLibrary": "lucide" "iconLibrary": "lucide",
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
} }
+3751 -6
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -26,6 +26,7 @@
"download": "node ./scripts/download-bins.js" "download": "node ./scripts/download-bins.js"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "^5.2.8",
"@hookform/resolvers": "^5.4.0", "@hookform/resolvers": "^5.4.0",
"@hugeicons/core-free-icons": "^4.2.2", "@hugeicons/core-free-icons": "^4.2.2",
"@hugeicons/react": "^1.1.9", "@hugeicons/react": "^1.1.9",
@@ -57,6 +58,7 @@
"react-hook-form": "^7.81.0", "react-hook-form": "^7.81.0",
"react-resizable-panels": "^4.12.1", "react-resizable-panels": "^4.12.1",
"react-router-dom": "^7.18.1", "react-router-dom": "^7.18.1",
"shadcn": "^4.13.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"ulid": "^3.0.2", "ulid": "^3.0.2",
@@ -17,7 +17,7 @@ const IndeterminateProgress = React.forwardRef<
<ProgressPrimitive.Root <ProgressPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20", "relative h-1.5 w-full overflow-hidden rounded-full bg-primary/20",
className className
)} )}
{...props} {...props}
+1 -1
View File
@@ -56,7 +56,7 @@ export default function Navbar() {
<DialogTitle>Log Viewer</DialogTitle> <DialogTitle>Log Viewer</DialogTitle>
<DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription> <DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-muted"> <div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-background">
{logs.length === 0 ? ( {logs.length === 0 ? (
<p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p> <p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p>
) : ( ) : (
@@ -13,7 +13,7 @@ import { dirname } from "@tauri-apps/api/path";
import { DownloadState } from "@/types/download"; import { DownloadState } from "@/types/download";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useDeleteDownloadState } from "@/services/mutations"; import { useDeleteDownloadState } from "@/services/mutations";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@@ -295,24 +295,27 @@ export function CompletedDownload({ state }: CompletedDownloadProps) {
Remove Remove
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent size="sm">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
<Trash2 />
</AlertDialogMedia>
<AlertDialogTitle>Remove from library?</AlertDialogTitle> <AlertDialogTitle>Remove from library?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Are you sure you want to remove this download from the library? You can also delete the downloaded file by cheking the box below. This action cannot be undone. Are you sure you want to remove this download from the library? You can also delete the downloaded file by cheking the box below. This action cannot be undone.
</AlertDialogDescription> </AlertDialogDescription>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2 mt-1">
<Checkbox id="delete-file" checked={itemActionStates.isDeleteFileChecked} onCheckedChange={() => {setIsDeleteFileChecked(state.download_id, !itemActionStates.isDeleteFileChecked)}} /> <Checkbox id="delete-file" checked={itemActionStates.isDeleteFileChecked} onCheckedChange={() => {setIsDeleteFileChecked(state.download_id, !itemActionStates.isDeleteFileChecked)}} />
<Label htmlFor="delete-file">Delete the downloaded file</Label> <Label htmlFor="delete-file">Delete downloaded file</Label>
</div> </div>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={ <AlertDialogAction variant="destructive" onClick={
() => removeFromDownloads(state, itemActionStates.isDeleteFileChecked).then(() => { () => removeFromDownloads(state, itemActionStates.isDeleteFileChecked).then(() => {
setIsDeleteFileChecked(state.download_id, false); setIsDeleteFileChecked(state.download_id, false);
}) })
}>Remove</AlertDialogAction> }>{itemActionStates.isDeleteFileChecked ? "Delete" : "Remove"}</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@@ -99,7 +99,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
<span className="text-sm text-nowrap">({state.item})</span> <span className="text-sm text-nowrap">({state.item})</span>
) : null} ) : null}
<span className="text-sm text-nowrap">{state.progress}%</span> <span className="text-sm text-nowrap">{state.progress}%</span>
<Progress value={state.progress} /> <Progress value={state.progress} className="mt-[0.2rem]" />
<span className="text-sm text-nowrap">{ <span className="text-sm text-nowrap">{
state.downloaded && state.total state.downloaded && state.total
? `(${formatFileSize(state.downloaded)} / ${formatFileSize(state.total)})` ? `(${formatFileSize(state.downloaded)} / ${formatFileSize(state.total)})`
@@ -20,7 +20,7 @@ import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { join } from "@tauri-apps/api/path"; import { join } from "@tauri-apps/api/path";
@@ -418,14 +418,18 @@ function AppFilesystemSettings() {
<BrushCleaning className="size-4" /> Clean <BrushCleaning className="size-4" /> Clean
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent size="sm">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Clean up all temporary downloads?</AlertDialogTitle> <AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
<BrushCleaning />
</AlertDialogMedia>
<AlertDialogTitle>Clean temporary downloads?</AlertDialogTitle>
<AlertDialogDescription>Are you sure you want to clean up all temporary downloads? This will remove all broken, cancelled and paused downloads from the temporary folder. Paused downloads will re-start from the begining. This action cannot be undone!</AlertDialogDescription> <AlertDialogDescription>Are you sure you want to clean up all temporary downloads? This will remove all broken, cancelled and paused downloads from the temporary folder. Paused downloads will re-start from the begining. This action cannot be undone!</AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
variant="destructive"
onClick={() => cleanTemporaryDownloads()} onClick={() => cleanTemporaryDownloads()}
>Clean</AlertDialogAction> >Clean</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
@@ -1376,7 +1380,7 @@ function AppPoTokenSettings() {
<Label htmlFor="use-potoken">Use PO Token</Label> <Label htmlFor="use-potoken">Use PO Token</Label>
</div> </div>
<Label className="text-xs text-muted-foreground flex items-center"> <Label className="text-xs text-muted-foreground flex items-center">
<span className="mr-1">NeoDLP POT Server is</span> <span className="">NeoDLP POT Server is</span>
{isStartingPotServer ? ( {isStartingPotServer ? (
<span className="text-amber-600 dark:text-amber-500 underline">Starting</span> <span className="text-amber-600 dark:text-amber-500 underline">Starting</span>
) : isRunningPotServer ? ( ) : isRunningPotServer ? (
@@ -1385,7 +1389,7 @@ function AppPoTokenSettings() {
<span className="text-red-600 dark:text-red-500 underline">Not Running</span> <span className="text-red-600 dark:text-red-500 underline">Not Running</span>
)} )}
{isRunningPotServer && potServerPort ? ( {isRunningPotServer && potServerPort ? (
<span className="ml-1">on Port {potServerPort}</span> <span className="">on Port {potServerPort}</span>
) : null} ) : null}
</Label> </Label>
</div> </div>
@@ -1803,13 +1807,13 @@ function AppInfoSettings() {
return ( return (
<> <>
<div className="app-info"> <div className="app-info">
<Card className="p-4 space-y-4 flex items-center gap-4"> <Card className="p-4 space-y-4 flex flex-row items-center gap-4">
<div className="flex aspect-square size-18 items-center justify-center rounded-lg m-0"> <div className="flex aspect-square size-18 items-center justify-center rounded-lg m-0">
<NeoDlpLogo className="size-full rounded-lg border border-border [--logo-stop-color-1:#4444FF] [--logo-stop-color-2:#FF43D0] customscheme:[--logo-stop-color-1:var(--color-chart-5)] customscheme:[--logo-stop-color-2:var(--color-chart-1)]" /> <NeoDlpLogo className="size-full rounded-lg border border-border [--logo-stop-color-1:#4444FF] [--logo-stop-color-2:#FF43D0] customscheme:[--logo-stop-color-1:var(--color-chart-5)] customscheme:[--logo-stop-color-2:var(--color-chart-1)]" />
</div> </div>
<div className="flex flex-col justify-center gap-1"> <div className="flex flex-col justify-center gap-1">
<span className="truncate font-semibold">{config.appName} <Badge className="ml-1 border-primary px-1.5" variant="outline"><span className="mb-0.5">v{appVersion}</span></Badge></span> <span className="truncate font-semibold">{config.appName} <Badge className="ml-1 border-primary px-1.5" variant="outline"><span className="mb-0.5">v{appVersion}</span></Badge></span>
<span className="truncate text-xs text-muted-foreground">The Next-Gen Truly Cross-Platform Video/Audio Downloader</span> <span className="truncate text-xs text-muted-foreground">Modern Video/Audio Downloader based on YT-DLP</span>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<a href={config.appHomepage} target="_blank" className="text-sm text-foreground" title="Homepage"> <a href={config.appHomepage} target="_blank" className="text-sm text-foreground" title="Homepage">
<Globe className="size-3.5" /> <Globe className="size-3.5" />
@@ -1828,7 +1832,7 @@ function AppInfoSettings() {
<div className="about-developer"> <div className="about-developer">
<h3 className="font-semibold">Developer</h3> <h3 className="font-semibold">Developer</h3>
<p className="text-xs text-muted-foreground mb-3">Meet the Creator of NeoDLP</p> <p className="text-xs text-muted-foreground mb-3">Meet the Creator of NeoDLP</p>
<Card className="p-4 space-y-4 flex items-center gap-4"> <Card className="p-4 space-y-4 flex flex-row items-center gap-4">
<div className="relative w-fit m-0"> <div className="relative w-fit m-0">
<Avatar className="size-11"> <Avatar className="size-11">
<AvatarImage src={neosubhamoyImage} /> <AvatarImage src={neosubhamoyImage} />
@@ -1926,7 +1930,7 @@ function AppInfoSettings() {
<DialogTitle>Dependencies</DialogTitle> <DialogTitle>Dependencies</DialogTitle>
<DialogDescription>Major dependencies of NeoDLP</DialogDescription> <DialogDescription>Major dependencies of NeoDLP</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-4 max-h-[45vh] overflow-y-auto"> <div className="flex flex-col gap-4 max-h-[45vh] overflow-y-auto no-scrollbar">
<h4 className="text-sm font-semibold">External Binaries</h4> <h4 className="text-sm font-semibold">External Binaries</h4>
{binDepsList.map(({key, ...dep}) => ( {binDepsList.map(({key, ...dep}) => (
<DependencyItem key={key} {...dep} /> <DependencyItem key={key} {...dep} />
+1 -1
View File
@@ -79,7 +79,7 @@ export function AppSidebar() {
<SidebarMenuButton size="lg" asChild> <SidebarMenuButton size="lg" asChild>
<a href="#"> <a href="#">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg"> <div className="flex aspect-square size-8 items-center justify-center rounded-lg">
<NeoDlpLogo className="size-full rounded-md border border-border [--logo-stop-color-1:#4444FF] [--logo-stop-color-2:#FF43D0] customscheme:[--logo-stop-color-1:var(--color-chart-5)] customscheme:[--logo-stop-color-2:var(--color-chart-1)]" /> <NeoDlpLogo className="size-full! rounded-md border border-border [--logo-stop-color-1:#4444FF] [--logo-stop-color-2:#FF43D0] customscheme:[--logo-stop-color-1:var(--color-chart-5)] customscheme:[--logo-stop-color-2:var(--color-chart-1)]" />
</div> </div>
<div className="grid flex-1 text-left text-sm leading-tight"> <div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">Neo Downloader Plus</span> <span className="truncate font-semibold">Neo Downloader Plus</span>
+142 -84
View File
@@ -2,138 +2,196 @@ import * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "radix-ui" import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button" import { Button } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
const AlertDialogTrigger = AlertDialogPrimitive.Trigger function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}
const AlertDialogPortal = AlertDialogPrimitive.Portal function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}
const AlertDialogOverlay = React.forwardRef< function AlertDialogOverlay({
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 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/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className className
)} )}
{...props} {...props}
ref={ref}
/> />
)) )
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName }
const AlertDialogContent = React.forwardRef< function AlertDialogContent({
React.ElementRef<typeof AlertDialogPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> size = "default",
>(({ className, ...props }, ref) => ( ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
size?: "default" | "sm"
}) {
return (
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref} data-slot="alert-dialog-content"
data-size={size}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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 sm:rounded-lg", "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className className
)} )}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
)) )
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName }
const AlertDialogHeader = ({ function AlertDialogHeader({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<"div">) {
return (
<div <div
data-slot="alert-dialog-header"
className={cn( className={cn(
"flex flex-col space-y-2 text-center sm:text-left", "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className className
)} )}
{...props} {...props}
/> />
) )
AlertDialogHeader.displayName = "AlertDialogHeader" }
const AlertDialogFooter = ({ function AlertDialogFooter({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<"div">) {
return (
<div <div
data-slot="alert-dialog-footer"
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className className
)} )}
{...props} {...props}
/> />
) )
AlertDialogFooter.displayName = "AlertDialogFooter" }
const AlertDialogTitle = React.forwardRef< function AlertDialogMedia({
React.ElementRef<typeof AlertDialogPrimitive.Title>, className,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
className
)}
{...props}
/>
)
}
function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
ref={ref} data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn( className={cn(
buttonVariants({ variant: "outline" }), "text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
"mt-2 sm:mt-0",
className className
)} )}
{...props} {...props}
/> />
)) )
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName }
function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
function AlertDialogAction({
className,
variant = "default",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Action
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
</Button>
)
}
function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Cancel
data-slot="alert-dialog-cancel"
className={cn(className)}
{...props}
/>
</Button>
)
}
export { export {
AlertDialog, AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} }
+48 -31
View File
@@ -4,13 +4,13 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-background text-foreground", default: "bg-card text-card-foreground",
destructive: destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -19,41 +19,58 @@ const alertVariants = cva(
} }
) )
const Alert = React.forwardRef< function Alert({
HTMLDivElement, className,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> variant,
>(({ className, variant, ...props }, ref) => ( ...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div <div
ref={ref} data-slot="alert"
role="alert" role="alert"
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)) )
Alert.displayName = "Alert" }
const AlertTitle = React.forwardRef< function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
HTMLParagraphElement, return (
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="alert-title"
className={cn("text-sm [&_p]:leading-relaxed", className)} className={cn(
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className
)}
{...props} {...props}
/> />
)) )
AlertDescription.displayName = "AlertDescription" }
export { Alert, AlertTitle, AlertDescription } function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className
)}
{...props}
/>
)
}
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn("absolute top-2.5 right-3", className)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription, AlertAction }
+5 -1
View File
@@ -1,5 +1,9 @@
import { AspectRatio as AspectRatioPrimitive } from "radix-ui" import { AspectRatio as AspectRatioPrimitive } from "radix-ui"
const AspectRatio = AspectRatioPrimitive.Root function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio } export { AspectRatio }
+91 -31
View File
@@ -1,50 +1,110 @@
"use client"
import * as React from "react" import * as React from "react"
import { Avatar as AvatarPrimitive } from "radix-ui" import { Avatar as AvatarPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Avatar = React.forwardRef< function Avatar({
React.ElementRef<typeof AvatarPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> size = "default",
>(({ className, ...props }, ref) => ( ...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
size?: "default" | "sm" | "lg"
}) {
return (
<AvatarPrimitive.Root <AvatarPrimitive.Root
ref={ref} data-slot="avatar"
data-size={size}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className className
)} )}
{...props} {...props}
/> />
)) )
Avatar.displayName = AvatarPrimitive.Root.displayName }
const AvatarImage = React.forwardRef< function AvatarImage({
React.ElementRef<typeof AvatarPrimitive.Image>, className,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} data-slot="avatar-image"
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", "aspect-square size-full rounded-full object-cover",
className className
)} )}
{...props} {...props}
/> />
)) )
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName }
export { Avatar, AvatarImage, AvatarFallback } function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className
)}
{...props}
/>
)
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className
)}
{...props}
/>
)
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className
)}
{...props}
/>
)
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className
)}
{...props}
/>
)
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
}
+24 -11
View File
@@ -1,20 +1,24 @@
import * as React from "react" import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{ {
variants: { variants: {
variant: { variant: {
default: default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline: "text-foreground", outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -23,13 +27,22 @@ const badgeVariants = cva(
} }
) )
export interface BadgeProps function Badge({
extends React.HTMLAttributes<HTMLDivElement>, className,
VariantProps<typeof badgeVariants> {} variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
) )
} }
+33 -25
View File
@@ -1,31 +1,36 @@
import * as React from "react" import * as React from "react"
import { Slot as SlotPrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: default: "bg-primary text-primary-foreground hover:bg-primary/80",
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2", default:
sm: "h-8 rounded-md px-3 text-xs", "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
lg: "h-10 rounded-md px-8", xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
icon: "h-9 w-9", sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
"icon-sm": "size-8", lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
icon: "size-9",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
"icon-lg": "size-10", "icon-lg": "size-10",
}, },
}, },
@@ -36,24 +41,27 @@ const buttonVariants = cva(
} }
) )
export interface ButtonProps function Button({
extends React.ButtonHTMLAttributes<HTMLButtonElement>, className,
VariantProps<typeof buttonVariants> { variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean asChild?: boolean
} }) {
const Comp = asChild ? Slot.Root : "button"
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "button"
return ( return (
<Comp <Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props} {...props}
/> />
) )
} }
)
Button.displayName = "Button"
export { Button, buttonVariants } export { Button, buttonVariants }
+75 -48
View File
@@ -2,75 +2,102 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Card = React.forwardRef< function Card({
HTMLDivElement, className,
React.HTMLAttributes<HTMLDivElement> size = "default",
>(({ className, ...props }, ref) => ( ...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div <div
ref={ref} data-slot="card"
data-size={size}
className={cn( className={cn(
"rounded-xl border bg-card text-card-foreground shadow", "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className className
)} )}
{...props} {...props}
/> />
)) )
Card.displayName = "Card" }
const CardHeader = React.forwardRef< function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-header"
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}
{...props} {...props}
/> />
)) )
CardHeader.displayName = "CardHeader" }
const CardTitle = React.forwardRef< function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-title"
className={cn("font-semibold leading-none tracking-tight", className)} className={cn(
"text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props} {...props}
/> />
)) )
CardTitle.displayName = "CardTitle" }
const CardDescription = React.forwardRef< function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) )
CardDescription.displayName = "CardDescription" }
const CardContent = React.forwardRef< function CardAction({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-action"
className={cn("flex items-center p-6 pt-0", className)} className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props} {...props}
/> />
)) )
CardFooter.displayName = "CardFooter" }
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
+5 -5
View File
@@ -1,8 +1,8 @@
import * as React from "react" import * as React from "react"
import { Checkbox as CheckboxPrimitive } from "radix-ui" import { Checkbox as CheckboxPrimitive } from "radix-ui"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { CheckIcon } from "lucide-react"
function Checkbox({ function Checkbox({
className, className,
@@ -12,20 +12,20 @@ function Checkbox({
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
className={cn( className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
className className
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
data-slot="checkbox-indicator" data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none" className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
> >
<CheckIcon className="size-3.5" /> <CheckIcon
/>
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
) )
} }
export { Checkbox } export { Checkbox }
+112 -71
View File
@@ -1,122 +1,163 @@
"use client"
import * as React from "react" import * as React from "react"
import { Dialog as DialogPrimitive } from "radix-ui" import { Dialog as DialogPrimitive } from "radix-ui"
import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { XIcon } from "lucide-react"
const Dialog = DialogPrimitive.Root function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
const DialogTrigger = DialogPrimitive.Trigger function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
const DialogPortal = DialogPrimitive.Portal function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
const DialogClose = DialogPrimitive.Close function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
const DialogOverlay = React.forwardRef< function DialogOverlay({
React.ElementRef<typeof DialogPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} data-slot="dialog-overlay"
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 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 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className className
)} )}
{...props} {...props}
/> />
)) )
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName }
const DialogContent = React.forwardRef< function DialogContent({
React.ElementRef<typeof DialogPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> children,
>(({ className, children, ...props }, ref) => ( showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} data-slot="dialog-content"
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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 sm:rounded-lg", "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> {showCloseButton && (
<X className="h-4 w-4" /> <DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-4 right-4"
size="icon-sm"
>
<XIcon
/>
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close> </DialogPrimitive.Close>
)}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) )
DialogContent.displayName = DialogPrimitive.Content.displayName }
const DialogHeader = ({ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className, return (
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
data-slot="dialog-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn( className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left", "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className className
)} )}
{...props} {...props}
/> >
) {children}
DialogHeader.displayName = "DialogHeader" {showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
const DialogFooter = ({ function DialogTitle({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof DialogPrimitive.Title>) {
<div return (
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} data-slot="dialog-title"
className={cn("leading-none font-medium", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn( className={cn(
"text-lg font-semibold leading-none tracking-tight", "text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className className
)} )}
{...props} {...props}
/> />
)) )
DialogTitle.displayName = DialogPrimitive.Title.displayName }
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export { export {
Dialog, Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription, DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} }
+9 -9
View File
@@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="empty" data-slot="empty"
className={cn( className={cn(
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12", "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-lg border-dashed p-12 text-center text-balance",
className className
)} )}
{...props} {...props}
@@ -19,10 +19,7 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="empty-header" data-slot="empty-header"
className={cn( className={cn("flex max-w-sm flex-col items-center gap-2", className)}
"flex max-w-sm flex-col items-center gap-2 text-center",
className
)}
{...props} {...props}
/> />
) )
@@ -34,7 +31,7 @@ const emptyMediaVariants = cva(
variants: { variants: {
variant: { variant: {
default: "bg-transparent", default: "bg-transparent",
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6", icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -62,7 +59,10 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="empty-title" data-slot="empty-title"
className={cn("text-lg font-medium tracking-tight", className)} className={cn(
"text-lg font-medium tracking-tight",
className
)}
{...props} {...props}
/> />
) )
@@ -73,7 +73,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
<div <div
data-slot="empty-description" data-slot="empty-description"
className={cn( className={cn(
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className className
)} )}
{...props} {...props}
@@ -86,7 +86,7 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="empty-content" data-slot="empty-content"
className={cn( className={cn(
"flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm", "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
className className
)} )}
{...props} {...props}
+4 -7
View File
@@ -2,21 +2,18 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( function Input({ className, type, ...props }: React.ComponentProps<"input">) {
({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
data-slot="input"
className={cn( className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
) )
} }
)
Input.displayName = "Input"
export { Input } export { Input }
+12 -16
View File
@@ -1,26 +1,22 @@
"use client"
import * as React from "react" import * as React from "react"
import { Label as LabelPrimitive } from "radix-ui" import { Label as LabelPrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const labelVariants = cva( function Label({
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className,
) ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const Label = React.forwardRef< return (
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
ref={ref} data-slot="label"
className={cn(labelVariants(), className)} className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props} {...props}
/> />
)) )
Label.displayName = LabelPrimitive.Root.displayName }
export { Label } export { Label }
+68 -56
View File
@@ -1,117 +1,129 @@
import * as React from "react" import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav <nav
role="navigation" role="navigation"
aria-label="pagination" aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)} className={cn("mx-auto flex w-full justify-center", className)}
{...props} {...props}
/> />
) )
Pagination.displayName = "Pagination" }
const PaginationContent = React.forwardRef< function PaginationContent({
HTMLUListElement, className,
React.ComponentProps<"ul"> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<"ul">) {
return (
<ul <ul
ref={ref} data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)} className={cn("flex items-center gap-1", className)}
{...props} {...props}
/> />
)) )
PaginationContent.displayName = "PaginationContent" }
const PaginationItem = React.forwardRef< function PaginationItem({ ...props }: React.ComponentProps<"li">) {
HTMLLIElement, return <li data-slot="pagination-item" {...props} />
React.ComponentProps<"li"> }
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean
} & Pick<ButtonProps, "size"> & } & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a"> React.ComponentProps<"a">
const PaginationLink = ({ function PaginationLink({
className, className,
isActive, isActive,
size = "icon", size = "icon",
...props ...props
}: PaginationLinkProps) => ( }: PaginationLinkProps) {
return (
<Button
asChild
variant={isActive ? "outline" : "ghost"}
size={size}
className={cn(className)}
>
<a <a
aria-current={isActive ? "page" : undefined} aria-current={isActive ? "page" : undefined}
className={cn( data-slot="pagination-link"
buttonVariants({ data-active={isActive}
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props} {...props}
/> />
) </Button>
PaginationLink.displayName = "PaginationLink" )
}
const PaginationPrevious = ({ function PaginationPrevious({
className, className,
text = "Previous",
...props ...props
}: React.ComponentProps<typeof PaginationLink>) => ( }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink <PaginationLink
aria-label="Go to previous page" aria-label="Go to previous page"
size="default" size="default"
className={cn("gap-1 pl-2.5", className)} className={cn("pl-2!", className)}
{...props} {...props}
> >
<ChevronLeft className="h-4 w-4" /> <ChevronLeftIcon data-icon="inline-start" />
<span>Previous</span> <span className="hidden sm:block">{text}</span>
</PaginationLink> </PaginationLink>
) )
PaginationPrevious.displayName = "PaginationPrevious" }
const PaginationNext = ({ function PaginationNext({
className, className,
text = "Next",
...props ...props
}: React.ComponentProps<typeof PaginationLink>) => ( }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink <PaginationLink
aria-label="Go to next page" aria-label="Go to next page"
size="default" size="default"
className={cn("gap-1 pr-2.5", className)} className={cn("pr-2!", className)}
{...props} {...props}
> >
<span>Next</span> <span className="hidden sm:block">{text}</span>
<ChevronRight className="h-4 w-4" /> <ChevronRightIcon data-icon="inline-end" />
</PaginationLink> </PaginationLink>
) )
PaginationNext.displayName = "PaginationNext" }
const PaginationEllipsis = ({ function PaginationEllipsis({
className, className,
...props ...props
}: React.ComponentProps<"span">) => ( }: React.ComponentProps<"span">) {
return (
<span <span
aria-hidden aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)} data-slot="pagination-ellipsis"
className={cn(
"flex size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} {...props}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontalIcon
/>
<span className="sr-only">More pages</span> <span className="sr-only">More pages</span>
</span> </span>
) )
PaginationEllipsis.displayName = "PaginationEllipsis" }
export { export {
Pagination, Pagination,
PaginationContent, PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis, PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} }
+12 -11
View File
@@ -1,28 +1,29 @@
"use client"
import * as React from "react" import * as React from "react"
import { Progress as ProgressPrimitive } from "radix-ui" import { Progress as ProgressPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Progress = React.forwardRef< function Progress({
React.ElementRef<typeof ProgressPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> value,
>(({ className, value, ...props }, ref) => ( ...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root <ProgressPrimitive.Root
ref={ref} data-slot="progress"
className={cn( className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20", "relative flex h-1.5 w-full items-center overflow-x-hidden rounded-full bg-muted",
className className
)} )}
{...props} {...props}
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all" data-slot="progress-indicator"
className="size-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
)) )
Progress.displayName = ProgressPrimitive.Root.displayName }
export { Progress } export { Progress }
+4 -6
View File
@@ -1,6 +1,5 @@
import * as React from "react" import * as React from "react"
import { RadioGroup as RadioGroupPrimitive } from "radix-ui" import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -11,7 +10,7 @@ function RadioGroup({
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
data-slot="radio-group" data-slot="radio-group"
className={cn("grid gap-3", className)} className={cn("grid w-full gap-3", className)}
{...props} {...props}
/> />
) )
@@ -25,20 +24,19 @@ function RadioGroupItem({
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
data-slot="radio-group-item" data-slot="radio-group-item"
className={cn( className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
className className
)} )}
{...props} {...props}
> >
<RadioGroupPrimitive.Indicator <RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator" data-slot="radio-group-indicator"
className="relative flex items-center justify-center" className="flex size-4 items-center justify-center"
> >
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" /> <span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) )
} }
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem }
+2 -4
View File
@@ -1,5 +1,3 @@
"use client"
import * as ResizablePrimitive from "react-resizable-panels" import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -35,13 +33,13 @@ function ResizableHandle({
<ResizablePrimitive.Separator <ResizablePrimitive.Separator
data-slot="resizable-handle" data-slot="resizable-handle"
className={cn( className={cn(
"bg-border focus-visible:ring-ring ring-offset-background relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90", "relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
className className
)} )}
{...props} {...props}
> >
{withHandle && ( {withHandle && (
<div className="bg-border h-6 w-1 rounded-lg z-10 flex shrink-0" /> <div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />
)} )}
</ResizablePrimitive.Separator> </ResizablePrimitive.Separator>
) )
+129 -98
View File
@@ -1,94 +1,83 @@
"use client"
import * as React from "react" import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui" import { Select as SelectPrimitive } from "radix-ui"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
const Select = SelectPrimitive.Root function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
const SelectGroup = SelectPrimitive.Group function SelectGroup({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1 p-1", className)}
{...props}
/>
)
}
const SelectValue = SelectPrimitive.Value function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
const SelectTrigger = React.forwardRef< function SelectTrigger({
React.ElementRef<typeof SelectPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> size = "default",
>(({ className, children, ...props }, ref) => ( children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} data-slot="select-trigger"
data-size={size}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) )
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName }
const SelectScrollUpButton = React.forwardRef< function SelectContent({
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> children,
>(({ className, ...props }, ref) => ( position = "item-aligned",
<SelectPrimitive.ScrollUpButton align = "center",
ref={ref} ...props
className={cn( }: React.ComponentProps<typeof SelectPrimitive.Content>) {
"flex cursor-default items-center justify-center py-1", return (
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} data-slot="select-content"
className={cn( data-align-trigger={position === "item-aligned"}
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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 origin-[--radix-select-content-transform-origin]", className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position} position={position}
align={align}
{...props} {...props}
> >
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
data-position={position}
className={cn( className={cn(
"p-1", "data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
position === "popper" && position === "popper" && ""
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)} )}
> >
{children} {children}
@@ -96,64 +85,106 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)) )
SelectContent.displayName = SelectPrimitive.Content.displayName }
const SelectLabel = React.forwardRef< function SelectLabel({
React.ElementRef<typeof SelectPrimitive.Label>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} data-slot="select-label"
className={cn("px-2 py-1.5 text-sm font-semibold", className)} className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
{...props} {...props}
/> />
)) )
SelectLabel.displayName = SelectPrimitive.Label.displayName }
const SelectItem = React.forwardRef< function SelectItem({
React.ElementRef<typeof SelectPrimitive.Item>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> children,
>(({ className, children, ...props }, ref) => ( ...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} data-slot="select-item"
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className className
)} )}
{...props} {...props}
> >
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)) )
SelectItem.displayName = SelectPrimitive.Item.displayName }
const SelectSeparator = React.forwardRef< function SelectSeparator({
React.ElementRef<typeof SelectPrimitive.Separator>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} data-slot="select-separator"
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) )
SelectSeparator.displayName = SelectPrimitive.Separator.displayName }
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronUpIcon
/>
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronDownIcon
/>
</SelectPrimitive.ScrollDownButton>
)
}
export { export {
Select, Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectGroup,
SelectItem, SelectItem,
SelectSeparator, SelectLabel,
SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} }
+10 -13
View File
@@ -3,27 +3,24 @@ import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Separator = React.forwardRef< function Separator({
React.ElementRef<typeof SeparatorPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> orientation = "horizontal",
>( decorative = true,
( ...props
{ className, orientation = "horizontal", decorative = true, ...props }, }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
ref return (
) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} data-slot="separator"
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"shrink-0 bg-border", "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className
)} )}
{...props} {...props}
/> />
) )
) }
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator } export { Separator }
+90 -88
View File
@@ -1,135 +1,137 @@
"use client"
import * as React from "react" import * as React from "react"
import { Dialog as SheetPrimitive } from "radix-ui" import { Dialog as SheetPrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { XIcon } from "lucide-react"
const Sheet = SheetPrimitive.Root function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
const SheetTrigger = SheetPrimitive.Trigger function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
const SheetClose = SheetPrimitive.Close function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
const SheetPortal = SheetPrimitive.Portal function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
const SheetOverlay = React.forwardRef< function SheetOverlay({
React.ElementRef<typeof SheetPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 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/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className className
)} )}
{...props} {...props}
ref={ref}
/> />
)) )
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName }
const sheetVariants = cva( function SheetContent({
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", className,
{ children,
variants: { side = "right",
side: { showCloseButton = true,
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", ...props
bottom: }: React.ComponentProps<typeof SheetPrimitive.Content> & {
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", side?: "top" | "right" | "bottom" | "left"
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", showCloseButton?: boolean
right: }) {
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", return (
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal> <SheetPortal>
<SheetOverlay /> <SheetOverlay />
<SheetPrimitive.Content <SheetPrimitive.Content
ref={ref} data-slot="sheet-content"
className={cn(sheetVariants({ side }), className)} data-side={side}
className={cn(
"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
className
)}
{...props} {...props}
> >
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children} {children}
{showCloseButton && (
<SheetPrimitive.Close data-slot="sheet-close" asChild>
<Button
variant="ghost"
className="absolute top-4 right-4"
size="icon-sm"
>
<XIcon
/>
<span className="sr-only">Close</span>
</Button>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Content> </SheetPrimitive.Content>
</SheetPortal> </SheetPortal>
)) )
SheetContent.displayName = SheetPrimitive.Content.displayName }
const SheetHeader = ({ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
className, return (
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( data-slot="sheet-header"
"flex flex-col space-y-2 text-center sm:text-left", className={cn("flex flex-col gap-1.5 p-4", className)}
className
)}
{...props} {...props}
/> />
) )
SheetHeader.displayName = "SheetHeader" }
const SheetFooter = ({ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
className, return (
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( data-slot="sheet-footer"
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className={cn("mt-auto flex flex-col gap-2 p-4", className)}
className
)}
{...props} {...props}
/> />
) )
SheetFooter.displayName = "SheetFooter" }
const SheetTitle = React.forwardRef< function SheetTitle({
React.ElementRef<typeof SheetPrimitive.Title>, className,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title <SheetPrimitive.Title
ref={ref} data-slot="sheet-title"
className={cn("text-lg font-semibold text-foreground", className)} className={cn("font-medium text-foreground", className)}
{...props} {...props}
/> />
)) )
SheetTitle.displayName = SheetPrimitive.Title.displayName }
const SheetDescription = React.forwardRef< function SheetDescription({
React.ElementRef<typeof SheetPrimitive.Description>, className,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description <SheetPrimitive.Description
ref={ref} data-slot="sheet-description"
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) )
SheetDescription.displayName = SheetPrimitive.Description.displayName }
export { export {
Sheet, Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger, SheetTrigger,
SheetClose, SheetClose,
SheetContent, SheetContent,
+38 -78
View File
@@ -1,9 +1,8 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Slot as SlotPrimitive } from "radix-ui" import { cva, type VariantProps } from "class-variance-authority"
import { cva, VariantProps } from "class-variance-authority" import { Slot } from "radix-ui"
import { PanelLeftIcon } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -21,12 +20,12 @@ import { Skeleton } from "@/components/ui/skeleton"
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } from "@/components/ui/tooltip"
import { PanelLeftIcon } from "lucide-react"
const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 30 // 30 days const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_WIDTH_ICON = "3rem"
@@ -69,25 +68,9 @@ function SidebarProvider({
const isMobile = useIsMobile() const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false) const [openMobile, setOpenMobile] = React.useState(false)
// Helper to read cookie value
const getCookieValue = (name: string): boolean | null => {
if (typeof document === "undefined") return null
const value = document.cookie
.split("; ")
.find((row) => row.startsWith(`${name}=`))
?.split("=")[1]
return value === "true" ? true : value === "false" ? false : null
}
// Read initial state from cookie, fallback to defaultOpen
const getInitialState = () => {
const cookieValue = getCookieValue(SIDEBAR_COOKIE_NAME)
return cookieValue !== null ? cookieValue : defaultOpen
}
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(getInitialState) const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open const open = openProp ?? _open
const setOpen = React.useCallback( const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => { (value: boolean | ((value: boolean) => boolean)) => {
@@ -144,7 +127,6 @@ function SidebarProvider({
return ( return (
<SidebarContext.Provider value={contextValue}> <SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div <div
data-slot="sidebar-wrapper" data-slot="sidebar-wrapper"
style={ style={
@@ -155,14 +137,13 @@ function SidebarProvider({
} as React.CSSProperties } as React.CSSProperties
} }
className={cn( className={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
className className
)} )}
{...props} {...props}
> >
{children} {children}
</div> </div>
</TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
) )
} }
@@ -173,6 +154,7 @@ function Sidebar({
collapsible = "offcanvas", collapsible = "offcanvas",
className, className,
children, children,
dir,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
side?: "left" | "right" side?: "left" | "right"
@@ -186,7 +168,7 @@ function Sidebar({
<div <div
data-slot="sidebar" data-slot="sidebar"
className={cn( className={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className className
)} )}
{...props} {...props}
@@ -200,10 +182,11 @@ function Sidebar({
return ( return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent <SheetContent
dir={dir}
data-sidebar="sidebar" data-sidebar="sidebar"
data-slot="sidebar" data-slot="sidebar"
data-mobile="true" data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden" className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={ style={
{ {
"--sidebar-width": SIDEBAR_WIDTH_MOBILE, "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -223,7 +206,7 @@ function Sidebar({
return ( return (
<div <div
className="group peer text-sidebar-foreground hidden md:block" className="group peer hidden text-sidebar-foreground md:block"
data-state={state} data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""} data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant} data-variant={variant}
@@ -244,11 +227,9 @@ function Sidebar({
/> />
<div <div
data-slot="sidebar-container" data-slot="sidebar-container"
data-side={side}
className={cn( className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
@@ -260,7 +241,7 @@ function Sidebar({
<div <div
data-sidebar="sidebar" data-sidebar="sidebar"
data-slot="sidebar-inner" data-slot="sidebar-inner"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm" className="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border"
> >
{children} {children}
</div> </div>
@@ -281,8 +262,8 @@ function SidebarTrigger({
data-sidebar="trigger" data-sidebar="trigger"
data-slot="sidebar-trigger" data-slot="sidebar-trigger"
variant="ghost" variant="ghost"
size="icon" size="icon-sm"
className={cn("size-7", className)} className={cn(className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event) onClick?.(event)
toggleSidebar() toggleSidebar()
@@ -307,10 +288,10 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
onClick={toggleSidebar} onClick={toggleSidebar}
title="Toggle Sidebar" title="Toggle Sidebar"
className={cn( className={cn(
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className className
@@ -325,8 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main <main
data-slot="sidebar-inset" data-slot="sidebar-inset"
className={cn( className={cn(
"bg-background relative flex w-full flex-1 flex-col", "relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className className
)} )}
{...props} {...props}
@@ -342,7 +322,7 @@ function SidebarInput({
<Input <Input
data-slot="sidebar-input" data-slot="sidebar-input"
data-sidebar="input" data-sidebar="input"
className={cn("bg-background h-8 w-full shadow-none", className)} className={cn("h-8 w-full bg-background shadow-none", className)}
{...props} {...props}
/> />
) )
@@ -378,7 +358,7 @@ function SidebarSeparator({
<Separator <Separator
data-slot="sidebar-separator" data-slot="sidebar-separator"
data-sidebar="separator" data-sidebar="separator"
className={cn("bg-sidebar-border mx-2 w-auto", className)} className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props} {...props}
/> />
) )
@@ -390,7 +370,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-content" data-slot="sidebar-content"
data-sidebar="content" data-sidebar="content"
className={cn( className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", "no-scrollbar flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className className
)} )}
{...props} {...props}
@@ -414,15 +394,14 @@ function SidebarGroupLabel({
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) { }: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "div" const Comp = asChild ? Slot.Root : "div"
return ( return (
<Comp <Comp
data-slot="sidebar-group-label" data-slot="sidebar-group-label"
data-sidebar="group-label" data-sidebar="group-label"
className={cn( className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className className
)} )}
{...props} {...props}
@@ -435,17 +414,14 @@ function SidebarGroupAction({
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) { }: React.ComponentProps<"button"> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? Slot.Root : "button"
return ( return (
<Comp <Comp
data-slot="sidebar-group-action" data-slot="sidebar-group-action"
data-sidebar="group-action" data-sidebar="group-action"
className={cn( className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className className
)} )}
{...props} {...props}
@@ -490,13 +466,13 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
} }
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
{ {
variants: { variants: {
variant: { variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline: outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
}, },
size: { size: {
default: "h-8 text-sm", default: "h-8 text-sm",
@@ -524,7 +500,7 @@ function SidebarMenuButton({
isActive?: boolean isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent> tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>) { } & VariantProps<typeof sidebarMenuButtonVariants>) {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? Slot.Root : "button"
const { isMobile, state } = useSidebar() const { isMobile, state } = useSidebar()
const button = ( const button = (
@@ -570,22 +546,16 @@ function SidebarMenuAction({
asChild?: boolean asChild?: boolean
showOnHover?: boolean showOnHover?: boolean
}) { }) {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? Slot.Root : "button"
return ( return (
<Comp <Comp
data-slot="sidebar-menu-action" data-slot="sidebar-menu-action"
data-sidebar="menu-action" data-sidebar="menu-action"
className={cn( className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover && showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
className className
)} )}
{...props} {...props}
@@ -602,12 +572,7 @@ function SidebarMenuBadge({
data-slot="sidebar-menu-badge" data-slot="sidebar-menu-badge"
data-sidebar="menu-badge" data-sidebar="menu-badge"
className={cn( className={cn(
"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none", "pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className className
)} )}
{...props} {...props}
@@ -623,9 +588,9 @@ function SidebarMenuSkeleton({
showIcon?: boolean showIcon?: boolean
}) { }) {
// Random width between 50 to 90%. // Random width between 50 to 90%.
const width = React.useMemo(() => { const [width] = React.useState(() => {
return `${Math.floor(Math.random() * 40) + 50}%` return `${Math.floor(Math.random() * 40) + 50}%`
}, []) })
return ( return (
<div <div
@@ -659,8 +624,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu-sub" data-slot="sidebar-menu-sub"
data-sidebar="menu-sub" data-sidebar="menu-sub"
className={cn( className={cn(
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5", "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
"group-data-[collapsible=icon]:hidden",
className className
)} )}
{...props} {...props}
@@ -693,7 +657,7 @@ function SidebarMenuSubButton({
size?: "sm" | "md" size?: "sm" | "md"
isActive?: boolean isActive?: boolean
}) { }) {
const Comp = asChild ? SlotPrimitive.Slot : "a" const Comp = asChild ? Slot.Root : "a"
return ( return (
<Comp <Comp
@@ -702,11 +666,7 @@ function SidebarMenuSubButton({
data-size={size} data-size={size}
data-active={isActive} data-active={isActive}
className={cn( className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className className
)} )}
{...props} {...props}
+3 -5
View File
@@ -1,12 +1,10 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Skeleton({ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div
className={cn("animate-pulse rounded-md bg-primary/10", className)} data-slot="skeleton"
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props} {...props}
/> />
) )
+42 -11
View File
@@ -3,24 +3,55 @@ import { Slider as SliderPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Slider = React.forwardRef< function Slider({
React.ElementRef<typeof SliderPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> defaultValue,
>(({ className, ...props }, ref) => ( value,
min = 0,
max = 100,
...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
const _values = React.useMemo(
() =>
Array.isArray(value)
? value
: Array.isArray(defaultValue)
? defaultValue
: [min, max],
[value, defaultValue, min, max]
)
return (
<SliderPrimitive.Root <SliderPrimitive.Root
ref={ref} data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn( className={cn(
"relative flex w-full touch-none select-none items-center", "relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col",
className className
)} )}
{...props} {...props}
> >
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20"> <SliderPrimitive.Track
<SliderPrimitive.Range className="absolute h-full bg-primary" /> data-slot="slider-track"
className="relative grow overflow-hidden rounded-full bg-muted data-horizontal:h-1.5 data-horizontal:w-full data-vertical:h-full data-vertical:w-1.5"
>
<SliderPrimitive.Range
data-slot="slider-range"
className="absolute bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/>
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" /> {Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] select-none hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Root> </SliderPrimitive.Root>
)) )
Slider.displayName = SliderPrimitive.Root.displayName }
export { Slider } export { Slider }
+18 -18
View File
@@ -1,16 +1,6 @@
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner" import { Toaster as Sonner, type ToasterProps } from "sonner"
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme()
@@ -20,11 +10,21 @@ const Toaster = ({ ...props }: ToasterProps) => {
theme={theme as ToasterProps["theme"]} theme={theme as ToasterProps["theme"]}
className="toaster group" className="toaster group"
icons={{ icons={{
success: <CircleCheckIcon className="size-4" />, success: (
info: <InfoIcon className="size-4" />, <CircleCheckIcon className="size-4" />
warning: <TriangleAlertIcon className="size-4" />, ),
error: <OctagonXIcon className="size-4" />, info: (
loading: <Loader2Icon className="size-4 animate-spin" />, <InfoIcon className="size-4" />
),
warning: (
<TriangleAlertIcon className="size-4" />
),
error: (
<OctagonXIcon className="size-4" />
),
loading: (
<Loader2Icon className="size-4 animate-spin" />
),
}} }}
style={ style={
{ {
@@ -36,7 +36,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
} }
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: "group toast", toast: "group cn-toast",
icon: "group-data-[type=error]:!text-red-500 group-data-[type=success]:!text-green-500 group-data-[type=warning]:!text-amber-500 group-data-[type=info]:!text-sky-500", icon: "group-data-[type=error]:!text-red-500 group-data-[type=success]:!text-green-500 group-data-[type=warning]:!text-amber-500 group-data-[type=info]:!text-sky-500",
description: "group-[.toast]:!text-muted-foreground", description: "group-[.toast]:!text-muted-foreground",
actionButton: "group-[.toast]:!bg-primary group-[.toast]:!text-primary-foreground", actionButton: "group-[.toast]:!bg-primary group-[.toast]:!text-primary-foreground",
+2 -8
View File
@@ -1,15 +1,9 @@
import { Loader2Icon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Loader2Icon } from "lucide-react"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) { function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return ( return (
<Loader2Icon <Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
) )
} }
+19 -15
View File
@@ -1,27 +1,31 @@
import * as React from "react" import * as React from "react"
import { Switch as SwitchPrimitives } from "radix-ui" import { Switch as SwitchPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Switch = React.forwardRef< function Switch({
React.ElementRef<typeof SwitchPrimitives.Root>, className,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> size = "default",
>(({ className, ...props }, ref) => ( ...props
<SwitchPrimitives.Root }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
size?: "sm" | "default"
}) {
return (
<SwitchPrimitive.Root
data-slot="switch"
data-size={size}
className={cn( className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
className className
)} )}
{...props} {...props}
ref={ref}
> >
<SwitchPrimitives.Thumb <SwitchPrimitive.Thumb
className={cn( data-slot="switch-thumb"
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
)}
/> />
</SwitchPrimitives.Root> </SwitchPrimitive.Root>
)) )
Switch.displayName = SwitchPrimitives.Root.displayName }
export { Switch } export { Switch }
+67 -32
View File
@@ -1,53 +1,88 @@
import * as React from "react" import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Tabs as TabsPrimitive } from "radix-ui" import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root function Tabs({
className,
orientation = "horizontal",
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
className={cn(
"group/tabs flex gap-2 data-horizontal:flex-col",
className
)}
{...props}
/>
)
}
const TabsList = React.forwardRef< const tabsListVariants = cva(
React.ElementRef<typeof TabsPrimitive.List>, "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {
>(({ className, ...props }, ref) => ( variants: {
variant: {
default: "bg-muted",
line: "gap-1 bg-transparent",
},
},
defaultVariants: {
variant: "default",
},
}
)
function TabsList({
className,
variant = "default",
...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
VariantProps<typeof tabsListVariants>) {
return (
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} data-slot="tabs-list"
className={cn( data-variant={variant}
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className={cn(tabsListVariants({ variant }), className)}
className
)}
{...props} {...props}
/> />
)) )
TabsList.displayName = TabsPrimitive.List.displayName }
const TabsTrigger = React.forwardRef< function TabsTrigger({
React.ElementRef<typeof TabsPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} data-slot="tabs-trigger"
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className className
)} )}
{...props} {...props}
/> />
)) )
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName }
const TabsContent = React.forwardRef< function TabsContent({
React.ElementRef<typeof TabsPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content <TabsPrimitive.Content
ref={ref} data-slot="tabs-content"
className={cn( className={cn("flex-1 text-sm outline-none", className)}
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props} {...props}
/> />
)) )
TabsContent.displayName = TabsPrimitive.Content.displayName }
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
+4 -8
View File
@@ -2,21 +2,17 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Textarea = React.forwardRef< function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return ( return (
<textarea <textarea
data-slot="textarea"
className={cn( className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
) )
}) }
Textarea.displayName = "Textarea"
export { Textarea } export { Textarea }
+50 -22
View File
@@ -1,48 +1,78 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
import { type VariantProps } from "class-variance-authority" import { type VariantProps } from "class-variance-authority"
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle" import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext< const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants> & {
spacing?: number
orientation?: "horizontal" | "vertical"
}
>({ >({
size: "default", size: "default",
variant: "default", variant: "default",
spacing: 2,
orientation: "horizontal",
}) })
const ToggleGroup = React.forwardRef< function ToggleGroup({
React.ElementRef<typeof ToggleGroupPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> & variant,
VariantProps<typeof toggleVariants> size,
>(({ className, variant, size, children, ...props }, ref) => ( spacing = 2,
orientation = "horizontal",
children,
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants> & {
spacing?: number
orientation?: "horizontal" | "vertical"
}) {
return (
<ToggleGroupPrimitive.Root <ToggleGroupPrimitive.Root
ref={ref} data-slot="toggle-group"
className={cn("flex items-center justify-center gap-1", className)} data-variant={variant}
data-size={size}
data-spacing={spacing}
data-orientation={orientation}
style={{ "--gap": spacing } as React.CSSProperties}
className={cn(
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
className
)}
{...props} {...props}
> >
<ToggleGroupContext.Provider value={{ variant, size }}> <ToggleGroupContext.Provider
value={{ variant, size, spacing, orientation }}
>
{children} {children}
</ToggleGroupContext.Provider> </ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root> </ToggleGroupPrimitive.Root>
)) )
}
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName function ToggleGroupItem({
className,
const ToggleGroupItem = React.forwardRef< children,
React.ElementRef<typeof ToggleGroupPrimitive.Item>, variant = "default",
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & size = "default",
VariantProps<typeof toggleVariants> ...props
>(({ className, children, variant, size, ...props }, ref) => { }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext) const context = React.useContext(ToggleGroupContext)
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
ref={ref} data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
data-spacing={context.spacing}
className={cn( className={cn(
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md data-[state=on]:bg-muted group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
toggleVariants({ toggleVariants({
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, size: context.size || size,
@@ -54,8 +84,6 @@ const ToggleGroupItem = React.forwardRef<
{children} {children}
</ToggleGroupPrimitive.Item> </ToggleGroupPrimitive.Item>
) )
}) }
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem } export { ToggleGroup, ToggleGroupItem }
+18 -16
View File
@@ -1,22 +1,22 @@
import * as React from "react" import * as React from "react"
import { Toggle as TogglePrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { Toggle as TogglePrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "group/toggle inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-transparent", default: "bg-transparent",
outline: outline: "border border-input bg-transparent shadow-xs hover:bg-muted",
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
}, },
size: { size: {
default: "h-9 px-2 min-w-9", default:
sm: "h-8 px-1.5 min-w-8", "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
lg: "h-10 px-2.5 min-w-10", sm: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
lg: "h-10 min-w-10 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -26,18 +26,20 @@ const toggleVariants = cva(
} }
) )
const Toggle = React.forwardRef< function Toggle({
React.ElementRef<typeof TogglePrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & variant = "default",
VariantProps<typeof toggleVariants> size = "default",
>(({ className, variant, size, ...props }, ref) => ( ...props
}: React.ComponentProps<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<TogglePrimitive.Root <TogglePrimitive.Root
ref={ref} data-slot="toggle"
className={cn(toggleVariants({ variant, size, className }))} className={cn(toggleVariants({ variant, size, className }))}
{...props} {...props}
/> />
)) )
}
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants } export { Toggle, toggleVariants }
+38 -13
View File
@@ -5,28 +5,53 @@ import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
const Tooltip = TooltipPrimitive.Root function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}
const TooltipTrigger = TooltipPrimitive.Trigger function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
const TooltipContent = React.forwardRef< function TooltipContent({
React.ElementRef<typeof TooltipPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> sideOffset = 0,
>(({ className, sideOffset = 4, ...props }, ref) => ( children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipPrimitive.Content <TooltipPrimitive.Content
ref={ref} data-slot="tooltip-content"
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 origin-[--radix-tooltip-content-transform-origin]", "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 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 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className className
)} )}
{...props} {...props}
/> >
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
)) )
TooltipContent.displayName = TooltipPrimitive.Content.displayName }
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
+2
View File
@@ -1,5 +1,7 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "@fontsource-variable/inter";
@custom-variant dark (&:is(.dark *, .blue-dark *, .green-dark *, .orange-dark *, .red-dark *, .rose-dark *, .violet-dark *, .yellow-dark *)); @custom-variant dark (&:is(.dark *, .blue-dark *, .green-dark *, .orange-dark *, .red-dark *, .rose-dark *, .violet-dark *, .yellow-dark *));
@custom-variant light (&:is(:not(.dark):not(.blue-dark):not(.green-dark):not(.orange-dark):not(.red-dark):not(.rose-dark):not(.violet-dark):not(.yellow-dark) *)); @custom-variant light (&:is(:not(.dark):not(.blue-dark):not(.green-dark):not(.orange-dark):not(.red-dark):not(.rose-dark):not(.violet-dark):not(.yellow-dark) *));
+2 -1
View File
@@ -78,7 +78,7 @@ export default function LibraryPage() {
Stop Stop
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent size="sm">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Stop all ongoing downloads?</AlertDialogTitle> <AlertDialogTitle>Stop all ongoing downloads?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
@@ -88,6 +88,7 @@ export default function LibraryPage() {
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
variant="destructive"
onClick={() => stopOngoingDownloads()} onClick={() => stopOngoingDownloads()}
>Stop</AlertDialogAction> >Stop</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
+2 -2
View File
@@ -53,7 +53,7 @@ export default function SettingsPage() {
Reset Reset
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent size="sm">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Reset settings to default?</AlertDialogTitle> <AlertDialogTitle>Reset settings to default?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
@@ -62,7 +62,7 @@ export default function SettingsPage() {
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={ <AlertDialogAction variant="destructive" onClick={
async () => { async () => {
resetSettings(); resetSettings();
if (isRunningPotServer) { if (isRunningPotServer) {