last commit before trying out opencode on repo

This commit is contained in:
2026-01-11 10:17:30 -05:00
parent 843b264c61
commit 91682bd887
85 changed files with 1589 additions and 1196 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,11 @@
import { defineConfig } from "eslint/config";
import { defineConfig } from 'eslint/config';
import { baseConfig } from "@gib/eslint-config/base";
import { reactConfig } from "@gib/eslint-config/react";
import { baseConfig } from '@gib/eslint-config/base';
import { reactConfig } from '@gib/eslint-config/react';
export default defineConfig(
{
ignores: ["dist/**"],
ignores: ['dist/**'],
},
baseConfig,
reactConfig,

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '@gib/ui';
function Avatar({
className,
@@ -12,7 +13,7 @@ function Avatar({
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
'relative flex size-8 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
@@ -27,7 +28,7 @@ function AvatarImage({
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
className={cn('aspect-square size-full', className)}
{...props}
/>
);
@@ -41,7 +42,7 @@ function AvatarFallback({
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
'bg-muted flex size-full items-center justify-center rounded-full',
className,
)}
{...props}

View File

@@ -1,15 +1,15 @@
"use client";
'use client';
import { type ComponentProps } from "react";
import { AvatarImage } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { User } from "lucide-react";
import { type ComponentProps } from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { User } from 'lucide-react';
import { AvatarImage, cn } from '@gib/ui';
type BasedAvatarProps = ComponentProps<typeof AvatarPrimitive.Root> & {
src?: string | null;
fullName?: string | null;
imageProps?: Omit<ComponentProps<typeof AvatarImage>, "data-slot">;
imageProps?: Omit<ComponentProps<typeof AvatarImage>, 'data-slot'>;
fallbackProps?: ComponentProps<typeof AvatarPrimitive.Fallback>;
userIconProps?: ComponentProps<typeof User>;
};
@@ -29,7 +29,7 @@ const BasedAvatar = ({
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 cursor-pointer overflow-hidden rounded-full",
'relative flex size-8 shrink-0 cursor-pointer overflow-hidden rounded-full',
className,
)}
{...props}
@@ -45,20 +45,20 @@ const BasedAvatar = ({
{...fallbackProps}
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
'bg-muted flex size-full items-center justify-center rounded-full',
fallbackProps?.className,
)}
>
{fullName ? (
fullName
.split(" ")
.split(' ')
.map((n) => n[0])
.join("")
.join('')
.toUpperCase()
) : (
<User
{...userIconProps}
className={cn("", userIconProps?.className)}
className={cn('', userIconProps?.className)}
/>
)}
</AvatarPrimitive.Fallback>

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from '@gib/ui';
type BasedProgressProps = React.ComponentProps<
typeof ProgressPrimitive.Root
@@ -36,7 +37,7 @@ const BasedProgress = ({
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className,
)}
{...props}

View File

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

View File

@@ -1,12 +1,13 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import * as React from 'react';
function Card({ className, ...props }: React.ComponentProps<"div">) {
import { cn } from '@gib/ui';
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
className,
)}
{...props}
@@ -14,12 +15,12 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
);
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
@@ -27,32 +28,32 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
);
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
className={cn('leading-none font-semibold', className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
@@ -60,21 +61,21 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
);
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
className={cn('px-6', className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
);

View File

@@ -1,9 +1,10 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from 'lucide-react';
import { cn } from '@gib/ui';
function Checkbox({
className,
@@ -13,7 +14,7 @@ function Checkbox({
<CheckboxPrimitive.Root
data-slot="checkbox"
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 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',
className,
)}
{...props}

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import { Drawer as DrawerPrimitive } from "vaul";
import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from '@gib/ui';
function Drawer({
...props
@@ -36,7 +37,7 @@ function DrawerOverlay({
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
@@ -55,11 +56,11 @@ function DrawerContent({
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',
'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t',
'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',
'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',
className,
)}
{...props}
@@ -71,12 +72,12 @@ function DrawerContent({
);
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-header"
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',
className,
)}
{...props}
@@ -84,11 +85,11 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
);
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
);
@@ -101,7 +102,7 @@ function DrawerTitle({
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("text-foreground font-semibold", className)}
className={cn('text-foreground font-semibold', className)}
{...props}
/>
);
@@ -114,7 +115,7 @@ function DrawerDescription({
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);

View File

@@ -1,9 +1,10 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
import { cn } from '@gib/ui';
function DropdownMenu({
...props
@@ -41,7 +42,7 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className,
)}
{...props}
@@ -61,11 +62,11 @@ function DropdownMenuGroup({
function DropdownMenuItem({
className,
inset,
variant = "default",
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
variant?: 'default' | 'destructive';
}) {
return (
<DropdownMenuPrimitive.Item
@@ -154,7 +155,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
className,
)}
{...props}
@@ -169,7 +170,7 @@ function DropdownMenuSeparator({
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
@@ -178,12 +179,12 @@ function DropdownMenuSeparator({
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
@@ -210,7 +211,7 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
className,
)}
{...props}
@@ -229,7 +230,7 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className,
)}
{...props}

View File

@@ -1,23 +1,21 @@
"use client";
'use client';
import type { VariantProps } from "class-variance-authority";
import { useMemo } from "react";
import { cva } from "class-variance-authority";
import type { VariantProps } from 'class-variance-authority';
import { useMemo } from 'react';
import { cva } from 'class-variance-authority';
import { cn } from "@acme/ui";
import { Label } from "@acme/ui/label";
import { Separator } from "@acme/ui/separator";
import { cn, Label, Separator } from '@gib/ui';
export function FieldSet({
className,
...props
}: React.ComponentProps<"fieldset">) {
}: React.ComponentProps<'fieldset'>) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-6",
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
'flex flex-col gap-6',
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
className,
)}
{...props}
@@ -27,17 +25,17 @@ export function FieldSet({
export function FieldLegend({
className,
variant = "legend",
variant = 'legend',
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
"mb-3 font-medium",
"data-[variant=legend]:text-base",
"data-[variant=label]:text-sm",
'mb-3 font-medium',
'data-[variant=legend]:text-base',
'data-[variant=label]:text-sm',
className,
)}
{...props}
@@ -48,12 +46,12 @@ export function FieldLegend({
export function FieldGroup({
className,
...props
}: React.ComponentProps<"div">) {
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-group"
className={cn(
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
className,
)}
{...props}
@@ -62,34 +60,34 @@ export function FieldGroup({
}
const fieldVariants = cva(
"group/field data-[invalid=true]:text-destructive flex w-full gap-3",
'group/field data-[invalid=true]:text-destructive flex w-full gap-3',
{
variants: {
orientation: {
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
'flex-row items-center',
'[&>[data-slot=field-label]]:flex-auto',
'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
],
responsive: [
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
'flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto',
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
],
},
},
defaultVariants: {
orientation: "vertical",
orientation: 'vertical',
},
},
);
export function Field({
className,
orientation = "vertical",
orientation = 'vertical',
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
@@ -104,12 +102,12 @@ export function Field({
export function FieldContent({
className,
...props
}: React.ComponentProps<"div">) {
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
'group/field-content flex flex-1 flex-col gap-1.5 leading-snug',
className,
)}
{...props}
@@ -125,9 +123,9 @@ export function FieldLabel({
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
className,
)}
{...props}
@@ -138,12 +136,12 @@ export function FieldLabel({
export function FieldTitle({
className,
...props
}: React.ComponentProps<"div">) {
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
className,
)}
{...props}
@@ -154,14 +152,14 @@ export function FieldTitle({
export function FieldDescription({
className,
...props
}: React.ComponentProps<"p">) {
}: React.ComponentProps<'p'>) {
return (
<p
data-slot="field-description"
className={cn(
"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
className,
)}
{...props}
@@ -173,7 +171,7 @@ export function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
}: React.ComponentProps<'div'> & {
children?: React.ReactNode;
}) {
return (
@@ -181,7 +179,7 @@ export function FieldSeparator({
data-slot="field-separator"
data-content={!!children}
className={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
className,
)}
{...props}
@@ -204,7 +202,7 @@ export function FieldError({
children,
errors: maybeErrors,
...props
}: React.ComponentProps<"div"> & {
}: React.ComponentProps<'div'> & {
errors?: ({ message?: string } | undefined)[];
}) {
const content = useMemo(() => {
@@ -240,7 +238,7 @@ export function FieldError({
<div
role="alert"
data-slot="field-error"
className={cn("text-destructive text-sm font-normal", className)}
className={cn('text-destructive text-sm font-normal', className)}
{...props}
>
{content}

View File

@@ -1,17 +1,17 @@
"use client";
'use client';
import type { ControllerProps, FieldPath, FieldValues } from "react-hook-form";
import * as React from "react";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import {
Controller,
FormProvider,
useFormContext,
useFormState,
} from "react-hook-form";
} from 'react-hook-form';
import { cn, Label } from '@gib/ui';
const Form = FormProvider;
@@ -47,7 +47,7 @@ const useFormField = () => {
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
throw new Error('useFormField should be used within <FormField>');
}
const { id } = itemContext;
@@ -70,14 +70,14 @@ const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
className={cn('grid gap-2', className)}
{...props}
/>
</FormItemContext.Provider>
@@ -94,7 +94,7 @@ function FormLabel({
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
className={cn('data-[error=true]:text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
@@ -120,22 +120,22 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
);
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
const { formDescriptionId } = useFormField();
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? "") : props.children;
const body = error ? String(error?.message ?? '') : props.children;
if (!body) {
return null;
@@ -145,7 +145,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
className={cn('text-destructive text-sm', className)}
{...props}
>
{body}

View File

@@ -1,16 +1,109 @@
import { cx } from "class-variance-authority";
import { twMerge } from "tailwind-merge";
import { cx } from 'class-variance-authority';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: Parameters<typeof cx>) => twMerge(cx(inputs));
export const ccn = ({
context,
className,
on = "",
off = "",
on = '',
off = '',
}: {
context: boolean;
className: string;
on: string;
off: string;
}) => twMerge(className, context ? on : off);
export { Avatar, AvatarImage, AvatarFallback } from './avatar';
export { BasedAvatar } from './based-avatar';
export { BasedProgress } from './based-progress';
export { Button, buttonVariants } from './button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
} from './card';
export { Checkbox } from './checkbox';
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
} from './drawer';
export {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from './dropdown-menu';
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
} from './form';
export {
type ImageCropProps,
type ImageCropApplyProps,
type ImageCropContentProps,
type ImageCropResetProps,
type CropperProps,
Cropper,
ImageCrop,
ImageCropApply,
ImageCropContent,
ImageCropReset,
} from './shadcn-io/image-crop';
export { Input } from './input';
export {
InputOTP,
InputOTPGroup,
InputOTPSlot,
InputOTPSeparator,
} from './input-otp';
export { Label } from './label';
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from './pagination';
export { Progress } from './progress';
export { ScrollArea, ScrollBar } from './scroll-area';
export { Separator } from './separator';
export { StatusMessage } from './status-message';
export { SubmitButton } from './submit-button';
export { Switch } from './switch';
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
} from './table';
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs';
export { Toaster } from './sonner';
export { ThemeProvider, ThemeToggle, type ThemeToggleProps } from './theme';

View File

@@ -1,9 +1,10 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import { OTPInput, OTPInputContext } from "input-otp";
import { MinusIcon } from "lucide-react";
import * as React from 'react';
import { OTPInput, OTPInputContext } from 'input-otp';
import { MinusIcon } from 'lucide-react';
import { cn } from '@gib/ui';
function InputOTP({
className,
@@ -16,20 +17,20 @@ function InputOTP({
<OTPInput
data-slot="input-otp"
containerClassName={cn(
"flex items-center gap-2 has-disabled:opacity-50",
'flex items-center gap-2 has-disabled:opacity-50',
containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
);
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="input-otp-group"
className={cn("flex items-center", className)}
className={cn('flex items-center', className)}
{...props}
/>
);
@@ -39,7 +40,7 @@ function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<"div"> & {
}: React.ComponentProps<'div'> & {
index: number;
}) {
const inputOTPContext = React.useContext(OTPInputContext);
@@ -50,7 +51,7 @@ function InputOTPSlot({
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
'data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]',
className,
)}
{...props}
@@ -65,7 +66,7 @@ function InputOTPSlot({
);
}
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
return (
<div data-slot="input-otp-separator" role="separator" {...props}>
<MinusIcon />

View File

@@ -1,15 +1,16 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import * as React from 'react';
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
import { cn } from '@gib/ui';
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 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 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 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 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
)}
{...props}

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as LabelPrimitive from "@radix-ui/react-label";
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cn } from '@gib/ui';
function Label({
className,
@@ -12,7 +13,7 @@ function Label({
<LabelPrimitive.Root
data-slot="label"
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",
'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}

View File

@@ -1,19 +1,19 @@
import * as React from "react";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import * as React from 'react';
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react";
} from 'lucide-react';
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
import { Button, buttonVariants, cn } from '@gib/ui';
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
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}
/>
);
@@ -22,39 +22,39 @@ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
function PaginationContent({
className,
...props
}: React.ComponentProps<"ul">) {
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
);
}
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
className,
isActive,
size = "icon",
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? "page" : undefined}
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
variant: isActive ? 'outline' : 'ghost',
size,
}),
className,
@@ -72,7 +72,7 @@ function PaginationPrevious({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon />
@@ -89,7 +89,7 @@ function PaginationNext({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<span className="hidden sm:block">Next</span>
@@ -101,12 +101,12 @@ function PaginationNext({
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from '@gib/ui';
function Progress({
className,
@@ -13,7 +14,7 @@ function Progress({
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className,
)}
{...props}

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@gib/ui';
function ScrollArea({
className,
@@ -12,7 +13,7 @@ function ScrollArea({
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
className={cn('relative', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
@@ -29,7 +30,7 @@ function ScrollArea({
function ScrollBar({
className,
orientation = "vertical",
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
@@ -37,11 +38,11 @@ function ScrollBar({
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
'flex touch-none p-px transition-colors select-none',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent',
className,
)}
{...props}

View File

@@ -1,12 +1,13 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '@gib/ui';
function Separator({
className,
orientation = "horizontal",
orientation = 'horizontal',
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
@@ -16,7 +17,7 @@ function Separator({
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
className,
)}
{...props}

View File

@@ -1,4 +1,4 @@
"use client";
'use client';
import type {
ComponentProps,
@@ -7,8 +7,8 @@ import type {
ReactNode,
RefObject,
SyntheticEvent,
} from "react";
import type { PercentCrop, PixelCrop, ReactCropProps } from "react-image-crop";
} from 'react';
import type { PercentCrop, PixelCrop, ReactCropProps } from 'react-image-crop';
import {
createContext,
useCallback,
@@ -16,14 +16,14 @@ import {
useEffect,
useRef,
useState,
} from "react";
import { Button } from "@/components/ui";
import { cn } from "@/lib/utils";
import { CropIcon, RotateCcwIcon } from "lucide-react";
import { Slot } from "radix-ui";
import ReactCrop, { centerCrop, makeAspectCrop } from "react-image-crop";
} from 'react';
import { CropIcon, RotateCcwIcon } from 'lucide-react';
import { Slot } from 'radix-ui';
import ReactCrop, { centerCrop, makeAspectCrop } from 'react-image-crop';
import "react-image-crop/dist/ReactCrop.css";
import { Button, cn } from '@gib/ui';
import 'react-image-crop/dist/ReactCrop.css';
const centerAspectCrop = (
mediaWidth: number,
@@ -34,14 +34,14 @@ const centerAspectCrop = (
aspect
? makeAspectCrop(
{
unit: "%",
unit: '%',
width: 90,
},
aspect,
mediaWidth,
mediaHeight,
)
: { x: 0, y: 0, width: 90, height: 90, unit: "%" },
: { x: 0, y: 0, width: 90, height: 90, unit: '%' },
mediaWidth,
mediaHeight,
);
@@ -52,11 +52,11 @@ const getCroppedPngImage = async (
pixelCrop: PixelCrop,
maxImageSize: number,
): Promise<string> => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error("Context is null, this should never happen.");
throw new Error('Context is null, this should never happen.');
}
const scaleX = imageSrc.naturalWidth / imageSrc.width;
@@ -78,7 +78,7 @@ const getCroppedPngImage = async (
canvas.height,
);
const croppedImageUrl = canvas.toDataURL("image/png");
const croppedImageUrl = canvas.toDataURL('image/png');
const response = await fetch(croppedImageUrl);
const blob = await response.blob();
@@ -102,7 +102,7 @@ type ImageCropContextType = {
completedCrop: PixelCrop | null;
imgRef: RefObject<HTMLImageElement | null>;
onCrop?: (croppedImage: string) => void;
reactCropProps: Omit<ReactCropProps, "onChange" | "onComplete" | "children">;
reactCropProps: Omit<ReactCropProps, 'onChange' | 'onComplete' | 'children'>;
handleChange: (pixelCrop: PixelCrop, percentCrop: PercentCrop) => void;
handleComplete: (
pixelCrop: PixelCrop,
@@ -118,7 +118,7 @@ const ImageCropContext = createContext<ImageCropContextType | null>(null);
const useImageCrop = () => {
const context = useContext(ImageCropContext);
if (!context) {
throw new Error("ImageCrop components must be used within ImageCrop");
throw new Error('ImageCrop components must be used within ImageCrop');
}
return context;
};
@@ -128,9 +128,9 @@ export type ImageCropProps = {
maxImageSize?: number;
onCrop?: (croppedImage: string) => void;
children: ReactNode;
onChange?: ReactCropProps["onChange"];
onComplete?: ReactCropProps["onComplete"];
} & Omit<ReactCropProps, "onChange" | "onComplete" | "children">;
onChange?: ReactCropProps['onChange'];
onComplete?: ReactCropProps['onComplete'];
} & Omit<ReactCropProps, 'onChange' | 'onComplete' | 'children'>;
export const ImageCrop = ({
file,
@@ -142,15 +142,15 @@ export const ImageCrop = ({
...reactCropProps
}: ImageCropProps) => {
const imgRef = useRef<HTMLImageElement | null>(null);
const [imgSrc, setImgSrc] = useState<string>("");
const [imgSrc, setImgSrc] = useState<string>('');
const [crop, setCrop] = useState<PercentCrop>();
const [completedCrop, setCompletedCrop] = useState<PixelCrop | null>(null);
const [initialCrop, setInitialCrop] = useState<PercentCrop>();
useEffect(() => {
const reader = new FileReader();
reader.addEventListener("load", () =>
setImgSrc(reader.result?.toString() || ""),
reader.addEventListener('load', () =>
setImgSrc(reader.result?.toString() || ''),
);
reader.readAsDataURL(file);
}, [file]);
@@ -244,13 +244,13 @@ export const ImageCropContent = ({
} = useImageCrop();
const shadcnStyle = {
"--rc-border-color": "var(--color-border)",
"--rc-focus-color": "var(--color-primary)",
'--rc-border-color': 'var(--color-border)',
'--rc-focus-color': 'var(--color-primary)',
} as CSSProperties;
return (
<ReactCrop
className={cn("max-h-[277px] max-w-full", className)}
className={cn('max-h-[277px] max-w-full', className)}
crop={crop}
onChange={handleChange}
onComplete={handleComplete}
@@ -270,7 +270,7 @@ export const ImageCropContent = ({
);
};
export type ImageCropApplyProps = ComponentProps<"button"> & {
export type ImageCropApplyProps = ComponentProps<'button'> & {
asChild?: boolean;
};
@@ -302,7 +302,7 @@ export const ImageCropApply = ({
);
};
export type ImageCropResetProps = ComponentProps<"button"> & {
export type ImageCropResetProps = ComponentProps<'button'> & {
asChild?: boolean;
};
@@ -335,11 +335,11 @@ export const ImageCropReset = ({
};
// Keep the original Cropper component for backward compatibility
export type CropperProps = Omit<ReactCropProps, "onChange"> & {
export type CropperProps = Omit<ReactCropProps, 'onChange'> & {
file: File;
maxImageSize?: number;
onCrop?: (croppedImage: string) => void;
onChange?: ReactCropProps["onChange"];
onChange?: ReactCropProps['onChange'];
};
export const Cropper = ({

View File

@@ -1,20 +1,20 @@
"use client";
'use client';
import { useTheme } from "next-themes";
import { Toaster as Sonner, ToasterProps } from "sonner";
import { useTheme } from 'next-themes';
import { Toaster as Sonner, ToasterProps } from 'sonner';
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
const { theme = 'system' } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
theme={theme as ToasterProps['theme']}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
'--normal-bg': 'var(--popover)',
'--normal-text': 'var(--popover-foreground)',
'--normal-border': 'var(--border)',
} as React.CSSProperties
}
{...props}

View File

@@ -1,12 +1,13 @@
import { type ComponentProps } from "react";
import { cn } from "@/lib/utils";
import { type ComponentProps } from 'react';
import { cn } from '@gib/ui';
type Message = { success: string } | { error: string } | { message: string };
type StatusMessageProps = {
message: Message;
containerProps?: ComponentProps<"div">;
textProps?: ComponentProps<"div">;
containerProps?: ComponentProps<'div'>;
textProps?: ComponentProps<'div'>;
};
export const StatusMessage = ({
@@ -16,37 +17,37 @@ export const StatusMessage = ({
}: StatusMessageProps) => {
return (
<div className="flex w-full flex-col items-center">
{"success" in message && (
{'success' in message && (
<div
{...containerProps}
className={cn(
"flex w-11/12 flex-col items-center rounded-md p-2",
"border-2 bg-green-700/20 dark:bg-green-500/20",
"border-green-700/50 dark:border-green-500/50",
'flex w-11/12 flex-col items-center rounded-md p-2',
'border-2 bg-green-700/20 dark:bg-green-500/20',
'border-green-700/50 dark:border-green-500/50',
containerProps?.className,
)}
>
<p {...textProps}>{message.success}</p>
</div>
)}
{"error" in message && (
{'error' in message && (
<div
{...containerProps}
className={cn(
"flex w-11/12 flex-col items-center rounded-md p-2",
"bg-destructive/20 border-destructive/80 border-2",
'flex w-11/12 flex-col items-center rounded-md p-2',
'bg-destructive/20 border-destructive/80 border-2',
containerProps?.className,
)}
>
<p {...textProps}>{message.error}</p>
</div>
)}
{"message" in message && (
{'message' in message && (
<div
{...containerProps}
className={cn(
"flex w-11/12 flex-col items-center rounded-md p-2",
"bg-accent/20 border-primary/80 border-2",
'flex w-11/12 flex-col items-center rounded-md p-2',
'bg-accent/20 border-primary/80 border-2',
containerProps?.className,
)}
>

View File

@@ -1,24 +1,24 @@
"use client";
'use client';
import { type ComponentProps } from "react";
import { Button } from "@/components/ui";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
import { useFormStatus } from "react-dom";
import { type ComponentProps } from 'react';
import { Loader2 } from 'lucide-react';
import { useFormStatus } from 'react-dom';
import { Button, cn } from '@gib/ui';
export type SubmitButtonProps = Omit<
ComponentProps<typeof Button>,
"type" | "aria-disabled"
'type' | 'aria-disabled'
> & {
pendingText?: string;
pendingTextProps?: ComponentProps<"p">;
pendingTextProps?: ComponentProps<'p'>;
loaderProps?: ComponentProps<typeof Loader2>;
};
export const SubmitButton = ({
children,
className,
pendingText = "Submitting...",
pendingText = 'Submitting...',
pendingTextProps,
loaderProps,
...props
@@ -29,17 +29,17 @@ export const SubmitButton = ({
type="submit"
aria-disabled={pending}
{...props}
className={cn("cursor-pointer", className)}
className={cn('cursor-pointer', className)}
>
{pending || props.disabled ? (
<>
<Loader2
{...loaderProps}
className={cn("mr-2 h-4 w-4 animate-spin", loaderProps?.className)}
className={cn('mr-2 h-4 w-4 animate-spin', loaderProps?.className)}
/>
<p
{...pendingTextProps}
className={cn("text-sm font-medium", pendingTextProps?.className)}
className={cn('text-sm font-medium', pendingTextProps?.className)}
>
{pendingText}
</p>

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import * as React from 'react';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { cn } from '@gib/ui';
function Switch({
className,
@@ -12,7 +13,7 @@ function Switch({
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
@@ -20,7 +21,7 @@ function Switch({
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitive.Root>

View File

@@ -1,9 +1,10 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as React from 'react';
function Table({ className, ...props }: React.ComponentProps<"table">) {
import { cn } from '@gib/ui';
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div
data-slot="table-container"
@@ -11,39 +12,39 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
>
<table
data-slot="table"
className={cn("w-full caption-bottom text-sm", className)}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
className={cn('[&_tr]:border-b', className)}
{...props}
/>
);
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
);
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return (
<tfoot
data-slot="table-footer"
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
@@ -51,12 +52,12 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
);
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
<tr
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className,
)}
{...props}
@@ -64,12 +65,12 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
);
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
@@ -77,12 +78,12 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
);
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
@@ -93,11 +94,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
}: React.ComponentProps<'caption'>) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props}
/>
);

View File

@@ -1,8 +1,9 @@
"use client";
'use client';
import * as React from "react";
import { cn } from "@/lib/utils";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@gib/ui';
function Tabs({
className,
@@ -11,7 +12,7 @@ function Tabs({
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
className={cn('flex flex-col gap-2', className)}
{...props}
/>
);
@@ -25,7 +26,7 @@ function TabsList({
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
@@ -56,7 +57,7 @@ function TabsContent({
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
className={cn('flex-1 outline-none', className)}
{...props}
/>
);

View File

@@ -1,184 +1,70 @@
"use client";
'use client';
import * as React from "react";
import { DesktopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
import * as z from "zod/v4";
import type { ComponentProps } from 'react';
import { useEffect, useState } from 'react';
import { Moon, Sun } from 'lucide-react';
import { ThemeProvider as NextThemesProvider, useTheme } from 'next-themes';
import { Button } from "./button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "./dropdown-menu";
import { Button, cn } from '@gib/ui';
const ThemeModeSchema = z.enum(["light", "dark", "auto"]);
const ThemeProvider = ({
children,
...props
}: ComponentProps<typeof NextThemesProvider>) => {
const [mounted, setMounted] = useState(false);
const themeKey = "theme-mode";
useEffect(() => {
setMounted(true);
}, []);
export type ThemeMode = z.output<typeof ThemeModeSchema>;
export type ResolvedTheme = Exclude<ThemeMode, "auto">;
if (!mounted) return null;
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};
const getStoredThemeMode = (): ThemeMode => {
if (typeof window === "undefined") return "auto";
try {
const storedTheme = localStorage.getItem(themeKey);
return ThemeModeSchema.parse(storedTheme);
} catch {
return "auto";
type ThemeToggleProps = {
size?: number;
buttonProps?: Omit<ComponentProps<typeof Button>, 'onClick'>;
};
const ThemeToggle = ({ size = 1, buttonProps }: ThemeToggleProps) => {
const { setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<Button {...buttonProps}>
<span style={{ height: `${size}rem`, width: `${size}rem` }} />
</Button>
);
}
};
const setStoredThemeMode = (theme: ThemeMode) => {
try {
const parsedTheme = ThemeModeSchema.parse(theme);
localStorage.setItem(themeKey, parsedTheme);
} catch {
// Silently fail if localStorage is unavailable
}
};
const getSystemTheme = () => {
if (typeof window === "undefined") return "light";
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
const updateThemeClass = (themeMode: ThemeMode) => {
const root = document.documentElement;
root.classList.remove("light", "dark", "auto");
const newTheme = themeMode === "auto" ? getSystemTheme() : themeMode;
root.classList.add(newTheme);
if (themeMode === "auto") {
root.classList.add("auto");
}
};
const setupPreferredListener = () => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = () => updateThemeClass("auto");
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
};
const getNextTheme = (current: ThemeMode): ThemeMode => {
const themes: ThemeMode[] =
getSystemTheme() === "dark"
? ["auto", "light", "dark"]
: ["auto", "dark", "light"];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return themes[(themes.indexOf(current) + 1) % themes.length]!;
};
export const themeDetectorScript = (function () {
function themeFn() {
const isValidTheme = (theme: string): theme is ThemeMode => {
const validThemes = ["light", "dark", "auto"] as const;
return validThemes.includes(theme as ThemeMode);
};
const storedTheme = localStorage.getItem("theme-mode") ?? "auto";
const validTheme = isValidTheme(storedTheme) ? storedTheme : "auto";
if (validTheme === "auto") {
const autoTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
document.documentElement.classList.add(autoTheme, "auto");
} else {
document.documentElement.classList.add(validTheme);
}
}
return `(${themeFn.toString()})();`;
})();
interface ThemeContextProps {
themeMode: ThemeMode;
resolvedTheme: ResolvedTheme;
setTheme: (theme: ThemeMode) => void;
toggleMode: () => void;
}
const ThemeContext = React.createContext<ThemeContextProps | undefined>(
undefined,
);
export function ThemeProvider({ children }: React.PropsWithChildren) {
const [themeMode, setThemeMode] = React.useState(getStoredThemeMode);
React.useEffect(() => {
if (themeMode !== "auto") return;
return setupPreferredListener();
}, [themeMode]);
const resolvedTheme = themeMode === "auto" ? getSystemTheme() : themeMode;
const setTheme = (newTheme: ThemeMode) => {
setThemeMode(newTheme);
setStoredThemeMode(newTheme);
updateThemeClass(newTheme);
};
const toggleMode = () => {
setTheme(getNextTheme(themeMode));
const toggleTheme = () => {
if (resolvedTheme === 'dark') setTheme('light');
else setTheme('dark');
};
return (
<ThemeContext
value={{
themeMode,
resolvedTheme,
setTheme,
toggleMode,
}}
<Button
variant="outline"
size="icon"
{...buttonProps}
onClick={toggleTheme}
className={cn('cursor-pointer', buttonProps?.className)}
>
<script
dangerouslySetInnerHTML={{ __html: themeDetectorScript }}
suppressHydrationWarning
<Sun
style={{ height: `${size}rem`, width: `${size}rem` }}
className="scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90"
/>
{children}
</ThemeContext>
<Moon
style={{ height: `${size}rem`, width: `${size}rem` }}
className="absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0"
/>
</Button>
);
}
};
export function useTheme() {
const context = React.use(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}
export function ThemeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="[&>svg]:absolute [&>svg]:size-5 [&>svg]:scale-0"
>
<SunIcon className="light:scale-100! auto:scale-0!" />
<MoonIcon className="auto:scale-0! dark:scale-100!" />
<DesktopIcon className="auto:scale-100!" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("auto")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export { ThemeProvider, ThemeToggle, type ThemeToggleProps };

View File

@@ -1,27 +0,0 @@
"use client";
import type { ToasterProps } from "sonner";
import { Toaster as Sonner, toast } from "sonner";
import { useTheme } from "./theme";
export const Toaster = ({ ...props }: ToasterProps) => {
const { themeMode } = useTheme();
return (
<Sonner
theme={themeMode === "auto" ? "system" : themeMode}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
{...props}
/>
);
};
export { toast };