+
diff --git a/src/components/layout/data/sidebar-data.ts b/src/components/layout/data/sidebar-data.ts
new file mode 100644
index 0000000..d534150
--- /dev/null
+++ b/src/components/layout/data/sidebar-data.ts
@@ -0,0 +1,102 @@
+import {
+ AudioWaveform,
+ Check,
+ Command,
+ GalleryVerticalEnd,
+ HelpCircle,
+ LayoutDashboard,
+ MessageCircle,
+ PackagePlus,
+ Settings,
+ Settings2,
+ User2,
+ Users,
+} from "lucide-react";
+
+import type { NavChildItem, SidebarData } from "@/types/types";
+
+export const profileSidebarItems: NavChildItem[] = [
+ {
+ title: "Profile",
+ icon: User2,
+ url: "/dashboard/settings",
+ },
+ {
+ title: "Account",
+ icon: Settings2,
+ url: "/dashboard/settings/account",
+ },
+];
+
+export const sidebarData: SidebarData = {
+ user: {
+ name: "satnaing",
+ email: "satnaingdev@gmail.com",
+ avatar: "/avatars/shadcn.jpg",
+ },
+ teams: [
+ {
+ name: "Shadcn Admin",
+ logo: Command,
+ plan: "Vite + ShadcnUI",
+ },
+ {
+ name: "Acme Inc",
+ logo: GalleryVerticalEnd,
+ plan: "Enterprise",
+ },
+ {
+ name: "Acme Corp.",
+ logo: AudioWaveform,
+ plan: "Startup",
+ },
+ ],
+ navGroups: [
+ {
+ title: "General",
+ items: [
+ {
+ title: "Dashboard",
+ url: "/dashboard",
+ icon: LayoutDashboard,
+ },
+ {
+ title: "Tasks",
+ url: "/tasks",
+ icon: Check,
+ },
+ {
+ title: "Apps",
+ url: "/apps",
+ icon: PackagePlus,
+ },
+ {
+ title: "Chats",
+ url: "/chats",
+ badge: "3",
+ icon: MessageCircle,
+ },
+ {
+ title: "Users",
+ url: "/users",
+ icon: Users,
+ },
+ ],
+ },
+ {
+ title: "Other",
+ items: [
+ {
+ title: "Settings",
+ icon: Settings,
+ items: profileSidebarItems,
+ },
+ {
+ title: "Help Center",
+ url: "/help-center",
+ icon: HelpCircle,
+ },
+ ],
+ },
+ ],
+};
diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx
new file mode 100644
index 0000000..b80e59c
--- /dev/null
+++ b/src/components/layout/header.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+import { Separator } from "@/components/ui/separator";
+import { SidebarTrigger } from "@/components/ui/sidebar";
+import { cn } from "@/lib/utils";
+
+interface HeaderProps extends React.HTMLAttributes
{
+ fixed?: boolean;
+ ref?: React.Ref;
+}
+
+export const Header = ({ className, fixed, children, ...props }: HeaderProps) => {
+ const [offset, setOffset] = useState(0);
+
+ useEffect(() => {
+ const onScroll = () => {
+ setOffset(document.body.scrollTop || document.documentElement.scrollTop);
+ };
+
+ // Add scroll listener to the body
+ document.addEventListener("scroll", onScroll, { passive: true });
+
+ // Clean up the event listener on unmount
+ return () => document.removeEventListener("scroll", onScroll);
+ }, []);
+
+ return (
+ 10 && fixed ? "shadow-sm" : "shadow-none",
+ className
+ )}
+ {...props}
+ >
+
+
+ {children}
+
+ );
+};
+
+Header.displayName = "Header";
diff --git a/src/components/layout/main.tsx b/src/components/layout/main.tsx
new file mode 100644
index 0000000..e23242a
--- /dev/null
+++ b/src/components/layout/main.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+import { cn } from "@/lib/utils";
+
+interface MainProps extends React.HTMLAttributes {
+ fixed?: boolean;
+ ref?: React.Ref;
+}
+
+export const Main = ({ fixed, className, ...props }: MainProps) => {
+ return (
+
+ );
+};
+
+Main.displayName = "Main";
diff --git a/src/components/layout/top-nav.tsx b/src/components/layout/top-nav.tsx
new file mode 100644
index 0000000..15f14d2
--- /dev/null
+++ b/src/components/layout/top-nav.tsx
@@ -0,0 +1,81 @@
+import Link from "next/link";
+
+import { Menu } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/utils";
+
+interface TopNavProps extends React.HTMLAttributes {
+ links: {
+ title: string;
+ href: string;
+ isActive: boolean;
+ disabled?: boolean;
+ }[];
+}
+
+export function TopNav({ className, links, ...props }: TopNavProps) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {links.map(({ title, href, isActive, disabled }) => (
+
+ {/*
+ {title}
+ */}
+
+ {title}
+
+
+ ))}
+
+
+
+
+
+ {links.map(({ title, href, isActive, disabled }) => (
+ //
+ // {title}
+ //
+
+ {title}
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/components/profile-dropdown.tsx b/src/components/profile-dropdown.tsx
new file mode 100644
index 0000000..f7e8f79
--- /dev/null
+++ b/src/components/profile-dropdown.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import Link from "next/link";
+
+import { AvatarImage } from "@radix-ui/react-avatar";
+
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import { useAppSelector } from "../redux/store";
+
+export function ProfileDropdown() {
+ const { profile } = useAppSelector((state) => state.auth);
+
+ return (
+
+
+
+
+
+
+ {profile?.profile?.name
+ ? profile.profile.name.slice(0, 2).toUpperCase()
+ : profile?.user?.username
+ ? profile.user.username.slice(0, 2).toUpperCase()
+ : "NA"}
+
+
+
+
+
+
+
+
{profile?.user.username}
+
{profile?.user.email}
+
+
+
+
+
+
+ Profile
+ â§âP
+
+
+
+
+ Billing
+ âB
+
+
+
+
+ Settings
+ âS
+
+
+ New Team
+
+
+
+ Log out
+ â§âQ
+
+
+
+ );
+}
diff --git a/src/components/profile/ProfileInfo.tsx b/src/components/profile/ProfileInfo.tsx
index 59483af..a5fa8a7 100644
--- a/src/components/profile/ProfileInfo.tsx
+++ b/src/components/profile/ProfileInfo.tsx
@@ -2,7 +2,7 @@
import { User } from "lucide-react";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle, Typography } from "@/components/ui";
import { Profile } from "@/types";
import { useAppSelector } from "../../redux/store";
@@ -64,14 +64,14 @@ export const ProfileInfo = ({ profile }: ProfileInfoProps) => {
return (
-
{section.label}
+
{section.label}
{section.fields.map(
(field) =>
field.value && (
-
{field.label}
-
{field.value}
+
{field.label}
+
{field.value}
)
)}
diff --git a/src/components/theme/ThemeToggle.tsx b/src/components/theme/ThemeToggle.tsx
index 19873c3..51ea53b 100644
--- a/src/components/theme/ThemeToggle.tsx
+++ b/src/components/theme/ThemeToggle.tsx
@@ -22,8 +22,8 @@ const ThemeToggle = ({ className }: ThemeToggleProps) => {
-
-
+
+
Toggle theme
diff --git a/src/components/ui/UsernameInput.tsx b/src/components/ui/UsernameInput.tsx
new file mode 100644
index 0000000..6e2c0f1
--- /dev/null
+++ b/src/components/ui/UsernameInput.tsx
@@ -0,0 +1,111 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+
+import useUserInput from "@/app/(auth)/hooks/useUserInput";
+
+interface UsernameInputProps {
+ readOnly?: boolean;
+ editMode?: boolean;
+ value?: string;
+ onChange?: (value: string) => void;
+ disabled?: boolean;
+ onAvailabilityCheckResult?: (isAvailable: boolean, data: any) => void;
+ placeholder?: string;
+ label?: string;
+}
+
+const UsernameInput = ({
+ readOnly = false,
+ editMode = false,
+ value,
+ onChange,
+ disabled = false,
+ onAvailabilityCheckResult,
+ placeholder = "Enter your username",
+ label = "Username",
+ ...props
+}: UsernameInputProps) => {
+ const [internalUsername, setInternalUsername] = useState("");
+ const [isUserNameAvailable, setIsUserNameAvailable] = useState(false);
+ const [hasChecked, setHasChecked] = useState(false);
+ const { mutate, isPending, data, error } = useUserInput();
+
+ // Use controlled value if provided, otherwise use internal state
+ const username = value !== undefined ? value : internalUsername;
+
+ const handleUsernameCheck = useCallback(() => {
+ if (username.trim() && !readOnly && !disabled) {
+ mutate({ username: username.trim() });
+ setHasChecked(true);
+ }
+ }, [mutate, username, readOnly, disabled]);
+
+ // Update availability state when data changes
+ useEffect(() => {
+ if (data && hasChecked) {
+ if (data.status === "success") {
+ const available = data.data.isAvailable;
+ setIsUserNameAvailable(available);
+
+ // Call the callback if provided
+ if (onAvailabilityCheckResult) {
+ onAvailabilityCheckResult(available, data);
+ }
+ }
+ }
+ }, [data, hasChecked, onAvailabilityCheckResult]);
+
+ const handleInputChange = (e: React.FormEvent) => {
+ const newValue = e.currentTarget.value;
+
+ if (!readOnly && editMode && !disabled) {
+ if (onChange) {
+ // Controlled component
+ onChange(newValue);
+ } else {
+ // Uncontrolled component
+ setInternalUsername(newValue);
+ }
+ setHasChecked(false); // Reset when user types
+ }
+ };
+
+ const isInputDisabled = isPending || readOnly || !editMode || disabled;
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {isPending && hasChecked && Checking availability... }
+ {error && {error.message} }
+ {hasChecked && isUserNameAvailable && !isPending && (
+ Username is available!
+ )}
+ {hasChecked && !isUserNameAvailable && !isPending && (
+ Username is not available
+ )}
+
+
+
+ );
+};
+
+export default UsernameInput;
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
index a6acff4..b85298a 100644
--- a/src/components/ui/accordion.tsx
+++ b/src/components/ui/accordion.tsx
@@ -3,52 +3,52 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
-import { ChevronDownIcon } from "@radix-ui/react-icons";
+import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
-const Accordion = AccordionPrimitive.Root;
-
-const AccordionItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, ...props }, ref) => (
-
-));
-AccordionItem.displayName = "AccordionItem";
-
-const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180",
- className
- )}
+function Accordion({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({ className, children, ...props }: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({ className, children, ...props }: React.ComponentProps) {
+ return (
+
- {children}
-
-
-
-));
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
-
-const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, children, ...props }, ref) => (
-
- {children}
-
-));
-AccordionContent.displayName = AccordionPrimitive.Content.displayName;
+ {children}
+
+ );
+}
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
index e857c69..c294218 100644
--- a/src/components/ui/avatar.tsx
+++ b/src/components/ui/avatar.tsx
@@ -6,42 +6,30 @@ import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils";
-const Avatar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-));
-Avatar.displayName = AvatarPrimitive.Root.displayName;
+function Avatar({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
-const AvatarImage = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-));
-AvatarImage.displayName = AvatarPrimitive.Image.displayName;
+function AvatarImage({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
-const AvatarFallback = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-));
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
+function AvatarFallback({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
-export { Avatar, AvatarFallback, AvatarImage };
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index 8026d37..0be8fd2 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -1,18 +1,20 @@
import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
- "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
- default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
- destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
- outline: "text-foreground",
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
@@ -21,10 +23,15 @@ const badgeVariants = cva(
}
);
-export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
-export const Badge = ({ className, variant, ...props }: BadgeProps) => {
- return
;
-};
+ return ;
+}
-export { badgeVariants };
+export { Badge, badgeVariants };
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 07c6dd0..545a4df 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -2,26 +2,29 @@ import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
+import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
{
variants: {
variant: {
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
- outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
- default: "h-9 px-4 py-2",
- sm: "h-8 rounded-md px-3 text-xs",
- lg: "h-10 rounded-md px-8",
- icon: "h-9 w-9",
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
},
},
defaultVariants: {
@@ -31,18 +34,34 @@ const buttonVariants = cva(
}
);
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean;
-}
+function Button({
+ className,
+ variant,
+ size,
+ loading = false,
+ disabled,
+ asChild = false,
+ children,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ loading?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+ const isDisabled = disabled || loading;
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
- return ;
- }
-);
-Button.displayName = "Button";
+ return (
+
+ {loading && }
+ {children}
+
+ );
+}
export { Button, buttonVariants };
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..0edee86
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,159 @@
+"use client";
+
+import * as React from "react";
+
+import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
+import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
+
+import { Button, buttonVariants } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn("flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", defaultClassNames.nav),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
+ week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ defaultClassNames.day
+ ),
+ range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return
;
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return ;
+ }
+
+ if (orientation === "right") {
+ return ;
+ }
+
+ return ;
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ );
+}
+
+function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+export { Calendar, CalendarDayButton };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 0314abd..65ee44a 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -2,40 +2,55 @@ import * as React from "react";
import { cn } from "@/lib/utils";
-const Card = React.forwardRef>(({ className, ...props }, ref) => (
-
-));
-Card.displayName = "Card";
-
-const CardHeader = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardHeader.displayName = "CardHeader";
-
-const CardTitle = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardTitle.displayName = "CardTitle";
-
-const CardDescription = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardDescription.displayName = "CardDescription";
-
-const CardContent = React.forwardRef>(
- ({ className, ...props }, ref) =>
-);
-CardContent.displayName = "CardContent";
-
-const CardFooter = React.forwardRef>(
- ({ className, ...props }, ref) =>
-);
-CardFooter.displayName = "CardFooter";
-
-export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return
;
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return
;
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return
;
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
index 75a6775..4cc0e23 100644
--- a/src/components/ui/checkbox.tsx
+++ b/src/components/ui/checkbox.tsx
@@ -3,29 +3,28 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
-import { CheckIcon } from "@radix-ui/react-icons";
+import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
-const Checkbox = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-
-
-
-
-));
-Checkbox.displayName = CheckboxPrimitive.Root.displayName;
+function Checkbox({ className, ...props }: React.ComponentProps) {
+ return (
+
+
+
+
+
+ );
+}
export { Checkbox };
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..5dd5784
--- /dev/null
+++ b/src/components/ui/collapsible.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
+
+function Collapsible({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function CollapsibleTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function CollapsibleContent({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 0000000..938570d
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,132 @@
+import * as React from "react";
+
+import { Command as CommandPrimitive } from "cmdk";
+import { SearchIcon } from "lucide-react";
+
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { cn } from "@/lib/utils";
+
+function Command({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CommandDialog({
+ title = "Command Palette",
+ description = "Search for a command to run...",
+ children,
+ ...props
+}: React.ComponentProps & {
+ title?: string;
+ description?: string;
+}) {
+ return (
+
+
+ {title}
+ {description}
+
+
+
+ {children}
+
+
+
+ );
+}
+
+function CommandInput({ className, ...props }: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function CommandList({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CommandEmpty({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function CommandGroup({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CommandSeparator({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CommandItem({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+export {
+ Command,
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+ CommandShortcut,
+};
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 42e6661..e881046 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -3,86 +3,100 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { Cross2Icon } from "@radix-ui/react-icons";
+import { XIcon } from "lucide-react";
import { cn } from "@/lib/utils";
-const Dialog = DialogPrimitive.Root;
+function Dialog({ ...props }: React.ComponentProps) {
+ return ;
+}
-const DialogTrigger = DialogPrimitive.Trigger;
+function DialogTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
-const DialogPortal = DialogPrimitive.Portal;
+function DialogPortal({ ...props }: React.ComponentProps) {
+ return ;
+}
-const DialogClose = DialogPrimitive.Close;
+function DialogClose({ ...props }: React.ComponentProps) {
+ return ;
+}
-const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, ...props }, ref) => (
-
-));
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
-
-const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, children, ...props }, ref) => (
-
-
- ) {
+ return (
+
- {children}
-
-
- Close
-
-
-
-));
-DialogContent.displayName = DialogPrimitive.Content.displayName;
+ />
+ );
+}
+
+function DialogContent({ className, children, ...props }: React.ComponentProps) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ );
+}
-const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-DialogHeader.displayName = "DialogHeader";
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-DialogFooter.displayName = "DialogFooter";
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, ...props }, ref) => (
-
-));
-DialogTitle.displayName = DialogPrimitive.Title.displayName;
+function DialogTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
-const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, ...props }, ref) => (
-
-));
-DialogDescription.displayName = DialogPrimitive.Description.displayName;
+function DialogDescription({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
export {
Dialog,
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index 47bbdd7..9f264e0 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -3,196 +3,218 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
-import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "@/lib/utils";
-const DropdownMenu = DropdownMenuPrimitive.Root;
-
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-
-const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-
-const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- className?: string;
- }
->(({ className, inset, children, ...props }, ref) => (
-
- {children}
-
-
-));
-DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- sideOffset?: number;
- }
->(({ className, sideOffset = 4, ...props }, ref) => (
-
- ) {
+ return ;
+}
+
+function DropdownMenuPortal({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
-
-));
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- className?: string;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- checked?: boolean;
- }
->(({ className, children, checked, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- className?: string;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- className?: string;
- }
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
-
-const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return ;
-};
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
export {
DropdownMenu,
- DropdownMenuCheckboxItem,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
- DropdownMenuItem,
DropdownMenuLabel,
- DropdownMenuPortal,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
- DropdownMenuSubContent,
DropdownMenuSubTrigger,
- DropdownMenuTrigger,
+ DropdownMenuSubContent,
};
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
index 69cb65c..07cabbb 100644
--- a/src/components/ui/form.tsx
+++ b/src/components/ui/form.tsx
@@ -4,11 +4,21 @@ import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
-import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
+import {
+ Controller,
+ type ControllerProps,
+ type FieldPath,
+ type FieldValues,
+ FormProvider,
+ useFormContext,
+ useFormState,
+} from "react-hook-form";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
+import { Typography } from "./typography";
+
const Form = FormProvider;
type FormFieldContextValue<
@@ -36,8 +46,8 @@ const FormField = <
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
-
+ const { getFieldState } = useFormContext();
+ const formState = useFormState({ name: fieldContext.name });
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
@@ -62,78 +72,75 @@ type FormItemContextValue = {
const FormItemContext = React.createContext({} as FormItemContextValue);
-const FormItem = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const id = React.useId();
+function FormItem({ className, ...props }: React.ComponentProps<"div">) {
+ const id = React.useId();
- return (
-
-
-
- );
- }
-);
-FormItem.displayName = "FormItem";
+ return (
+
+
+
+ );
+}
-const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & { className?: string }
->(({ className, ...props }, ref) => {
+function FormLabel({ className, ...props }: React.ComponentProps) {
const { error, formItemId } = useFormField();
- return ;
-});
-FormLabel.displayName = "FormLabel";
-
-const FormControl = React.forwardRef, React.ComponentPropsWithoutRef>(
- ({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
-
- return (
-
- );
- }
-);
-FormControl.displayName = "FormControl";
+ return (
+
+ );
+}
-const FormDescription = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
+function FormControl({ ...props }: React.ComponentProps) {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
- return (
-