diff --git a/components.json b/components.json index 7a83b770d..efd72b8d6 100644 --- a/components.json +++ b/components.json @@ -2,7 +2,7 @@ "$schema": "https://ui.shadcn.com/schema.json", "style": "default", "rsc": true, - "tsx": false, + "tsx": true, "tailwind": { "config": "tailwind.config.js", "css": "src/app/globals.css", diff --git a/package-lock.json b/package-lock.json index 0801c5594..ddb2f303a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@react-email/components": "^0.0.16", @@ -30,6 +31,7 @@ "firebase": "^10.8.0", "firebase-admin": "^11.11.0", "gray-matter": "^4.0.3", + "input-otp": "^1.2.4", "jszip": "^3.10.1", "lucide-react": "^0.408.0", "next": "^14.0.1", @@ -58,6 +60,7 @@ "@types/node": "^20.14.10", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.16", "cypress": "^13.12.0", "eslint": "^8.53.0", @@ -1666,6 +1669,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz", "integrity": "sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==", + "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", @@ -2004,6 +2008,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", + "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", @@ -2939,6 +2967,13 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", @@ -7627,6 +7662,16 @@ "fast-loops": "^1.1.3" } }, + "node_modules/input-otp": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz", + "integrity": "sha512-md6rhmD+zmMnUh5crQNSQxq3keBRYvE3odbr4Qb9g2NWzQv9azi+t1a3X4TBTbh98fsGHgEEJlzbe1q860uGCA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -8981,6 +9026,7 @@ "version": "0.408.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.408.0.tgz", "integrity": "sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==", + "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -11650,6 +11696,7 @@ "version": "2.12.7", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "license": "MIT", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", diff --git a/package.json b/package.json index 21bef0e64..379418099 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@react-email/components": "^0.0.16", @@ -36,6 +37,7 @@ "firebase": "^10.8.0", "firebase-admin": "^11.11.0", "gray-matter": "^4.0.3", + "input-otp": "^1.2.4", "jszip": "^3.10.1", "lucide-react": "^0.408.0", "next": "^14.0.1", @@ -64,6 +66,7 @@ "@types/node": "^20.14.10", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.16", "cypress": "^13.12.0", "eslint": "^8.53.0", diff --git a/src/app/admin/[type]/page.js b/src/app/admin/[type]/page.js index 888e2c1e4..14256428c 100644 --- a/src/app/admin/[type]/page.js +++ b/src/app/admin/[type]/page.js @@ -19,6 +19,7 @@ import Volunteers from "@/components/admin/dashboards/Volunteers"; import Leads from "@/components/admin/dashboards/Leads"; import Fault from "@/utils/error"; import Settings from "@/components/admin/services/settings/Settings"; +import Timer from "@/components/admin/services/timer/Timer"; const Page = ({ params, searchParams }) => { const components = { @@ -40,6 +41,7 @@ const Page = ({ params, searchParams }) => { teams: , volunteers: , leads: , + timer: , }; const capitalizeFirstLetter = (word) => { diff --git a/src/components/admin/services/timer/Clock.jsx b/src/components/admin/services/timer/Clock.jsx new file mode 100644 index 000000000..2b7c53ef9 --- /dev/null +++ b/src/components/admin/services/timer/Clock.jsx @@ -0,0 +1,111 @@ +import { useState, useEffect } from "react"; +import Controls from "./Controls"; +import { Progress } from "@/components/ui/progress"; +import { Pause, Play } from "lucide-react"; +import { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from "@/components/ui/input-otp"; + +const Timer = ({ onRemove }) => { + const [edit, setEdit] = useState(false); + const [play, setPlay] = useState(false); + const [total, setTotal] = useState(60000); + const [original, setOriginal] = useState(0); + const [time, setTime] = useState({ + hours: "00", + minutes: "01", + seconds: "00", + }); + + const onChange = (value) => { + if (!edit) return; + + const [hours, minutes, seconds] = value.match(/.{2}/g); + + setTime({ + hours, + minutes, + seconds, + }); + + setTotal( + parseInt(hours) * (1000 * 60 * 60) + + parseInt(minutes) * (1000 * 60) + + parseInt(seconds) * 1000, + ); + }; + + useEffect(() => { + const id = setInterval(() => { + if (!play) return; + + const newSeconds = total - 1000; + + setTotal((prev) => prev - 1000); + + setTime({ + hours: Math.floor((newSeconds / (1000 * 60 * 60)) % 24) + .toString() + .padStart(2, "0"), + minutes: Math.floor((newSeconds / 1000 / 60) % 60) + .toString() + .padStart(2, "0"), + seconds: Math.floor((newSeconds / 1000) % 60) + .toString() + .padStart(2, "0"), + }); + + setOriginal((prev) => prev + 1000); + }, 1000); + + return () => clearInterval(id); + }, [play, total]); + + return ( +
+
+ + +
+ + + + + + + + + + + + + + + + +
+ {play && !edit && setPlay(false)} />} + {!play && !edit && setPlay(true)} />} +
+ + +
+ ); +}; + +export default Timer; diff --git a/src/components/admin/services/timer/Controls.tsx b/src/components/admin/services/timer/Controls.tsx new file mode 100644 index 000000000..289808deb --- /dev/null +++ b/src/components/admin/services/timer/Controls.tsx @@ -0,0 +1,28 @@ +import { Trash2, Pen, Check } from "lucide-react"; + +type props = { + edit: boolean; + setEdit: (value: boolean) => void; + onRemove: () => void; +}; + +const Controls = ({ edit, setEdit, onRemove }: props) => { + return ( +
+ {edit && ( + <> + setEdit(false)} /> + + + )} + + {!edit && ( + <> + setEdit(true)} /> + + + )} +
+ ); +}; +export default Controls; diff --git a/src/components/admin/services/timer/Timer.tsx b/src/components/admin/services/timer/Timer.tsx new file mode 100644 index 000000000..16106ef6c --- /dev/null +++ b/src/components/admin/services/timer/Timer.tsx @@ -0,0 +1,55 @@ +import { useState } from "react"; +import Clock from "./Clock"; +import { v4 as uuidv4 } from "uuid"; +import Title from "../../Title"; +import { Button } from "@/components/ui/button"; + +type TimerType = { + id: string; +}; + +const Timer = () => { + const [timers, setTimers] = useState([]); + + const addTimer = () => { + setTimers([ + ...timers, + { + id: uuidv4(), + }, + ]); + }; + + const clearAll = () => { + setTimers([]); + }; + + const deleteTimer = (id: string) => { + setTimers(timers.filter((timer) => timer.id !== id)); + }; + + return ( +
+
+ + <Button onClick={addTimer}>+ add timer</Button> + <Button variant="destructive" onClick={clearAll}> + clear all + </Button> + </div> + <div className="flex h-full flex-col overflow-y-scroll rounded-3xl bg-gray-200 p-4"> + {timers.length === 0 ? ( + <div className="flex h-full items-center justify-center text-2xl font-bold opacity-30"> + No Timers + </div> + ) : ( + timers.map((timer) => ( + <Clock key={timer.id} onRemove={() => deleteTimer(timer.id)} /> + )) + )} + </div> + </div> + ); +}; + +export default Timer; diff --git a/src/components/ui/accordion.jsx b/src/components/ui/accordion.jsx deleted file mode 100644 index f48dc5eaa..000000000 --- a/src/components/ui/accordion.jsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as AccordionPrimitive from "@radix-ui/react-accordion"; -import { ChevronDown } from "lucide-react"; - -import { cn } from "@/utils/tailwind"; - -const Accordion = AccordionPrimitive.Root; - -const AccordionItem = React.forwardRef(({ className, ...props }, ref) => ( - <AccordionPrimitive.Item - ref={ref} - className={cn("border-b", className)} - {...props} - /> -)); -AccordionItem.displayName = "AccordionItem"; - -const AccordionTrigger = React.forwardRef( - ({ className, children, ...props }, ref) => ( - <AccordionPrimitive.Header className="flex"> - <AccordionPrimitive.Trigger - ref={ref} - className={cn( - "flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>svg]:rotate-180", - className, - )} - {...props} - > - {children} - <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> - </AccordionPrimitive.Trigger> - </AccordionPrimitive.Header> - ), -); -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; - -const AccordionContent = React.forwardRef( - ({ className, children, ...props }, ref) => ( - <AccordionPrimitive.Content - ref={ref} - className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" - {...props} - > - <div className={cn("pb-4 pt-0", className)}>{children}</div> - </AccordionPrimitive.Content> - ), -); - -AccordionContent.displayName = AccordionPrimitive.Content.displayName; - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 000000000..ff879aa31 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client"; + +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/utils/tailwind"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Item>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> +>(({ className, ...props }, ref) => ( + <AccordionPrimitive.Item + ref={ref} + className={cn("border-b", className)} + {...props} + /> +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> +>(({ className, children, ...props }, ref) => ( + <AccordionPrimitive.Header className="flex"> + <AccordionPrimitive.Trigger + ref={ref} + className={cn( + "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", + className, + )} + {...props} + > + {children} + <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> + </AccordionPrimitive.Trigger> + </AccordionPrimitive.Header> +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef<typeof AccordionPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> +>(({ className, children, ...props }, ref) => ( + <AccordionPrimitive.Content + ref={ref} + className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" + {...props} + > + <div className={cn("pb-4 pt-0", className)}>{children}</div> + </AccordionPrimitive.Content> +)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/components/ui/alert-dialog.jsx b/src/components/ui/alert-dialog.tsx similarity index 64% rename from src/components/ui/alert-dialog.jsx rename to src/components/ui/alert-dialog.tsx index cf05f8b33..801f12a5a 100644 --- a/src/components/ui/alert-dialog.jsx +++ b/src/components/ui/alert-dialog.tsx @@ -12,7 +12,10 @@ const AlertDialogTrigger = AlertDialogPrimitive.Trigger; const AlertDialogPortal = AlertDialogPrimitive.Portal; -const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( +const AlertDialogOverlay = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Overlay>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> +>(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Overlay 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", @@ -24,7 +27,10 @@ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( )); AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; -const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => ( +const AlertDialogContent = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> +>(({ className, ...props }, ref) => ( <AlertDialogPortal> <AlertDialogOverlay /> <AlertDialogPrimitive.Content @@ -39,7 +45,10 @@ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => ( )); AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; -const AlertDialogHeader = ({ className, ...props }) => ( +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-2 text-center sm:text-left", @@ -50,7 +59,10 @@ const AlertDialogHeader = ({ className, ...props }) => ( ); AlertDialogHeader.displayName = "AlertDialogHeader"; -const AlertDialogFooter = ({ className, ...props }) => ( +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", @@ -61,7 +73,10 @@ const AlertDialogFooter = ({ className, ...props }) => ( ); AlertDialogFooter.displayName = "AlertDialogFooter"; -const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => ( +const AlertDialogTitle = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Title>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> +>(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} @@ -70,19 +85,23 @@ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => ( )); AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; -const AlertDialogDescription = React.forwardRef( - ({ className, ...props }, ref) => ( - <AlertDialogPrimitive.Description - ref={ref} - className={cn("text-sm text-slate-500 dark:text-slate-400", className)} - {...props} - /> - ), -); +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-slate-500 dark:text-slate-400", className)} + {...props} + /> +)); AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; -const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => ( +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)} @@ -91,7 +110,10 @@ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => ( )); AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; -const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => ( +const AlertDialogCancel = React.forwardRef< + React.ElementRef<typeof AlertDialogPrimitive.Cancel>, + React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> +>(({ className, ...props }, ref) => ( <AlertDialogPrimitive.Cancel ref={ref} className={cn( diff --git a/src/components/ui/button.jsx b/src/components/ui/button.tsx similarity index 87% rename from src/components/ui/button.jsx rename to src/components/ui/button.tsx index 171cd9435..9a51a210a 100644 --- a/src/components/ui/button.jsx +++ b/src/components/ui/button.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; -import { cva } from "class-variance-authority"; +import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/tailwind"; @@ -35,7 +35,13 @@ const buttonVariants = cva( }, ); -const Button = React.forwardRef( +export interface ButtonProps + extends React.ButtonHTMLAttributes<HTMLButtonElement>, + VariantProps<typeof buttonVariants> { + asChild?: boolean; +} + +const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return ( diff --git a/src/components/ui/card.jsx b/src/components/ui/card.tsx similarity index 59% rename from src/components/ui/card.jsx rename to src/components/ui/card.tsx index 69e47bafa..725daa4e9 100644 --- a/src/components/ui/card.jsx +++ b/src/components/ui/card.tsx @@ -2,7 +2,10 @@ import * as React from "react"; import { cn } from "@/utils/tailwind"; -const Card = React.forwardRef(({ className, ...props }, ref) => ( +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => ( <div ref={ref} className={cn( @@ -14,7 +17,10 @@ const Card = React.forwardRef(({ className, ...props }, ref) => ( )); Card.displayName = "Card"; -const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => ( <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} @@ -23,7 +29,10 @@ const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( )); CardHeader.displayName = "CardHeader"; -const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLHeadingElement> +>(({ className, ...props }, ref) => ( <h3 ref={ref} className={cn( @@ -35,7 +44,10 @@ const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( )); CardTitle.displayName = "CardTitle"; -const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => ( <p ref={ref} className={cn("text-sm text-slate-500 dark:text-slate-400", className)} @@ -44,12 +56,18 @@ const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( )); CardDescription.displayName = "CardDescription"; -const CardContent = React.forwardRef(({ className, ...props }, ref) => ( +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => ( <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> )); CardContent.displayName = "CardContent"; -const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> +>(({ className, ...props }, ref) => ( <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} diff --git a/src/components/ui/chart.jsx b/src/components/ui/chart.tsx similarity index 62% rename from src/components/ui/chart.jsx rename to src/components/ui/chart.tsx index 979ecc2c7..3b98278ec 100644 --- a/src/components/ui/chart.jsx +++ b/src/components/ui/chart.tsx @@ -1,16 +1,29 @@ "use client"; + import * as React from "react"; import * as RechartsPrimitive from "recharts"; import { cn } from "@/utils/tailwind"; // Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { - light: "", - dark: ".dark", +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + // eslint-disable-next-line no-unused-vars + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record<keyof typeof THEMES, string> } + ); +}; + +type ChartContextProps = { + config: ChartConfig; }; -const ChartContext = React.createContext(null); +const ChartContext = React.createContext<ChartContextProps | null>(null); function useChart() { const context = React.useContext(ChartContext); @@ -22,34 +35,40 @@ function useChart() { return context; } -const ChartContainer = React.forwardRef( - ({ id, className, children, config, ...props }, ref) => { - const uniqueId = React.useId(); - const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; - return ( - <ChartContext.Provider value={{ config }}> - <div - data-chart={chartId} - ref={ref} - className={cn( - "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line-line]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", - className, - )} - {...props} - > - <ChartStyle id={chartId} config={config} /> - <RechartsPrimitive.ResponsiveContainer> - {children} - </RechartsPrimitive.ResponsiveContainer> - </div> - </ChartContext.Provider> - ); - }, -); + return ( + <ChartContext.Provider value={{ config }}> + <div + data-chart={chartId} + ref={ref} + className={cn( + "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", + className ?? "", + )} + {...props} + > + <ChartStyle id={chartId} config={config} /> + <RechartsPrimitive.ResponsiveContainer> + {children} + </RechartsPrimitive.ResponsiveContainer> + </div> + </ChartContext.Provider> + ); +}); ChartContainer.displayName = "Chart"; -const ChartStyle = ({ id, config }) => { +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( ([_, config]) => config.theme || config.color, ); @@ -67,7 +86,9 @@ const ChartStyle = ({ id, config }) => { ${prefix} [data-chart=${id}] { ${colorConfig .map(([key, itemConfig]) => { - const color = itemConfig.theme?.[theme] || itemConfig.color; + const color = + itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || + itemConfig.color; return color ? ` --color-${key}: ${color};` : null; }) .join("\n")} @@ -82,7 +103,17 @@ ${colorConfig const ChartTooltip = RechartsPrimitive.Tooltip; -const ChartTooltipContent = React.forwardRef( +const ChartTooltipContent = React.forwardRef< + HTMLDivElement, + React.ComponentProps<typeof RechartsPrimitive.Tooltip> & + React.ComponentProps<"div"> & { + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; + } +>( ( { active, @@ -113,12 +144,12 @@ const ChartTooltipContent = React.forwardRef( const itemConfig = getPayloadConfigFromPayload(config, item, key); const value = !labelKey && typeof label === "string" - ? config[label]?.label || label + ? config[label as keyof typeof config]?.label || label : itemConfig?.label; if (labelFormatter) { return ( - <div className={cn("font-medium", labelClassName)}> + <div className={cn("font-medium", labelClassName ?? "")}> {labelFormatter(value, payload)} </div> ); @@ -128,7 +159,9 @@ const ChartTooltipContent = React.forwardRef( return null; } - return <div className={cn("font-medium", labelClassName)}>{value}</div>; + return ( + <div className={cn("font-medium", labelClassName ?? "")}>{value}</div> + ); }, [ label, labelFormatter, @@ -150,7 +183,7 @@ const ChartTooltipContent = React.forwardRef( ref={ref} className={cn( "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-slate-200 border-slate-200/50 bg-white px-2.5 py-1.5 text-xs shadow-xl dark:border-slate-800 dark:border-slate-800/50 dark:bg-slate-950", - className, + className ?? "", )} > {!nestLabel ? tooltipLabel : null} @@ -164,8 +197,8 @@ const ChartTooltipContent = React.forwardRef( <div key={item.dataKey} className={cn( - "flex w-full items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-slate-500 dark:[&>svg]:text-slate-400", - indicator === "dot" && "items-center", + "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-slate-500 dark:[&>svg]:text-slate-400", + indicator === "dot" ? "items-center" : "", )} > {formatter && item?.value !== undefined && item.name ? ( @@ -178,7 +211,7 @@ const ChartTooltipContent = React.forwardRef( !hideIndicator && ( <div className={cn( - `shrink-0 rounded-[2px] border-[--color-border] ${indicatorColor}`, + "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]", { "h-2.5 w-2.5": indicator === "dot", "w-1": indicator === "line", @@ -187,10 +220,12 @@ const ChartTooltipContent = React.forwardRef( "my-0.5": nestLabel && indicator === "dashed", }, )} - style={{ - "--color-bg": indicatorColor, - "--color-border": indicatorColor, - }} + style={ + { + "--color-bg": indicatorColor, + "--color-border": indicatorColor, + } as React.CSSProperties + } /> ) )} @@ -226,7 +261,14 @@ ChartTooltipContent.displayName = "ChartTooltip"; const ChartLegend = RechartsPrimitive.Legend; -const ChartLegendContent = React.forwardRef( +const ChartLegendContent = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & + Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & { + hideIcon?: boolean; + nameKey?: string; + } +>( ( { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref, @@ -243,7 +285,7 @@ const ChartLegendContent = React.forwardRef( className={cn( "flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", - className, + className ?? "", )} > {payload.map((item) => { @@ -278,7 +320,11 @@ const ChartLegendContent = React.forwardRef( ChartLegendContent.displayName = "ChartLegend"; // Helper to extract item config from a payload. -function getPayloadConfigFromPayload(config, payload, key) { +function getPayloadConfigFromPayload( + config: ChartConfig, + payload: unknown, + key: string, +) { if (typeof payload !== "object" || payload === null) { return undefined; } @@ -290,19 +336,26 @@ function getPayloadConfigFromPayload(config, payload, key) { ? payload.payload : undefined; - let configLabelKey = key; + let configLabelKey: string = key; - if (key in payload && typeof payload[key] === "string") { - configLabelKey = payload[key]; + if ( + key in payload && + typeof payload[key as keyof typeof payload] === "string" + ) { + configLabelKey = payload[key as keyof typeof payload] as string; } else if ( payloadPayload && key in payloadPayload && - typeof payloadPayload[key] === "string" + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" ) { - configLabelKey = payloadPayload[key]; + configLabelKey = payloadPayload[ + key as keyof typeof payloadPayload + ] as string; } - return configLabelKey in config ? config[configLabelKey] : config[key]; + return configLabelKey in config + ? config[configLabelKey] + : config[key as keyof typeof config]; } export { diff --git a/src/components/ui/checkbox.jsx b/src/components/ui/checkbox.jsx deleted file mode 100644 index e2be3c27a..000000000 --- a/src/components/ui/checkbox.jsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; -import * as React from "react"; -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; -import { Check } from "lucide-react"; - -import { cn } from "@/utils/tailwind"; - -const Checkbox = React.forwardRef(({ className, ...props }, ref) => ( - <CheckboxPrimitive.Root - ref={ref} - className={cn( - "peer h-4 w-4 shrink-0 rounded-sm disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:text-white", - className, - )} - {...props} - > - <CheckboxPrimitive.Indicator - className={cn("flex items-center justify-center text-current")} - > - <Check className="h-4 w-4" /> - </CheckboxPrimitive.Indicator> - </CheckboxPrimitive.Root> -)); -Checkbox.displayName = CheckboxPrimitive.Root.displayName; - -export { Checkbox }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 000000000..2c029e3c5 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Check } from "lucide-react"; + +import { cn } from "@/utils/tailwind"; + +const Checkbox = React.forwardRef< + React.ElementRef<typeof CheckboxPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> +>(({ className, ...props }, ref) => ( + <CheckboxPrimitive.Root + ref={ref} + className={cn( + "peer h-4 w-4 shrink-0 rounded-sm border border-slate-200 border-slate-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-slate-900 data-[state=checked]:text-slate-50 dark:border-slate-50 dark:border-slate-800 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 dark:data-[state=checked]:bg-slate-50 dark:data-[state=checked]:text-slate-900", + className, + )} + {...props} + > + <CheckboxPrimitive.Indicator + className={cn("flex items-center justify-center text-current")} + > + <Check className="h-4 w-4" /> + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx new file mode 100644 index 000000000..7d98a42b0 --- /dev/null +++ b/src/components/ui/input-otp.tsx @@ -0,0 +1,72 @@ +"use client"; + +import * as React from "react"; +import { OTPInput, OTPInputContext } from "input-otp"; +import { Dot } from "lucide-react"; + +import { cn } from "@/utils/tailwind"; + +const InputOTP = React.forwardRef< + React.ElementRef<typeof OTPInput>, + React.ComponentPropsWithoutRef<typeof OTPInput> +>(({ className, containerClassName, ...props }, ref) => ( + <OTPInput + ref={ref} + containerClassName={cn( + "flex items-center gap-2 has-[:disabled]:opacity-50", + containerClassName, + )} + className={cn("disabled:cursor-not-allowed", className)} + {...props} + /> +)); +InputOTP.displayName = "InputOTP"; + +const InputOTPGroup = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => ( + <div ref={ref} className={cn("flex items-center", className)} {...props} /> +)); +InputOTPGroup.displayName = "InputOTPGroup"; + +const InputOTPSlot = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> & { index: number } +>(({ index, className, ...props }, ref) => { + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + + return ( + <div + ref={ref} + className={cn( + "relative flex h-10 w-10 items-center justify-center border-y border-r border-slate-200 text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md dark:border-slate-800", + isActive && + "z-10 ring-2 ring-slate-950 ring-offset-white dark:ring-slate-300 dark:ring-offset-slate-950", + className, + )} + {...props} + > + {char} + {hasFakeCaret && ( + <div className="pointer-events-none absolute inset-0 flex items-center justify-center"> + <div className="h-4 w-px animate-caret-blink bg-slate-950 duration-1000 dark:bg-slate-50" /> + </div> + )} + </div> + ); +}); +InputOTPSlot.displayName = "InputOTPSlot"; + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( + <div ref={ref} role="separator" {...props}> + <Dot /> + </div> +)); +InputOTPSeparator.displayName = "InputOTPSeparator"; + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; diff --git a/src/components/ui/input.jsx b/src/components/ui/input.jsx deleted file mode 100644 index c93d82d34..000000000 --- a/src/components/ui/input.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/utils/tailwind"; - -const Input = React.forwardRef(({ className, type, ...props }, ref) => { - return ( - <input - type={type} - className={cn( - "flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300", - className, - )} - ref={ref} - {...props} - /> - ); -}); -Input.displayName = "Input"; - -export { Input }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 000000000..2f0d9f3b4 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/utils/tailwind"; + +export interface InputProps + extends React.InputHTMLAttributes<HTMLInputElement> {} + +const Input = React.forwardRef<HTMLInputElement, InputProps>( + ({ className, type, ...props }, ref) => { + return ( + <input + type={type} + className={cn( + "flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300", + className, + )} + ref={ref} + {...props} + /> + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/label.jsx b/src/components/ui/label.tsx similarity index 61% rename from src/components/ui/label.jsx rename to src/components/ui/label.tsx index e83a65957..47a250e12 100644 --- a/src/components/ui/label.jsx +++ b/src/components/ui/label.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as LabelPrimitive from "@radix-ui/react-label"; -import { cva } from "class-variance-authority"; +import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/tailwind"; @@ -10,7 +10,11 @@ const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", ); -const Label = React.forwardRef(({ className, ...props }, ref) => ( +const Label = React.forwardRef< + React.ElementRef<typeof LabelPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & + VariantProps<typeof labelVariants> +>(({ className, ...props }, ref) => ( <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 000000000..dab62712b --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as React from "react"; +import * as ProgressPrimitive from "@radix-ui/react-progress"; + +import { cn } from "@/utils/tailwind"; + +const Progress = React.forwardRef< + React.ElementRef<typeof ProgressPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> +>(({ className, value, ...props }, ref) => ( + <ProgressPrimitive.Root + ref={ref} + className={cn( + "relative h-4 w-full overflow-hidden rounded-full bg-slate-100 dark:bg-slate-800", + className, + )} + {...props} + > + <ProgressPrimitive.Indicator + className="h-full w-full flex-1 bg-slate-900 transition-all dark:bg-slate-50" + style={{ transform: `translateX(-${100 - (value || 0)}%)` }} + /> + </ProgressPrimitive.Root> +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/src/components/ui/tabs.jsx b/src/components/ui/tabs.tsx similarity index 73% rename from src/components/ui/tabs.jsx rename to src/components/ui/tabs.tsx index 28a5a5680..c17cb8c5b 100644 --- a/src/components/ui/tabs.jsx +++ b/src/components/ui/tabs.tsx @@ -7,7 +7,10 @@ import { cn } from "@/utils/tailwind"; const Tabs = TabsPrimitive.Root; -const TabsList = React.forwardRef(({ className, ...props }, ref) => ( +const TabsList = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.List>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> +>(({ className, ...props }, ref) => ( <TabsPrimitive.List ref={ref} className={cn( @@ -19,7 +22,10 @@ const TabsList = React.forwardRef(({ className, ...props }, ref) => ( )); TabsList.displayName = TabsPrimitive.List.displayName; -const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => ( +const TabsTrigger = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> +>(({ className, ...props }, ref) => ( <TabsPrimitive.Trigger ref={ref} className={cn( @@ -31,7 +37,10 @@ const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => ( )); TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; -const TabsContent = React.forwardRef(({ className, ...props }, ref) => ( +const TabsContent = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> +>(({ className, ...props }, ref) => ( <TabsPrimitive.Content ref={ref} className={cn( diff --git a/src/components/ui/textarea.jsx b/src/components/ui/textarea.jsx deleted file mode 100644 index bc6e9e0f3..000000000 --- a/src/components/ui/textarea.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from "react"; -import { cn } from "@/utils/tailwind"; - -const Textarea = React.forwardRef( - ({ className, title, required, ...props }, ref) => { - return ( - <div className="flex flex-col"> - <p className="mb-1 font-semibold"> - {title} - {required && <span className="text-red-500">*</span>} - </p> - <textarea - className={cn( - "flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300", - className, - )} - ref={ref} - {...props} - /> - </div> - ); - }, -); -Textarea.displayName = "Textarea"; - -export { Textarea }; diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 000000000..ae54d70f5 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "@/utils/tailwind"; + +export interface TextareaProps + extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} + +const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( + ({ className, ...props }, ref) => { + return ( + <textarea + className={cn( + "flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300", + className, + )} + ref={ref} + {...props} + /> + ); + }, +); +Textarea.displayName = "Textarea"; + +export { Textarea }; diff --git a/src/components/user/team/Details.jsx b/src/components/user/team/Details.jsx index 7f48be767..9044aabd4 100644 --- a/src/components/user/team/Details.jsx +++ b/src/components/user/team/Details.jsx @@ -40,8 +40,6 @@ const Details = ({ team }) => { }; const handleSave = async () => { - console.log(details); - if ( !( details.links.github === "" || diff --git a/src/data/Navigation.js b/src/data/Navigation.js index 44bebb414..028109b10 100644 --- a/src/data/Navigation.js +++ b/src/data/Navigation.js @@ -16,6 +16,7 @@ import { QrCode, CircleHelp, Presentation, + Timer, Newspaper, } from "lucide-react"; @@ -123,6 +124,11 @@ export const TABS = { link: "/admin/settings", icon: <Settings className={iconStyle} />, }, + { + name: "timer", + link: "/admin/timer", + icon: <Timer className={iconStyle} />, + }, ], }, }, diff --git a/src/data/admin/Judges.js b/src/data/admin/Judges.js index 2e1958e42..b94bb8ef7 100644 --- a/src/data/admin/Judges.js +++ b/src/data/admin/Judges.js @@ -60,8 +60,6 @@ export const COLUMNS = [ name, })); - console.log(photos); - const zip = new JSZip(); const folder = zip.folder(); diff --git a/src/utils/tailwind.ts b/src/utils/tailwind.ts index f98ee5e48..c3948e569 100644 --- a/src/utils/tailwind.ts +++ b/src/utils/tailwind.ts @@ -1,4 +1,4 @@ -import { clsx } from "clsx"; +import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -export const cn = (...inputs: string[]): string => twMerge(clsx(inputs)); +export const cn = (...inputs: ClassValue[]): string => twMerge(clsx(inputs)); diff --git a/tailwind.config.js b/tailwind.config.js index 4a4d6ade7..a4273d34e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -60,10 +60,15 @@ module.exports = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, + "caret-blink": { + "0%,70%,100%": { opacity: "1" }, + "20%,50%": { opacity: "0" }, + }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + "caret-blink": "caret-blink 1.25s ease-out infinite", }, }, },