refactor: apply consistent formatting to improve code quality
Ensure uniform code formatting across components by aligning with the established code style. Adjust imports, indentation, and spacing to enhance readability and maintainability.
This commit is contained in:
@@ -18,6 +18,11 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true,
|
||||||
|
"a11y": {
|
||||||
|
"useAriaPropsForRole": "warn",
|
||||||
|
"useSemanticElements": "warn",
|
||||||
|
"useFocusableInteractive": "warn"
|
||||||
|
},
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noUnknownAtRules": "off"
|
"noUnknownAtRules": "off"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { useAuth } from "@/providers/auth-provider";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -15,8 +15,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -25,11 +23,14 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
|
|
||||||
const loginSchema = z.object({
|
const loginSchema = z.object({
|
||||||
email: z.string().email({ message: "Email invalide" }),
|
email: z.string().email({ message: "Email invalide" }),
|
||||||
password: z.string().min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }),
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type LoginFormValues = z.infer<typeof loginSchema>;
|
type LoginFormValues = z.infer<typeof loginSchema>;
|
||||||
@@ -50,7 +51,7 @@ export default function LoginPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await login(values.email, values.password);
|
await login(values.email, values.password);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Error is handled in useAuth via toast
|
// Error is handled in useAuth via toast
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -112,7 +113,10 @@ export default function LoginPage() {
|
|||||||
<CardFooter className="flex flex-col space-y-2">
|
<CardFooter className="flex flex-col space-y-2">
|
||||||
<p className="text-sm text-center text-muted-foreground">
|
<p className="text-sm text-center text-muted-foreground">
|
||||||
Vous n'avez pas de compte ?{" "}
|
Vous n'avez pas de compte ?{" "}
|
||||||
<Link href="/register" className="text-primary hover:underline font-medium">
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="text-primary hover:underline font-medium"
|
||||||
|
>
|
||||||
S'inscrire
|
S'inscrire
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { useAuth } from "@/providers/auth-provider";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -24,12 +23,17 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
|
|
||||||
const registerSchema = z.object({
|
const registerSchema = z.object({
|
||||||
username: z.string().min(3, { message: "Le pseudo doit faire au moins 3 caractères" }),
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(3, { message: "Le pseudo doit faire au moins 3 caractères" }),
|
||||||
email: z.string().email({ message: "Email invalide" }),
|
email: z.string().email({ message: "Email invalide" }),
|
||||||
password: z.string().min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }),
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }),
|
||||||
displayName: z.string().optional(),
|
displayName: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +57,7 @@ export default function RegisterPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await register(values);
|
await register(values);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Error handled in useAuth
|
// Error handled in useAuth
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Dialog, DialogContent, DialogTitle, DialogDescription } from "@/components/ui/dialog";
|
import * as React from "react";
|
||||||
import { ContentService } from "@/services/content.service";
|
|
||||||
import { ContentCard } from "@/components/content-card";
|
import { ContentCard } from "@/components/content-card";
|
||||||
import type { Content } from "@/types/content";
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { ContentService } from "@/services/content.service";
|
||||||
|
import type { Content } from "@/types/content";
|
||||||
|
|
||||||
export default function MemeModal({ params }: { params: Promise<{ slug: string }> }) {
|
export default function MemeModal({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>;
|
||||||
|
}) {
|
||||||
const { slug } = React.use(params);
|
const { slug } = React.use(params);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [content, setContent] = React.useState<Content | null>(null);
|
const [content, setContent] = React.useState<Content | null>(null);
|
||||||
@@ -24,8 +33,12 @@ export default function MemeModal({ params }: { params: Promise<{ slug: string }
|
|||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(open) => !open && router.back()}>
|
<Dialog open onOpenChange={(open) => !open && router.back()}>
|
||||||
<DialogContent className="max-w-3xl p-0 overflow-hidden bg-transparent border-none">
|
<DialogContent className="max-w-3xl p-0 overflow-hidden bg-transparent border-none">
|
||||||
<DialogTitle className="sr-only">{content?.title || "Détail du mème"}</DialogTitle>
|
<DialogTitle className="sr-only">
|
||||||
<DialogDescription className="sr-only">Affiche le mème en grand avec ses détails</DialogDescription>
|
{content?.title || "Détail du mème"}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
Affiche le mème en grand avec ses détails
|
||||||
|
</DialogDescription>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="h-[500px] flex items-center justify-center bg-zinc-950/50 rounded-lg">
|
<div className="h-[500px] flex items-center justify-center bg-zinc-950/50 rounded-lg">
|
||||||
<Spinner className="h-10 w-10 text-white" />
|
<Spinner className="h-10 w-10 text-white" />
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function useInfiniteScroll({
|
|||||||
onLoadMore();
|
onLoadMore();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold }
|
{ threshold },
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentLoader = loaderRef.current;
|
const currentLoader = loaderRef.current;
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
import * as React from "react";
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { CategoryContent } from "@/components/category-content";
|
import { CategoryContent } from "@/components/category-content";
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string }>
|
params: Promise<{ slug: string }>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
try {
|
try {
|
||||||
const categories = await CategoryService.getAll();
|
const categories = await CategoryService.getAll();
|
||||||
const category = categories.find(c => c.slug === slug);
|
const category = categories.find((c) => c.slug === slug);
|
||||||
return {
|
return {
|
||||||
title: `${category?.name || slug} | MemeGoat`,
|
title: `${category?.name || slug} | MemeGoat`,
|
||||||
description: `Découvrez tous les mèmes de la catégorie ${category?.name || slug} sur MemeGoat.`,
|
description: `Découvrez tous les mèmes de la catégorie ${category?.name || slug} sur MemeGoat.`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return { title: `Catégorie : ${slug} | MemeGoat` };
|
return { title: `Catégorie : ${slug} | MemeGoat` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function CategoryPage({
|
export default async function CategoryPage({
|
||||||
params
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string }>
|
params: Promise<{ slug: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
return <CategoryContent slug={slug} />;
|
return <CategoryContent slug={slug} />;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import { LayoutGrid } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
import type { Category } from "@/types/content";
|
import type { Category } from "@/types/content";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { LayoutGrid } from "lucide-react";
|
|
||||||
|
|
||||||
export default function CategoriesPage() {
|
export default function CategoriesPage() {
|
||||||
const [categories, setCategories] = React.useState<Category[]>([]);
|
const [categories, setCategories] = React.useState<Category[]>([]);
|
||||||
@@ -25,15 +25,15 @@ export default function CategoriesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||||
{loading ? (
|
{loading
|
||||||
Array.from({ length: 6 }).map((_, i) => (
|
? Array.from({ length: 6 }).map((_, i) => (
|
||||||
<Card key={i} className="animate-pulse">
|
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
|
||||||
|
<Card key={`skeleton-${i}`} className="animate-pulse">
|
||||||
<CardHeader className="h-24 bg-zinc-100 dark:bg-zinc-800 rounded-t-lg" />
|
<CardHeader className="h-24 bg-zinc-100 dark:bg-zinc-800 rounded-t-lg" />
|
||||||
<CardContent className="h-12" />
|
<CardContent className="h-12" />
|
||||||
</Card>
|
</Card>
|
||||||
))
|
))
|
||||||
) : (
|
: categories.map((category) => (
|
||||||
categories.map((category) => (
|
|
||||||
<Link key={category.id} href={`/category/${category.slug}`}>
|
<Link key={category.id} href={`/category/${category.slug}`}>
|
||||||
<Card className="hover:border-primary transition-colors cursor-pointer group h-full">
|
<Card className="hover:border-primary transition-colors cursor-pointer group h-full">
|
||||||
<CardHeader className="bg-zinc-50 dark:bg-zinc-900 group-hover:bg-primary/5 transition-colors">
|
<CardHeader className="bg-zinc-50 dark:bg-zinc-900 group-hover:bg-primary/5 transition-colors">
|
||||||
@@ -41,13 +41,13 @@ export default function CategoriesPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-4">
|
<CardContent className="pt-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{category.description || `Découvrez tous les mèmes de la catégorie ${category.name}.`}
|
{category.description ||
|
||||||
|
`Découvrez tous les mèmes de la catégorie ${category.name}.`}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))
|
))}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { SidebarProvider, SidebarTrigger, SidebarInset } from "@/components/ui/sidebar";
|
|
||||||
import { AppSidebar } from "@/components/app-sidebar";
|
import { AppSidebar } from "@/components/app-sidebar";
|
||||||
import { SearchSidebar } from "@/components/search-sidebar";
|
|
||||||
import { MobileFilters } from "@/components/mobile-filters";
|
import { MobileFilters } from "@/components/mobile-filters";
|
||||||
|
import { SearchSidebar } from "@/components/search-sidebar";
|
||||||
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
SidebarTrigger,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
export default function DashboardLayout({
|
export default function DashboardLayout({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ export default function Loading() {
|
|||||||
<div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
|
<div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
{[...Array(3)].map((_, i) => (
|
{[...Array(3)].map((_, i) => (
|
||||||
<ContentSkeleton key={i} />
|
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
|
||||||
|
<ContentSkeleton key={`loading-skeleton-${i}`} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import * as React from "react";
|
|
||||||
import type { Metadata } from "next";
|
|
||||||
import { ContentService } from "@/services/content.service";
|
|
||||||
import { ContentCard } from "@/components/content-card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ChevronLeft } from "lucide-react";
|
import { ChevronLeft } from "lucide-react";
|
||||||
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
import { ContentCard } from "@/components/content-card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ContentService } from "@/services/content.service";
|
||||||
|
|
||||||
export const revalidate = 3600; // ISR: Revalider toutes les heures
|
export const revalidate = 3600; // ISR: Revalider toutes les heures
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string }>
|
params: Promise<{ slug: string }>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
try {
|
try {
|
||||||
@@ -24,15 +23,15 @@ export async function generateMetadata({
|
|||||||
images: [content.thumbnailUrl || content.url],
|
images: [content.thumbnailUrl || content.url],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return { title: "Mème non trouvé | MemeGoat" };
|
return { title: "Mème non trouvé | MemeGoat" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function MemePage({
|
export default async function MemePage({
|
||||||
params
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string }>
|
params: Promise<{ slug: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
|
||||||
@@ -41,7 +40,10 @@ export default async function MemePage({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-8 px-4">
|
<div className="max-w-4xl mx-auto py-8 px-4">
|
||||||
<Link href="/" className="inline-flex items-center text-sm mb-6 hover:text-primary transition-colors">
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex items-center text-sm mb-6 hover:text-primary transition-colors"
|
||||||
|
>
|
||||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||||
Retour au flux
|
Retour au flux
|
||||||
</Link>
|
</Link>
|
||||||
@@ -57,15 +59,19 @@ export default async function MemePage({
|
|||||||
<div className="space-y-4 text-sm">
|
<div className="space-y-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Publié par</p>
|
<p className="text-muted-foreground">Publié par</p>
|
||||||
<p className="font-medium">{content.author.displayName || content.author.username}</p>
|
<p className="font-medium">
|
||||||
|
{content.author.displayName || content.author.username}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Date</p>
|
<p className="text-muted-foreground">Date</p>
|
||||||
<p className="font-medium">{new Date(content.createdAt).toLocaleDateString('fr-FR', {
|
<p className="font-medium">
|
||||||
day: 'numeric',
|
{new Date(content.createdAt).toLocaleDateString("fr-FR", {
|
||||||
month: 'long',
|
day: "numeric",
|
||||||
year: 'numeric'
|
month: "long",
|
||||||
})}</p>
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{content.description && (
|
{content.description && (
|
||||||
<div>
|
<div>
|
||||||
@@ -77,14 +83,16 @@ export default async function MemePage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-sm border text-center">
|
<div className="bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-sm border text-center">
|
||||||
<p className="text-sm text-muted-foreground mb-4">Envie de créer votre propre mème ?</p>
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
Envie de créer votre propre mème ?
|
||||||
|
</p>
|
||||||
<Button className="w-full">Utiliser ce template</Button>
|
<Button className="w-full">Utiliser ce template</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import * as React from "react";
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import * as React from "react";
|
||||||
import { HomeContent } from "@/components/home-content";
|
import { HomeContent } from "@/components/home-content";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "MemeGoat | La meilleure plateforme de mèmes pour les chèvres",
|
title: "MemeGoat | La meilleure plateforme de mèmes pour les chèvres",
|
||||||
description: "Explorez, créez et partagez les meilleurs mèmes de la communauté. Rejoignez le troupeau sur MemeGoat.",
|
description:
|
||||||
|
"Explorez, créez et partagez les meilleurs mèmes de la communauté. Rejoignez le troupeau sur MemeGoat.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<React.Suspense fallback={
|
<React.Suspense
|
||||||
|
fallback={
|
||||||
<div className="flex items-center justify-center p-12">
|
<div className="flex items-center justify-center p-12">
|
||||||
<Spinner className="h-8 w-8 text-primary" />
|
<Spinner className="h-8 w-8 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
<HomeContent />
|
<HomeContent />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,33 +1,36 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import { Calendar, LogOut, Settings } from "lucide-react";
|
||||||
import { useAuth } from "@/providers/auth-provider";
|
|
||||||
import { ContentList } from "@/components/content-list";
|
|
||||||
import { ContentService } from "@/services/content.service";
|
|
||||||
import { FavoriteService } from "@/services/favorite.service";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Settings, LogOut, Calendar } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
import * as React from "react";
|
||||||
|
import { ContentList } from "@/components/content-list";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
|
import { ContentService } from "@/services/content.service";
|
||||||
|
import { FavoriteService } from "@/services/favorite.service";
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { user, isAuthenticated, isLoading, logout } = useAuth();
|
const { user, isAuthenticated, isLoading, logout } = useAuth();
|
||||||
|
|
||||||
|
const fetchMyMemes = React.useCallback(
|
||||||
|
(params: { limit: number; offset: number }) =>
|
||||||
|
ContentService.getExplore({ ...params, author: user?.username }),
|
||||||
|
[user?.username],
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchMyFavorites = React.useCallback(
|
||||||
|
(params: { limit: number; offset: number }) => FavoriteService.list(params),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading) return null;
|
if (isLoading) return null;
|
||||||
if (!isAuthenticated || !user) {
|
if (!isAuthenticated || !user) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMyMemes = React.useCallback((params: { limit: number; offset: number }) =>
|
|
||||||
ContentService.getExplore({ ...params, author: user.username }),
|
|
||||||
[user.username]);
|
|
||||||
|
|
||||||
const fetchMyFavorites = React.useCallback((params: { limit: number; offset: number }) =>
|
|
||||||
FavoriteService.list(params),
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-8 px-4">
|
<div className="max-w-4xl mx-auto py-8 px-4">
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-8 border shadow-sm mb-8">
|
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-8 border shadow-sm mb-8">
|
||||||
@@ -40,13 +43,19 @@ export default function ProfilePage() {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex-1 text-center md:text-left space-y-4">
|
<div className="flex-1 text-center md:text-left space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">{user.displayName || user.username}</h1>
|
<h1 className="text-3xl font-bold">
|
||||||
|
{user.displayName || user.username}
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground">@{user.username}</p>
|
<p className="text-muted-foreground">@{user.username}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap justify-center md:justify-start gap-4 text-sm text-muted-foreground">
|
<div className="flex flex-wrap justify-center md:justify-start gap-4 text-sm text-muted-foreground">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
Membre depuis {new Date(user.createdAt).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })}
|
Membre depuis{" "}
|
||||||
|
{new Date(user.createdAt).toLocaleDateString("fr-FR", {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap justify-center md:justify-start gap-2">
|
<div className="flex flex-wrap justify-center md:justify-start gap-2">
|
||||||
@@ -56,7 +65,12 @@ export default function ProfilePage() {
|
|||||||
Paramètres
|
Paramètres
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" onClick={() => logout()} className="text-red-500 hover:text-red-600 hover:bg-red-50">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => logout()}
|
||||||
|
className="text-red-500 hover:text-red-600 hover:bg-red-50"
|
||||||
|
>
|
||||||
<LogOut className="h-4 w-4 mr-2" />
|
<LogOut className="h-4 w-4 mr-2" />
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { ContentList } from "@/components/content-list";
|
|||||||
import { ContentService } from "@/services/content.service";
|
import { ContentService } from "@/services/content.service";
|
||||||
|
|
||||||
export default function RecentPage() {
|
export default function RecentPage() {
|
||||||
const fetchFn = React.useCallback((params: { limit: number; offset: number }) =>
|
const fetchFn = React.useCallback(
|
||||||
|
(params: { limit: number; offset: number }) =>
|
||||||
ContentService.getRecent(params.limit, params.offset),
|
ContentService.getRecent(params.limit, params.offset),
|
||||||
[]);
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return <ContentList fetchFn={fetchFn} title="Nouveaux Mèmes" />;
|
return <ContentList fetchFn={fetchFn} title="Nouveaux Mèmes" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { ContentList } from "@/components/content-list";
|
|||||||
import { ContentService } from "@/services/content.service";
|
import { ContentService } from "@/services/content.service";
|
||||||
|
|
||||||
export default function TrendsPage() {
|
export default function TrendsPage() {
|
||||||
const fetchFn = React.useCallback((params: { limit: number; offset: number }) =>
|
const fetchFn = React.useCallback(
|
||||||
|
(params: { limit: number; offset: number }) =>
|
||||||
ContentService.getTrends(params.limit, params.offset),
|
ContentService.getTrends(params.limit, params.offset),
|
||||||
[]);
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return <ContentList fetchFn={fetchFn} title="Top Tendances" />;
|
return <ContentList fetchFn={fetchFn} title="Top Tendances" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as z from "zod";
|
import { Image as ImageIcon, Loader2, Upload, X } from "lucide-react";
|
||||||
import { Upload, Image as ImageIcon, Film, X, Loader2 } from "lucide-react";
|
import NextImage from "next/image";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import * as z from "zod";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -26,7 +27,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
import { ContentService } from "@/services/content.service";
|
import { ContentService } from "@/services/content.service";
|
||||||
import type { Category } from "@/types/content";
|
import type { Category } from "@/types/content";
|
||||||
@@ -90,16 +90,36 @@ export default function UploadPage() {
|
|||||||
formData.append("type", values.type);
|
formData.append("type", values.type);
|
||||||
if (values.categoryId) formData.append("categoryId", values.categoryId);
|
if (values.categoryId) formData.append("categoryId", values.categoryId);
|
||||||
if (values.tags) {
|
if (values.tags) {
|
||||||
const tagsArray = values.tags.split(",").map(t => t.trim()).filter(t => t !== "");
|
const tagsArray = values.tags
|
||||||
tagsArray.forEach(tag => formData.append("tags[]", tag));
|
.split(",")
|
||||||
|
.map((t) => t.trim())
|
||||||
|
.filter((t) => t !== "");
|
||||||
|
for (const tag of tagsArray) {
|
||||||
|
formData.append("tags[]", tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await ContentService.upload(formData);
|
await ContentService.upload(formData);
|
||||||
toast.success("Mème uploadé avec succès !");
|
toast.success("Mème uploadé avec succès !");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error("Upload failed:", error);
|
console.error("Upload failed:", error);
|
||||||
toast.error(error.response?.data?.message || "Échec de l'upload. Êtes-vous connecté ?");
|
let errorMessage = "Échec de l'upload. Êtes-vous connecté ?";
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"response" in error &&
|
||||||
|
error.response &&
|
||||||
|
typeof error.response === "object" &&
|
||||||
|
"data" in error.response &&
|
||||||
|
error.response.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"message" in error.response.data &&
|
||||||
|
typeof error.response.data.message === "string"
|
||||||
|
) {
|
||||||
|
errorMessage = error.response.data.message;
|
||||||
|
}
|
||||||
|
toast.error(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
@@ -120,15 +140,18 @@ export default function UploadPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FormLabel>Fichier (Image ou GIF)</FormLabel>
|
<FormLabel>Fichier (Image ou GIF)</FormLabel>
|
||||||
{!preview ? (
|
{!preview ? (
|
||||||
<div
|
<button
|
||||||
className="border-2 border-dashed rounded-lg p-12 flex flex-col items-center justify-center bg-zinc-50 dark:bg-zinc-900 cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors"
|
type="button"
|
||||||
|
className="w-full border-2 border-dashed rounded-lg p-12 flex flex-col items-center justify-center bg-zinc-50 dark:bg-zinc-900 cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors"
|
||||||
onClick={() => document.getElementById("file-upload")?.click()}
|
onClick={() => document.getElementById("file-upload")?.click()}
|
||||||
>
|
>
|
||||||
<div className="bg-primary/10 p-4 rounded-full mb-4">
|
<div className="bg-primary/10 p-4 rounded-full mb-4">
|
||||||
<ImageIcon className="h-8 w-8 text-primary" />
|
<ImageIcon className="h-8 w-8 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<p className="font-medium">Cliquez pour choisir un fichier</p>
|
<p className="font-medium">Cliquez pour choisir un fichier</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">PNG, JPG ou GIF jusqu'à 10Mo</p>
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
PNG, JPG ou GIF jusqu'à 10Mo
|
||||||
|
</p>
|
||||||
<input
|
<input
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
type="file"
|
type="file"
|
||||||
@@ -136,19 +159,22 @@ export default function UploadPage() {
|
|||||||
accept="image/*,.gif"
|
accept="image/*,.gif"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative rounded-lg overflow-hidden border bg-zinc-100 dark:bg-zinc-800">
|
<div className="relative rounded-lg overflow-hidden border bg-zinc-100 dark:bg-zinc-800">
|
||||||
<img
|
<div className="relative h-[400px] w-full">
|
||||||
|
<NextImage
|
||||||
src={preview}
|
src={preview}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
className="max-h-[400px] mx-auto object-contain"
|
fill
|
||||||
|
className="object-contain"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute top-2 right-2 rounded-full"
|
className="absolute top-2 right-2 rounded-full z-10"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setPreview(null);
|
setPreview(null);
|
||||||
@@ -210,8 +236,10 @@ export default function UploadPage() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{categories.map(cat => (
|
{categories.map((cat) => (
|
||||||
<SelectItem key={cat.id} value={cat.id}>{cat.name}</SelectItem>
|
<SelectItem key={cat.id} value={cat.id}>
|
||||||
|
{cat.name}
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -230,9 +258,7 @@ export default function UploadPage() {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="funny, coding, goat..." {...field} />
|
<Input placeholder="funny, coding, goat..." {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>Séparez les tags par des virgules.</FormDescription>
|
||||||
Séparez les tags par des virgules.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { AlertTriangle, Home, RefreshCw } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { AlertTriangle, RefreshCw, Home } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: correct use
|
||||||
export default function Error({
|
export default function Error({
|
||||||
error,
|
error,
|
||||||
reset,
|
reset,
|
||||||
@@ -24,9 +25,12 @@ export default function Error({
|
|||||||
<AlertTriangle className="h-12 w-12 text-orange-600 dark:text-orange-400" />
|
<AlertTriangle className="h-12 w-12 text-orange-600 dark:text-orange-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold tracking-tight">Oups ! Une erreur est survenue</h1>
|
<h1 className="text-4xl font-bold tracking-tight">
|
||||||
|
Oups ! Une erreur est survenue
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground text-lg">
|
<p className="text-muted-foreground text-lg">
|
||||||
La chèvre a glissé sur une peau de banane. Nous essayons de la remettre sur pied.
|
La chèvre a glissé sur une peau de banane. Nous essayons de la remettre sur
|
||||||
|
pied.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||||
<Button onClick={reset} size="lg" className="gap-2">
|
<Button onClick={reset} size="lg" className="gap-2">
|
||||||
|
|||||||
@@ -6,8 +6,11 @@
|
|||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
--font-sans:
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--font-mono:
|
||||||
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
@@ -80,34 +83,37 @@
|
|||||||
--popover: oklch(0.9911 0 0);
|
--popover: oklch(0.9911 0 0);
|
||||||
--popover-foreground: oklch(0.2435 0 0);
|
--popover-foreground: oklch(0.2435 0 0);
|
||||||
--primary: oklch(0.4341 0.0392 41.9938);
|
--primary: oklch(0.4341 0.0392 41.9938);
|
||||||
--primary-foreground: oklch(1.0000 0 0);
|
--primary-foreground: oklch(1 0 0);
|
||||||
--secondary: oklch(0.9200 0.0651 74.3695);
|
--secondary: oklch(0.92 0.0651 74.3695);
|
||||||
--secondary-foreground: oklch(0.3499 0.0685 40.8288);
|
--secondary-foreground: oklch(0.3499 0.0685 40.8288);
|
||||||
--muted: oklch(0.9521 0 0);
|
--muted: oklch(0.9521 0 0);
|
||||||
--muted-foreground: oklch(0.5032 0 0);
|
--muted-foreground: oklch(0.5032 0 0);
|
||||||
--accent: oklch(0.9310 0 0);
|
--accent: oklch(0.931 0 0);
|
||||||
--accent-foreground: oklch(0.2435 0 0);
|
--accent-foreground: oklch(0.2435 0 0);
|
||||||
--destructive: oklch(0.6271 0.1936 33.3390);
|
--destructive: oklch(0.6271 0.1936 33.339);
|
||||||
--border: oklch(0.8822 0 0);
|
--border: oklch(0.8822 0 0);
|
||||||
--input: oklch(0.8822 0 0);
|
--input: oklch(0.8822 0 0);
|
||||||
--ring: oklch(0.4341 0.0392 41.9938);
|
--ring: oklch(0.4341 0.0392 41.9938);
|
||||||
--chart-1: oklch(0.4341 0.0392 41.9938);
|
--chart-1: oklch(0.4341 0.0392 41.9938);
|
||||||
--chart-2: oklch(0.9200 0.0651 74.3695);
|
--chart-2: oklch(0.92 0.0651 74.3695);
|
||||||
--chart-3: oklch(0.9310 0 0);
|
--chart-3: oklch(0.931 0 0);
|
||||||
--chart-4: oklch(0.9367 0.0523 75.5009);
|
--chart-4: oklch(0.9367 0.0523 75.5009);
|
||||||
--chart-5: oklch(0.4338 0.0437 41.6746);
|
--chart-5: oklch(0.4338 0.0437 41.6746);
|
||||||
--sidebar: oklch(0.9881 0 0);
|
--sidebar: oklch(0.9881 0 0);
|
||||||
--sidebar-foreground: oklch(0.2645 0 0);
|
--sidebar-foreground: oklch(0.2645 0 0);
|
||||||
--sidebar-primary: oklch(0.3250 0 0);
|
--sidebar-primary: oklch(0.325 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.9881 0 0);
|
--sidebar-primary-foreground: oklch(0.9881 0 0);
|
||||||
--sidebar-accent: oklch(0.9761 0 0);
|
--sidebar-accent: oklch(0.9761 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.3250 0 0);
|
--sidebar-accent-foreground: oklch(0.325 0 0);
|
||||||
--sidebar-border: oklch(0.9401 0 0);
|
--sidebar-border: oklch(0.9401 0 0);
|
||||||
--sidebar-ring: oklch(0.7731 0 0);
|
--sidebar-ring: oklch(0.7731 0 0);
|
||||||
--destructive-foreground: oklch(1.0000 0 0);
|
--destructive-foreground: oklch(1 0 0);
|
||||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
--font-sans:
|
||||||
|
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
--font-mono:
|
||||||
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
--shadow-color: oklch(0 0 0);
|
--shadow-color: oklch(0 0 0);
|
||||||
--shadow-opacity: 0.09;
|
--shadow-opacity: 0.09;
|
||||||
--shadow-blur: 5.5px;
|
--shadow-blur: 5.5px;
|
||||||
@@ -118,11 +124,21 @@
|
|||||||
--spacing: 0.2rem;
|
--spacing: 0.2rem;
|
||||||
--shadow-2xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
--shadow-2xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
||||||
--shadow-xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
--shadow-xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
||||||
--shadow-sm: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
--shadow-sm:
|
||||||
--shadow: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
--shadow-md: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 2px 4px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
--shadow-lg: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 4px 6px -0.5px hsl(0 0% 0% / 0.09);
|
--shadow:
|
||||||
--shadow-xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 8px 10px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-md:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 2px 4px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-lg:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 4px 6px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-xl:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 8px 10px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
--shadow-2xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.22);
|
--shadow-2xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.22);
|
||||||
--tracking-normal: 0em;
|
--tracking-normal: 0em;
|
||||||
}
|
}
|
||||||
@@ -135,35 +151,38 @@
|
|||||||
--popover: oklch(0.2134 0 0);
|
--popover: oklch(0.2134 0 0);
|
||||||
--popover-foreground: oklch(0.9491 0 0);
|
--popover-foreground: oklch(0.9491 0 0);
|
||||||
--primary: oklch(0.9247 0.0524 66.1732);
|
--primary: oklch(0.9247 0.0524 66.1732);
|
||||||
--primary-foreground: oklch(0.2490 0.0317 198.7326);
|
--primary-foreground: oklch(0.249 0.0317 198.7326);
|
||||||
--secondary: oklch(0.3163 0.0190 63.6992);
|
--secondary: oklch(0.3163 0.019 63.6992);
|
||||||
--secondary-foreground: oklch(0.9247 0.0524 66.1732);
|
--secondary-foreground: oklch(0.9247 0.0524 66.1732);
|
||||||
--muted: oklch(0.2520 0 0);
|
--muted: oklch(0.252 0 0);
|
||||||
--muted-foreground: oklch(0.7699 0 0);
|
--muted-foreground: oklch(0.7699 0 0);
|
||||||
--accent: oklch(0.2850 0 0);
|
--accent: oklch(0.285 0 0);
|
||||||
--accent-foreground: oklch(0.9491 0 0);
|
--accent-foreground: oklch(0.9491 0 0);
|
||||||
--destructive: oklch(0.6271 0.1936 33.3390);
|
--destructive: oklch(0.6271 0.1936 33.339);
|
||||||
--border: oklch(0.2351 0.0115 91.7467);
|
--border: oklch(0.2351 0.0115 91.7467);
|
||||||
--input: oklch(0.4017 0 0);
|
--input: oklch(0.4017 0 0);
|
||||||
--ring: oklch(0.9247 0.0524 66.1732);
|
--ring: oklch(0.9247 0.0524 66.1732);
|
||||||
--chart-1: oklch(0.9247 0.0524 66.1732);
|
--chart-1: oklch(0.9247 0.0524 66.1732);
|
||||||
--chart-2: oklch(0.3163 0.0190 63.6992);
|
--chart-2: oklch(0.3163 0.019 63.6992);
|
||||||
--chart-3: oklch(0.2850 0 0);
|
--chart-3: oklch(0.285 0 0);
|
||||||
--chart-4: oklch(0.3481 0.0219 67.0001);
|
--chart-4: oklch(0.3481 0.0219 67.0001);
|
||||||
--chart-5: oklch(0.9245 0.0533 67.0855);
|
--chart-5: oklch(0.9245 0.0533 67.0855);
|
||||||
--sidebar: oklch(0.2103 0.0059 285.8852);
|
--sidebar: oklch(0.2103 0.0059 285.8852);
|
||||||
--sidebar-foreground: oklch(0.9674 0.0013 286.3752);
|
--sidebar-foreground: oklch(0.9674 0.0013 286.3752);
|
||||||
--sidebar-primary: oklch(0.4882 0.2172 264.3763);
|
--sidebar-primary: oklch(0.4882 0.2172 264.3763);
|
||||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
--sidebar-primary-foreground: oklch(1 0 0);
|
||||||
--sidebar-accent: oklch(0.2739 0.0055 286.0326);
|
--sidebar-accent: oklch(0.2739 0.0055 286.0326);
|
||||||
--sidebar-accent-foreground: oklch(0.9674 0.0013 286.3752);
|
--sidebar-accent-foreground: oklch(0.9674 0.0013 286.3752);
|
||||||
--sidebar-border: oklch(0.2739 0.0055 286.0326);
|
--sidebar-border: oklch(0.2739 0.0055 286.0326);
|
||||||
--sidebar-ring: oklch(0.8711 0.0055 286.2860);
|
--sidebar-ring: oklch(0.8711 0.0055 286.286);
|
||||||
--destructive-foreground: oklch(1.0000 0 0);
|
--destructive-foreground: oklch(1 0 0);
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
--font-sans:
|
||||||
|
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
--font-mono:
|
||||||
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
--shadow-color: oklch(0 0 0);
|
--shadow-color: oklch(0 0 0);
|
||||||
--shadow-opacity: 0.09;
|
--shadow-opacity: 0.09;
|
||||||
--shadow-blur: 5.5px;
|
--shadow-blur: 5.5px;
|
||||||
@@ -174,11 +193,21 @@
|
|||||||
--spacing: 0.2rem;
|
--spacing: 0.2rem;
|
||||||
--shadow-2xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
--shadow-2xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
||||||
--shadow-xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
--shadow-xs: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.04);
|
||||||
--shadow-sm: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
--shadow-sm:
|
||||||
--shadow: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
--shadow-md: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 2px 4px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
--shadow-lg: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 4px 6px -0.5px hsl(0 0% 0% / 0.09);
|
--shadow:
|
||||||
--shadow-xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09), 2.5px 8px 10px -0.5px hsl(0 0% 0% / 0.09);
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 1px 2px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-md:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 2px 4px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-lg:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 4px 6px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
|
--shadow-xl:
|
||||||
|
2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.09),
|
||||||
|
2.5px 8px 10px -0.5px hsl(0 0% 0% / 0.09);
|
||||||
--shadow-2xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.22);
|
--shadow-2xl: 2.5px 3.5px 5.5px 0.5px hsl(0 0% 0% / 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { AlertCircle, Home } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Home, AlertCircle } from "lucide-react";
|
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
@@ -13,7 +13,9 @@ export default function NotFound() {
|
|||||||
<AlertCircle className="h-12 w-12 text-red-600 dark:text-red-400" />
|
<AlertCircle className="h-12 w-12 text-red-600 dark:text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold tracking-tight">404 - Perdu dans le troupeau ?</h1>
|
<h1 className="text-4xl font-bold tracking-tight">
|
||||||
|
404 - Perdu dans le troupeau ?
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground text-lg">
|
<p className="text-muted-foreground text-lg">
|
||||||
On dirait que ce mème s'est enfui. La chèvre ne l'a pas trouvé.
|
On dirait que ce mème s'est enfui. La chèvre ne l'a pas trouvé.
|
||||||
</p>
|
</p>
|
||||||
@@ -26,9 +28,7 @@ export default function NotFound() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12 text-8xl grayscale opacity-20 select-none">
|
<div className="mt-12 text-8xl grayscale opacity-20 select-none">🐐</div>
|
||||||
🐐
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
import { ContentService } from "@/services/content.service";
|
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
|
import { ContentService } from "@/services/content.service";
|
||||||
|
|
||||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://memegoat.local";
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://memegoat.local";
|
||||||
|
|
||||||
// Pages statiques
|
// Pages statiques
|
||||||
const routes: MetadataRoute.Sitemap = ["", "/trends", "/recent"].map((route) => ({
|
const routes: MetadataRoute.Sitemap = ["", "/trends", "/recent"].map(
|
||||||
|
(route) => ({
|
||||||
url: `${baseUrl}${route}`,
|
url: `${baseUrl}${route}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "daily" as const,
|
changeFrequency: "daily" as const,
|
||||||
priority: route === "" ? 1 : 0.8,
|
priority: route === "" ? 1 : 0.8,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Catégories
|
// Catégories
|
||||||
try {
|
try {
|
||||||
@@ -23,7 +25,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
}));
|
}));
|
||||||
routes.push(...categoryRoutes);
|
routes.push(...categoryRoutes);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
console.error("Sitemap: Failed to fetch categories");
|
console.error("Sitemap: Failed to fetch categories");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
}));
|
}));
|
||||||
routes.push(...memeRoutes);
|
routes.push(...memeRoutes);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
console.error("Sitemap: Failed to fetch memes");
|
console.error("Sitemap: Failed to fetch memes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import {
|
import {
|
||||||
Home,
|
ChevronRight,
|
||||||
TrendingUp,
|
|
||||||
Clock,
|
Clock,
|
||||||
|
HelpCircle,
|
||||||
|
Home,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
|
LogIn,
|
||||||
|
LogOut,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
Settings,
|
Settings,
|
||||||
HelpCircle,
|
TrendingUp,
|
||||||
ChevronRight,
|
|
||||||
LogOut,
|
|
||||||
User as UserIcon,
|
User as UserIcon,
|
||||||
LogIn,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "@/components/ui/collapsible";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -31,23 +44,9 @@ import {
|
|||||||
SidebarMenuSubButton,
|
SidebarMenuSubButton,
|
||||||
SidebarMenuSubItem,
|
SidebarMenuSubItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import {
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/components/ui/collapsible";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
import type { Category } from "@/types/content";
|
import type { Category } from "@/types/content";
|
||||||
import { useAuth } from "@/providers/auth-provider";
|
|
||||||
|
|
||||||
const mainNav = [
|
const mainNav = [
|
||||||
{
|
{
|
||||||
@@ -69,7 +68,7 @@ const mainNav = [
|
|||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { user, logout, isAuthenticated, isLoading } = useAuth();
|
const { user, logout, isAuthenticated } = useAuth();
|
||||||
const [categories, setCategories] = React.useState<Category[]>([]);
|
const [categories, setCategories] = React.useState<Category[]>([]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -80,9 +79,7 @@ export function AppSidebar() {
|
|||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<SidebarHeader className="flex items-center justify-center py-4">
|
<SidebarHeader className="flex items-center justify-center py-4">
|
||||||
<Link href="/" className="flex items-center gap-2 font-bold text-xl">
|
<Link href="/" className="flex items-center gap-2 font-bold text-xl">
|
||||||
<div className="bg-primary text-primary-foreground p-1 rounded">
|
<div className="bg-primary text-primary-foreground p-1 rounded">🐐</div>
|
||||||
🐐
|
|
||||||
</div>
|
|
||||||
<span className="group-data-[collapsible=icon]:hidden">MemeGoat</span>
|
<span className="group-data-[collapsible=icon]:hidden">MemeGoat</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
@@ -122,7 +119,10 @@ export function AppSidebar() {
|
|||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<SidebarMenuSubItem key={category.id}>
|
<SidebarMenuSubItem key={category.id}>
|
||||||
<SidebarMenuSubButton asChild isActive={pathname === `/category/${category.slug}`}>
|
<SidebarMenuSubButton
|
||||||
|
asChild
|
||||||
|
isActive={pathname === `/category/${category.slug}`}
|
||||||
|
>
|
||||||
<Link href={`/category/${category.slug}`}>
|
<Link href={`/category/${category.slug}`}>
|
||||||
<span>{category.name}</span>
|
<span>{category.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { ContentList } from "@/components/content-list";
|
|||||||
import { ContentService } from "@/services/content.service";
|
import { ContentService } from "@/services/content.service";
|
||||||
|
|
||||||
export function CategoryContent({ slug }: { slug: string }) {
|
export function CategoryContent({ slug }: { slug: string }) {
|
||||||
const fetchFn = React.useCallback((p: { limit: number; offset: number }) =>
|
const fetchFn = React.useCallback(
|
||||||
|
(p: { limit: number; offset: number }) =>
|
||||||
ContentService.getExplore({ ...p, category: slug }),
|
ContentService.getExplore({ ...p, category: slug }),
|
||||||
[slug]);
|
[slug],
|
||||||
|
);
|
||||||
|
|
||||||
return <ContentList fetchFn={fetchFn} title={`Catégorie : ${slug}`} />;
|
return <ContentList fetchFn={fetchFn} title={`Catégorie : ${slug}`} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import { Eye, Heart, MoreHorizontal, Share2 } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Heart, MessageSquare, Share2, MoreHorizontal, Eye } from "lucide-react";
|
import { useRouter } from "next/navigation";
|
||||||
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
|
import * as React from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { toast } from "sonner";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import type { Content } from "@/types/content";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { useAuth } from "@/providers/auth-provider";
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
import { FavoriteService } from "@/services/favorite.service";
|
import { FavoriteService } from "@/services/favorite.service";
|
||||||
import { useRouter } from "next/navigation";
|
import type { Content } from "@/types/content";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface ContentCardProps {
|
interface ContentCardProps {
|
||||||
content: Content;
|
content: Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContentCard({ content }: ContentCardProps) {
|
export function ContentCard({ content }: ContentCardProps) {
|
||||||
const { isAuthenticated, user } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isLiked, setIsLiked] = React.useState(false);
|
const [isLiked, setIsLiked] = React.useState(false);
|
||||||
const [likesCount, setLikesCount] = React.useState(content.favoritesCount);
|
const [likesCount, setLikesCount] = React.useState(content.favoritesCount);
|
||||||
@@ -38,13 +43,13 @@ export function ContentCard({ content }: ContentCardProps) {
|
|||||||
if (isLiked) {
|
if (isLiked) {
|
||||||
await FavoriteService.remove(content.id);
|
await FavoriteService.remove(content.id);
|
||||||
setIsLiked(false);
|
setIsLiked(false);
|
||||||
setLikesCount(prev => prev - 1);
|
setLikesCount((prev) => prev - 1);
|
||||||
} else {
|
} else {
|
||||||
await FavoriteService.add(content.id);
|
await FavoriteService.add(content.id);
|
||||||
setIsLiked(true);
|
setIsLiked(true);
|
||||||
setLikesCount(prev => prev + 1);
|
setLikesCount((prev) => prev + 1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Une erreur est survenue");
|
toast.error("Une erreur est survenue");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -57,11 +62,14 @@ export function ContentCard({ content }: ContentCardProps) {
|
|||||||
<AvatarFallback>{content.author.username[0].toUpperCase()}</AvatarFallback>
|
<AvatarFallback>{content.author.username[0].toUpperCase()}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Link href={`/user/${content.author.username}`} className="text-sm font-semibold hover:underline">
|
<Link
|
||||||
|
href={`/user/${content.author.username}`}
|
||||||
|
className="text-sm font-semibold hover:underline"
|
||||||
|
>
|
||||||
{content.author.displayName || content.author.username}
|
{content.author.displayName || content.author.username}
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{new Date(content.createdAt).toLocaleDateString('fr-FR')}
|
{new Date(content.createdAt).toLocaleDateString("fr-FR")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" className="ml-auto h-8 w-8">
|
<Button variant="ghost" size="icon" className="ml-auto h-8 w-8">
|
||||||
@@ -96,10 +104,10 @@ export function ContentCard({ content }: ContentCardProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={`gap-1.5 h-8 ${isLiked ? 'text-red-500 hover:text-red-600' : ''}`}
|
className={`gap-1.5 h-8 ${isLiked ? "text-red-500 hover:text-red-600" : ""}`}
|
||||||
onClick={handleLike}
|
onClick={handleLike}
|
||||||
>
|
>
|
||||||
<Heart className={`h-4 w-4 ${isLiked ? 'fill-current' : ''}`} />
|
<Heart className={`h-4 w-4 ${isLiked ? "fill-current" : ""}`} />
|
||||||
<span className="text-xs">{likesCount}</span>
|
<span className="text-xs">{likesCount}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" className="gap-1.5 h-8">
|
<Button variant="ghost" size="sm" className="gap-1.5 h-8">
|
||||||
@@ -118,9 +126,13 @@ export function ContentCard({ content }: ContentCardProps) {
|
|||||||
<div className="w-full space-y-2">
|
<div className="w-full space-y-2">
|
||||||
<h3 className="font-medium text-sm line-clamp-2">{content.title}</h3>
|
<h3 className="font-medium text-sm line-clamp-2">{content.title}</h3>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{content.tags.slice(0, 3).map((tag, i) => (
|
{content.tags.slice(0, 3).map((tag, _i) => (
|
||||||
<Badge key={typeof tag === 'string' ? tag : tag.id} variant="secondary" className="text-[10px] py-0 px-1.5">
|
<Badge
|
||||||
#{typeof tag === 'string' ? tag : tag.name}
|
key={typeof tag === "string" ? tag : tag.id}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-[10px] py-0 px-1.5"
|
||||||
|
>
|
||||||
|
#{typeof tag === "string" ? tag : tag.name}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ContentCard } from "@/components/content-card";
|
|
||||||
import { ContentService } from "@/services/content.service";
|
|
||||||
import type { Content, PaginatedResponse } from "@/types/content";
|
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
|
||||||
|
|
||||||
import { useInfiniteScroll } from "@/app/(dashboard)/_hooks/use-infinite-scroll";
|
import { useInfiniteScroll } from "@/app/(dashboard)/_hooks/use-infinite-scroll";
|
||||||
|
import { ContentCard } from "@/components/content-card";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import type { Content, PaginatedResponse } from "@/types/content";
|
||||||
|
|
||||||
interface ContentListProps {
|
interface ContentListProps {
|
||||||
fetchFn: (params: { limit: number; offset: number }) => Promise<PaginatedResponse<Content>>;
|
fetchFn: (params: {
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
}) => Promise<PaginatedResponse<Content>>;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ export function ContentList({ fetchFn, title }: ContentListProps) {
|
|||||||
offset: offset + 10,
|
offset: offset + 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
setContents(prev => [...prev, ...response.data]);
|
setContents((prev) => [...prev, ...response.data]);
|
||||||
setOffset(prev => prev + 10);
|
setOffset((prev) => prev + 10);
|
||||||
setHasMore(response.data.length === 10);
|
setHasMore(response.data.length === 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load more contents:", error);
|
console.error("Failed to load more contents:", error);
|
||||||
@@ -65,7 +66,6 @@ export function ContentList({ fetchFn, title }: ContentListProps) {
|
|||||||
fetchInitial();
|
fetchInitial();
|
||||||
}, [fetchFn]);
|
}, [fetchFn]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
|
<div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
|
||||||
{title && <h1 className="text-2xl font-bold">{title}</h1>}
|
{title && <h1 className="text-2xl font-bold">{title}</h1>}
|
||||||
@@ -78,10 +78,14 @@ export function ContentList({ fetchFn, title }: ContentListProps) {
|
|||||||
<div ref={loaderRef} className="py-8 flex justify-center">
|
<div ref={loaderRef} className="py-8 flex justify-center">
|
||||||
{loading && <Spinner className="h-8 w-8 text-primary" />}
|
{loading && <Spinner className="h-8 w-8 text-primary" />}
|
||||||
{!hasMore && contents.length > 0 && (
|
{!hasMore && contents.length > 0 && (
|
||||||
<p className="text-muted-foreground text-sm italic">Vous avez atteint la fin ! 🐐</p>
|
<p className="text-muted-foreground text-sm italic">
|
||||||
|
Vous avez atteint la fin ! 🐐
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
{!loading && contents.length === 0 && (
|
{!loading && contents.length === 0 && (
|
||||||
<p className="text-muted-foreground text-sm italic">Aucun mème trouvé ici... pour l'instant !</p>
|
<p className="text-muted-foreground text-sm italic">
|
||||||
|
Aucun mème trouvé ici... pour l'instant !
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
export function ContentSkeleton() {
|
export function ContentSkeleton() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ContentList } from "@/components/content-list";
|
import { ContentList } from "@/components/content-list";
|
||||||
import { ContentService } from "@/services/content.service";
|
import { ContentService } from "@/services/content.service";
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
|
|
||||||
export function HomeContent() {
|
export function HomeContent() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -13,15 +13,17 @@ export function HomeContent() {
|
|||||||
const tag = searchParams.get("tag") || undefined;
|
const tag = searchParams.get("tag") || undefined;
|
||||||
const query = searchParams.get("query") || undefined;
|
const query = searchParams.get("query") || undefined;
|
||||||
|
|
||||||
const fetchFn = React.useCallback((params: { limit: number; offset: number }) =>
|
const fetchFn = React.useCallback(
|
||||||
|
(params: { limit: number; offset: number }) =>
|
||||||
ContentService.getExplore({
|
ContentService.getExplore({
|
||||||
...params,
|
...params,
|
||||||
sort,
|
sort,
|
||||||
category,
|
category,
|
||||||
tag,
|
tag,
|
||||||
query
|
query,
|
||||||
}),
|
}),
|
||||||
[sort, category, tag, query]);
|
[sort, category, tag, query],
|
||||||
|
);
|
||||||
|
|
||||||
const title = query
|
const title = query
|
||||||
? `Résultats pour "${query}"`
|
? `Résultats pour "${query}"`
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { Filter, Search } from "lucide-react";
|
import { Filter, Search } from "lucide-react";
|
||||||
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -10,11 +15,6 @@ import {
|
|||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
import type { Category } from "@/types/content";
|
import type { Category } from "@/types/content";
|
||||||
|
|
||||||
@@ -33,7 +33,8 @@ export function MobileFilters() {
|
|||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const updateSearch = React.useCallback((name: string, value: string | null) => {
|
const updateSearch = React.useCallback(
|
||||||
|
(name: string, value: string | null) => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
if (value) {
|
if (value) {
|
||||||
params.set(name, value);
|
params.set(name, value);
|
||||||
@@ -41,7 +42,9 @@ export function MobileFilters() {
|
|||||||
params.delete(name);
|
params.delete(name);
|
||||||
}
|
}
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
}, [router, pathname, searchParams]);
|
},
|
||||||
|
[router, pathname, searchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -124,16 +127,20 @@ export function MobileFilters() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium mb-3">Tags populaires</h3>
|
<h3 className="text-sm font-medium mb-3">Tags populaires</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{["funny", "coding", "cat", "dog", "work", "relatable", "gaming"].map(tag => (
|
{["funny", "coding", "cat", "dog", "work", "relatable", "gaming"].map(
|
||||||
|
(tag) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={tag}
|
key={tag}
|
||||||
variant={searchParams.get("tag") === tag ? "default" : "outline"}
|
variant={searchParams.get("tag") === tag ? "default" : "outline"}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => updateSearch("tag", searchParams.get("tag") === tag ? null : tag)}
|
onClick={() =>
|
||||||
|
updateSearch("tag", searchParams.get("tag") === tag ? null : tag)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
#{tag}
|
#{tag}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Filter, Search } from "lucide-react";
|
||||||
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Search, Filter } from "lucide-react";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Input } from "@/components/ui/input";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { CategoryService } from "@/services/category.service";
|
import { CategoryService } from "@/services/category.service";
|
||||||
import type { Category } from "@/types/content";
|
import type { Category } from "@/types/content";
|
||||||
|
|
||||||
@@ -22,7 +22,8 @@ export function SearchSidebar() {
|
|||||||
CategoryService.getAll().then(setCategories).catch(console.error);
|
CategoryService.getAll().then(setCategories).catch(console.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateSearch = React.useCallback((name: string, value: string | null) => {
|
const updateSearch = React.useCallback(
|
||||||
|
(name: string, value: string | null) => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
if (value) {
|
if (value) {
|
||||||
params.set(name, value);
|
params.set(name, value);
|
||||||
@@ -32,7 +33,9 @@ export function SearchSidebar() {
|
|||||||
// If we are not on explore/trends/recent, maybe we should redirect to home?
|
// If we are not on explore/trends/recent, maybe we should redirect to home?
|
||||||
// For now, let's just update the URL.
|
// For now, let's just update the URL.
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
router.push(`${pathname}?${params.toString()}`);
|
||||||
}, [router, pathname, searchParams]);
|
},
|
||||||
|
[router, pathname, searchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -113,16 +116,20 @@ export function SearchSidebar() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium mb-3">Tags populaires</h3>
|
<h3 className="text-sm font-medium mb-3">Tags populaires</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{["funny", "coding", "cat", "dog", "work", "relatable", "gaming"].map(tag => (
|
{["funny", "coding", "cat", "dog", "work", "relatable", "gaming"].map(
|
||||||
|
(tag) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={tag}
|
key={tag}
|
||||||
variant={searchParams.get("tag") === tag ? "default" : "outline"}
|
variant={searchParams.get("tag") === tag ? "default" : "outline"}
|
||||||
className="cursor-pointer hover:bg-secondary"
|
className="cursor-pointer hover:bg-secondary"
|
||||||
onClick={() => updateSearch("tag", searchParams.get("tag") === tag ? null : tag)}
|
onClick={() =>
|
||||||
|
updateSearch("tag", searchParams.get("tag") === tag ? null : tag)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
#{tag}
|
#{tag}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Accordion({
|
function Accordion({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccordionItem({
|
function AccordionItem({
|
||||||
@@ -22,7 +22,7 @@ function AccordionItem({
|
|||||||
className={cn("border-b last:border-b-0", className)}
|
className={cn("border-b last:border-b-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccordionTrigger({
|
function AccordionTrigger({
|
||||||
@@ -36,7 +36,7 @@ function AccordionTrigger({
|
|||||||
data-slot="accordion-trigger"
|
data-slot="accordion-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -44,7 +44,7 @@ function AccordionTrigger({
|
|||||||
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccordionContent({
|
function AccordionContent({
|
||||||
@@ -60,7 +60,7 @@ function AccordionContent({
|
|||||||
>
|
>
|
||||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||||
</AccordionPrimitive.Content>
|
</AccordionPrimitive.Content>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
import type * as React from "react";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from "@/components/ui/button"
|
|
||||||
|
|
||||||
function AlertDialog({
|
function AlertDialog({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogTrigger({
|
function AlertDialogTrigger({
|
||||||
@@ -17,7 +16,7 @@ function AlertDialogTrigger({
|
|||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogPortal({
|
function AlertDialogPortal({
|
||||||
@@ -25,7 +24,7 @@ function AlertDialogPortal({
|
|||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogOverlay({
|
function AlertDialogOverlay({
|
||||||
@@ -37,11 +36,11 @@ function AlertDialogOverlay({
|
|||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogContent({
|
function AlertDialogContent({
|
||||||
@@ -55,12 +54,12 @@ function AlertDialogContent({
|
|||||||
data-slot="alert-dialog-content"
|
data-slot="alert-dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</AlertDialogPortal>
|
</AlertDialogPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogHeader({
|
function AlertDialogHeader({
|
||||||
@@ -73,7 +72,7 @@ function AlertDialogHeader({
|
|||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogFooter({
|
function AlertDialogFooter({
|
||||||
@@ -85,11 +84,11 @@ function AlertDialogFooter({
|
|||||||
data-slot="alert-dialog-footer"
|
data-slot="alert-dialog-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogTitle({
|
function AlertDialogTitle({
|
||||||
@@ -102,7 +101,7 @@ function AlertDialogTitle({
|
|||||||
className={cn("text-lg font-semibold", className)}
|
className={cn("text-lg font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogDescription({
|
function AlertDialogDescription({
|
||||||
@@ -115,7 +114,7 @@ function AlertDialogDescription({
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogAction({
|
function AlertDialogAction({
|
||||||
@@ -127,7 +126,7 @@ function AlertDialogAction({
|
|||||||
className={cn(buttonVariants(), className)}
|
className={cn(buttonVariants(), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogCancel({
|
function AlertDialogCancel({
|
||||||
@@ -139,7 +138,7 @@ function AlertDialogCancel({
|
|||||||
className={cn(buttonVariants({ variant: "outline" }), className)}
|
className={cn(buttonVariants({ variant: "outline" }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -154,4 +153,4 @@ export {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
AlertDialogCancel,
|
AlertDialogCancel,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||||
@@ -16,8 +16,8 @@ const alertVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Alert({
|
function Alert({
|
||||||
className,
|
className,
|
||||||
@@ -31,7 +31,7 @@ function Alert({
|
|||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDescription({
|
function AlertDescription({
|
||||||
@@ -56,11 +56,11 @@ function AlertDescription({
|
|||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription }
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
|
||||||
|
|
||||||
function AspectRatio({
|
function AspectRatio({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
||||||
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
|
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AspectRatio }
|
export { AspectRatio };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Avatar({
|
function Avatar({
|
||||||
className,
|
className,
|
||||||
@@ -14,11 +14,11 @@ function Avatar({
|
|||||||
data-slot="avatar"
|
data-slot="avatar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarImage({
|
function AvatarImage({
|
||||||
@@ -31,7 +31,7 @@ function AvatarImage({
|
|||||||
className={cn("aspect-square size-full", className)}
|
className={cn("aspect-square size-full", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarFallback({
|
function AvatarFallback({
|
||||||
@@ -43,11 +43,11 @@ function AvatarFallback({
|
|||||||
data-slot="avatar-fallback"
|
data-slot="avatar-fallback"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback }
|
export { Avatar, AvatarImage, AvatarFallback };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-full 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",
|
"inline-flex items-center justify-center rounded-full 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",
|
||||||
@@ -22,8 +22,8 @@ const badgeVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Badge({
|
function Badge({
|
||||||
className,
|
className,
|
||||||
@@ -32,7 +32,7 @@ function Badge({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span"> &
|
}: React.ComponentProps<"span"> &
|
||||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "span"
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -40,7 +40,7 @@ function Badge({
|
|||||||
className={cn(badgeVariants({ variant }), className)}
|
className={cn(badgeVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
export { Badge, badgeVariants };
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import * as React from "react"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
@@ -14,11 +14,11 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
|||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
@@ -28,7 +28,7 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
|||||||
className={cn("inline-flex items-center gap-1.5", className)}
|
className={cn("inline-flex items-center gap-1.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbLink({
|
function BreadcrumbLink({
|
||||||
@@ -36,9 +36,9 @@ function BreadcrumbLink({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"a"> & {
|
}: React.ComponentProps<"a"> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "a"
|
const Comp = asChild ? Slot : "a";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -46,7 +46,7 @@ function BreadcrumbLink({
|
|||||||
className={cn("hover:text-foreground transition-colors", className)}
|
className={cn("hover:text-foreground transition-colors", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -59,7 +59,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
className={cn("text-foreground font-normal", className)}
|
className={cn("text-foreground font-normal", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbSeparator({
|
function BreadcrumbSeparator({
|
||||||
@@ -77,7 +77,7 @@ function BreadcrumbSeparator({
|
|||||||
>
|
>
|
||||||
{children ?? <ChevronRight />}
|
{children ?? <ChevronRight />}
|
||||||
</li>
|
</li>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbEllipsis({
|
function BreadcrumbEllipsis({
|
||||||
@@ -95,7 +95,7 @@ function BreadcrumbEllipsis({
|
|||||||
<MoreHorizontal className="size-4" />
|
<MoreHorizontal className="size-4" />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">More</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -106,4 +106,4 @@ export {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
|
|
||||||
const buttonGroupVariants = cva(
|
const buttonGroupVariants = cva(
|
||||||
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
||||||
@@ -18,8 +17,8 @@ const buttonGroupVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
orientation: "horizontal",
|
orientation: "horizontal",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function ButtonGroup({
|
function ButtonGroup({
|
||||||
className,
|
className,
|
||||||
@@ -34,7 +33,7 @@ function ButtonGroup({
|
|||||||
className={cn(buttonGroupVariants({ orientation }), className)}
|
className={cn(buttonGroupVariants({ orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupText({
|
function ButtonGroupText({
|
||||||
@@ -42,19 +41,19 @@ function ButtonGroupText({
|
|||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "div"
|
const Comp = asChild ? Slot : "div";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupSeparator({
|
function ButtonGroupSeparator({
|
||||||
@@ -68,11 +67,11 @@ function ButtonGroupSeparator({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -80,4 +79,4 @@ export {
|
|||||||
ButtonGroupSeparator,
|
ButtonGroupSeparator,
|
||||||
ButtonGroupText,
|
ButtonGroupText,
|
||||||
buttonGroupVariants,
|
buttonGroupVariants,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-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",
|
"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",
|
||||||
@@ -14,8 +14,7 @@ const buttonVariants = cva(
|
|||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
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",
|
"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:
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
@@ -33,8 +32,8 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
@@ -44,9 +43,9 @@ function Button({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -56,7 +55,7 @@ function Button({
|
|||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
|
type DayButton,
|
||||||
DayPicker,
|
DayPicker,
|
||||||
getDefaultClassNames,
|
getDefaultClassNames,
|
||||||
type DayButton,
|
} from "react-day-picker";
|
||||||
} from "react-day-picker"
|
import { Button, buttonVariants } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
@@ -25,9 +24,9 @@ function Calendar({
|
|||||||
components,
|
components,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayPicker> & {
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
|
||||||
}) {
|
}) {
|
||||||
const defaultClassNames = getDefaultClassNames()
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DayPicker
|
<DayPicker
|
||||||
@@ -36,7 +35,7 @@ function Calendar({
|
|||||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
formatters={{
|
formatters={{
|
||||||
@@ -48,85 +47,82 @@ function Calendar({
|
|||||||
root: cn("w-fit", defaultClassNames.root),
|
root: cn("w-fit", defaultClassNames.root),
|
||||||
months: cn(
|
months: cn(
|
||||||
"flex gap-4 flex-col md:flex-row relative",
|
"flex gap-4 flex-col md:flex-row relative",
|
||||||
defaultClassNames.months
|
defaultClassNames.months,
|
||||||
),
|
),
|
||||||
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
|
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
|
||||||
nav: cn(
|
nav: cn(
|
||||||
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
|
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
|
||||||
defaultClassNames.nav
|
defaultClassNames.nav,
|
||||||
),
|
),
|
||||||
button_previous: cn(
|
button_previous: cn(
|
||||||
buttonVariants({ variant: buttonVariant }),
|
buttonVariants({ variant: buttonVariant }),
|
||||||
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
||||||
defaultClassNames.button_previous
|
defaultClassNames.button_previous,
|
||||||
),
|
),
|
||||||
button_next: cn(
|
button_next: cn(
|
||||||
buttonVariants({ variant: buttonVariant }),
|
buttonVariants({ variant: buttonVariant }),
|
||||||
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
||||||
defaultClassNames.button_next
|
defaultClassNames.button_next,
|
||||||
),
|
),
|
||||||
month_caption: cn(
|
month_caption: cn(
|
||||||
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
|
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
|
||||||
defaultClassNames.month_caption
|
defaultClassNames.month_caption,
|
||||||
),
|
),
|
||||||
dropdowns: cn(
|
dropdowns: cn(
|
||||||
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
|
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
|
||||||
defaultClassNames.dropdowns
|
defaultClassNames.dropdowns,
|
||||||
),
|
),
|
||||||
dropdown_root: cn(
|
dropdown_root: cn(
|
||||||
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
|
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
|
||||||
defaultClassNames.dropdown_root
|
defaultClassNames.dropdown_root,
|
||||||
),
|
),
|
||||||
dropdown: cn(
|
dropdown: cn(
|
||||||
"absolute bg-popover inset-0 opacity-0",
|
"absolute bg-popover inset-0 opacity-0",
|
||||||
defaultClassNames.dropdown
|
defaultClassNames.dropdown,
|
||||||
),
|
),
|
||||||
caption_label: cn(
|
caption_label: cn(
|
||||||
"select-none font-medium",
|
"select-none font-medium",
|
||||||
captionLayout === "label"
|
captionLayout === "label"
|
||||||
? "text-sm"
|
? "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",
|
: "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
|
defaultClassNames.caption_label,
|
||||||
),
|
),
|
||||||
table: "w-full border-collapse",
|
table: "w-full border-collapse",
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
||||||
defaultClassNames.weekday
|
defaultClassNames.weekday,
|
||||||
),
|
),
|
||||||
week: cn("flex w-full mt-2", defaultClassNames.week),
|
week: cn("flex w-full mt-2", defaultClassNames.week),
|
||||||
week_number_header: cn(
|
week_number_header: cn(
|
||||||
"select-none w-(--cell-size)",
|
"select-none w-(--cell-size)",
|
||||||
defaultClassNames.week_number_header
|
defaultClassNames.week_number_header,
|
||||||
),
|
),
|
||||||
week_number: cn(
|
week_number: cn(
|
||||||
"text-[0.8rem] select-none text-muted-foreground",
|
"text-[0.8rem] select-none text-muted-foreground",
|
||||||
defaultClassNames.week_number
|
defaultClassNames.week_number,
|
||||||
),
|
),
|
||||||
day: cn(
|
day: cn(
|
||||||
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
||||||
props.showWeekNumber
|
props.showWeekNumber
|
||||||
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
||||||
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
||||||
defaultClassNames.day
|
defaultClassNames.day,
|
||||||
),
|
|
||||||
range_start: cn(
|
|
||||||
"rounded-l-md bg-accent",
|
|
||||||
defaultClassNames.range_start
|
|
||||||
),
|
),
|
||||||
|
range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
|
||||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||||
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
|
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
|
||||||
today: cn(
|
today: cn(
|
||||||
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
||||||
defaultClassNames.today
|
defaultClassNames.today,
|
||||||
),
|
),
|
||||||
outside: cn(
|
outside: cn(
|
||||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||||
defaultClassNames.outside
|
defaultClassNames.outside,
|
||||||
),
|
),
|
||||||
disabled: cn(
|
disabled: cn(
|
||||||
"text-muted-foreground opacity-50",
|
"text-muted-foreground opacity-50",
|
||||||
defaultClassNames.disabled
|
defaultClassNames.disabled,
|
||||||
),
|
),
|
||||||
hidden: cn("invisible", defaultClassNames.hidden),
|
hidden: cn("invisible", defaultClassNames.hidden),
|
||||||
...classNames,
|
...classNames,
|
||||||
@@ -140,27 +136,20 @@ function Calendar({
|
|||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
Chevron: ({ className, orientation, ...props }) => {
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
if (orientation === "left") {
|
if (orientation === "left") {
|
||||||
return (
|
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />;
|
||||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orientation === "right") {
|
if (orientation === "right") {
|
||||||
return (
|
return (
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon className={cn("size-4", className)} {...props} />
|
||||||
className={cn("size-4", className)}
|
);
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <ChevronDownIcon className={cn("size-4", className)} {...props} />;
|
||||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
DayButton: CalendarDayButton,
|
DayButton: CalendarDayButton,
|
||||||
WeekNumber: ({ children, ...props }) => {
|
WeekNumber: ({ children, ...props }) => {
|
||||||
@@ -170,13 +159,13 @@ function Calendar({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
...components,
|
...components,
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CalendarDayButton({
|
function CalendarDayButton({
|
||||||
@@ -185,12 +174,12 @@ function CalendarDayButton({
|
|||||||
modifiers,
|
modifiers,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayButton>) {
|
}: React.ComponentProps<typeof DayButton>) {
|
||||||
const defaultClassNames = getDefaultClassNames()
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
const ref = React.useRef<HTMLButtonElement>(null)
|
const ref = React.useRef<HTMLButtonElement>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (modifiers.focused) ref.current?.focus()
|
if (modifiers.focused) ref.current?.focus();
|
||||||
}, [modifiers.focused])
|
}, [modifiers.focused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -210,11 +199,11 @@ function CalendarDayButton({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
|
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
|
||||||
defaultClassNames.day,
|
defaultClassNames.day,
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Calendar, CalendarDayButton }
|
export { Calendar, CalendarDayButton };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 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-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("leading-none font-semibold", className)}
|
className={cn("leading-none font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -54,21 +54,17 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-action"
|
data-slot="card-action"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div data-slot="card-content" className={cn("px-6", className)} {...props} />
|
||||||
data-slot="card-content"
|
);
|
||||||
className={cn("px-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -78,7 +74,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
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}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -89,4 +85,4 @@ export {
|
|||||||
CardAction,
|
CardAction,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react"
|
} from "embla-carousel-react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
type CarouselApi = UseEmblaCarouselType[1];
|
||||||
import { Button } from "@/components/ui/button"
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||||
|
type CarouselOptions = UseCarouselParameters[0];
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselPlugin = UseCarouselParameters[1];
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
|
||||||
type CarouselOptions = UseCarouselParameters[0]
|
|
||||||
type CarouselPlugin = UseCarouselParameters[1]
|
|
||||||
|
|
||||||
type CarouselProps = {
|
type CarouselProps = {
|
||||||
opts?: CarouselOptions
|
opts?: CarouselOptions;
|
||||||
plugins?: CarouselPlugin
|
plugins?: CarouselPlugin;
|
||||||
orientation?: "horizontal" | "vertical"
|
orientation?: "horizontal" | "vertical";
|
||||||
setApi?: (api: CarouselApi) => void
|
setApi?: (api: CarouselApi) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
type CarouselContextProps = {
|
type CarouselContextProps = {
|
||||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
||||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
api: ReturnType<typeof useEmblaCarousel>[1];
|
||||||
scrollPrev: () => void
|
scrollPrev: () => void;
|
||||||
scrollNext: () => void
|
scrollNext: () => void;
|
||||||
canScrollPrev: boolean
|
canScrollPrev: boolean;
|
||||||
canScrollNext: boolean
|
canScrollNext: boolean;
|
||||||
} & CarouselProps
|
} & CarouselProps;
|
||||||
|
|
||||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
||||||
|
|
||||||
function useCarousel() {
|
function useCarousel() {
|
||||||
const context = React.useContext(CarouselContext)
|
const context = React.useContext(CarouselContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useCarousel must be used within a <Carousel />")
|
throw new Error("useCarousel must be used within a <Carousel />");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Carousel({
|
function Carousel({
|
||||||
@@ -56,53 +55,53 @@ function Carousel({
|
|||||||
...opts,
|
...opts,
|
||||||
axis: orientation === "horizontal" ? "x" : "y",
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
},
|
},
|
||||||
plugins
|
plugins,
|
||||||
)
|
);
|
||||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||||
|
|
||||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
if (!api) return
|
if (!api) return;
|
||||||
setCanScrollPrev(api.canScrollPrev())
|
setCanScrollPrev(api.canScrollPrev());
|
||||||
setCanScrollNext(api.canScrollNext())
|
setCanScrollNext(api.canScrollNext());
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const scrollPrev = React.useCallback(() => {
|
const scrollPrev = React.useCallback(() => {
|
||||||
api?.scrollPrev()
|
api?.scrollPrev();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const scrollNext = React.useCallback(() => {
|
const scrollNext = React.useCallback(() => {
|
||||||
api?.scrollNext()
|
api?.scrollNext();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const handleKeyDown = React.useCallback(
|
const handleKeyDown = React.useCallback(
|
||||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === "ArrowLeft") {
|
if (event.key === "ArrowLeft") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollPrev()
|
scrollPrev();
|
||||||
} else if (event.key === "ArrowRight") {
|
} else if (event.key === "ArrowRight") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollNext()
|
scrollNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollPrev, scrollNext]
|
[scrollPrev, scrollNext],
|
||||||
)
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api || !setApi) return
|
if (!api || !setApi) return;
|
||||||
setApi(api)
|
setApi(api);
|
||||||
}, [api, setApi])
|
}, [api, setApi]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api) return
|
if (!api) return;
|
||||||
onSelect(api)
|
onSelect(api);
|
||||||
api.on("reInit", onSelect)
|
api.on("reInit", onSelect);
|
||||||
api.on("select", onSelect)
|
api.on("select", onSelect);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
api?.off("select", onSelect)
|
api?.off("select", onSelect);
|
||||||
}
|
};
|
||||||
}, [api, onSelect])
|
}, [api, onSelect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarouselContext.Provider
|
<CarouselContext.Provider
|
||||||
@@ -129,11 +128,11 @@ function Carousel({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CarouselContext.Provider>
|
</CarouselContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { carouselRef, orientation } = useCarousel()
|
const { carouselRef, orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -145,16 +144,16 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex",
|
"flex",
|
||||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { orientation } = useCarousel()
|
const { orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -164,11 +163,11 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 shrink-0 grow-0 basis-full",
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselPrevious({
|
function CarouselPrevious({
|
||||||
@@ -177,7 +176,7 @@ function CarouselPrevious({
|
|||||||
size = "icon",
|
size = "icon",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -189,7 +188,7 @@ function CarouselPrevious({
|
|||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -left-12 -translate-y-1/2"
|
? "top-1/2 -left-12 -translate-y-1/2"
|
||||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollPrev}
|
disabled={!canScrollPrev}
|
||||||
onClick={scrollPrev}
|
onClick={scrollPrev}
|
||||||
@@ -198,7 +197,7 @@ function CarouselPrevious({
|
|||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselNext({
|
function CarouselNext({
|
||||||
@@ -207,7 +206,7 @@ function CarouselNext({
|
|||||||
size = "icon",
|
size = "icon",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -219,7 +218,7 @@ function CarouselNext({
|
|||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -right-12 -translate-y-1/2"
|
? "top-1/2 -right-12 -translate-y-1/2"
|
||||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollNext}
|
disabled={!canScrollNext}
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
@@ -228,7 +227,7 @@ function CarouselNext({
|
|||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -238,4 +237,4 @@ export {
|
|||||||
CarouselItem,
|
CarouselItem,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as RechartsPrimitive from "recharts"
|
import * as RechartsPrimitive from "recharts";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
const THEMES = { light: "", dark: ".dark" } as const
|
const THEMES = { light: "", dark: ".dark" } as const;
|
||||||
|
|
||||||
export type ChartConfig = {
|
export type ChartConfig = {
|
||||||
[k in string]: {
|
[k in string]: {
|
||||||
label?: React.ReactNode
|
label?: React.ReactNode;
|
||||||
icon?: React.ComponentType
|
icon?: React.ComponentType;
|
||||||
} & (
|
} & (
|
||||||
| { color?: string; theme?: never }
|
| { color?: string; theme?: never }
|
||||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type ChartContextProps = {
|
type ChartContextProps = {
|
||||||
config: ChartConfig
|
config: ChartConfig;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
||||||
|
|
||||||
function useChart() {
|
function useChart() {
|
||||||
const context = React.useContext(ChartContext)
|
const context = React.useContext(ChartContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useChart must be used within a <ChartContainer />")
|
throw new Error("useChart must be used within a <ChartContainer />");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartContainer({
|
function ChartContainer({
|
||||||
@@ -41,13 +41,13 @@ function ChartContainer({
|
|||||||
config,
|
config,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
config: ChartConfig
|
config: ChartConfig;
|
||||||
children: React.ComponentProps<
|
children: React.ComponentProps<
|
||||||
typeof RechartsPrimitive.ResponsiveContainer
|
typeof RechartsPrimitive.ResponsiveContainer
|
||||||
>["children"]
|
>["children"];
|
||||||
}) {
|
}) {
|
||||||
const uniqueId = React.useId()
|
const uniqueId = React.useId();
|
||||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartContext.Provider value={{ config }}>
|
<ChartContext.Provider value={{ config }}>
|
||||||
@@ -56,7 +56,7 @@ function ChartContainer({
|
|||||||
data-chart={chartId}
|
data-chart={chartId}
|
||||||
className={cn(
|
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-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
"[&_.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-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -66,20 +66,21 @@ function ChartContainer({
|
|||||||
</RechartsPrimitive.ResponsiveContainer>
|
</RechartsPrimitive.ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
</ChartContext.Provider>
|
</ChartContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||||
const colorConfig = Object.entries(config).filter(
|
const colorConfig = Object.entries(config).filter(
|
||||||
([, config]) => config.theme || config.color
|
([, config]) => config.theme || config.color,
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!colorConfig.length) {
|
if (!colorConfig.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<style
|
<style
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: no comment...
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: Object.entries(THEMES)
|
__html: Object.entries(THEMES)
|
||||||
.map(
|
.map(
|
||||||
@@ -89,20 +90,20 @@ ${colorConfig
|
|||||||
.map(([key, itemConfig]) => {
|
.map(([key, itemConfig]) => {
|
||||||
const color =
|
const color =
|
||||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||||
itemConfig.color
|
itemConfig.color;
|
||||||
return color ? ` --color-${key}: ${color};` : null
|
return color ? ` --color-${key}: ${color};` : null;
|
||||||
})
|
})
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
const ChartTooltip = RechartsPrimitive.Tooltip;
|
||||||
|
|
||||||
function ChartTooltipContent({
|
function ChartTooltipContent({
|
||||||
active,
|
active,
|
||||||
@@ -120,40 +121,40 @@ function ChartTooltipContent({
|
|||||||
labelKey,
|
labelKey,
|
||||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||||
React.ComponentProps<"div"> & {
|
React.ComponentProps<"div"> & {
|
||||||
hideLabel?: boolean
|
hideLabel?: boolean;
|
||||||
hideIndicator?: boolean
|
hideIndicator?: boolean;
|
||||||
indicator?: "line" | "dot" | "dashed"
|
indicator?: "line" | "dot" | "dashed";
|
||||||
nameKey?: string
|
nameKey?: string;
|
||||||
labelKey?: string
|
labelKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const { config } = useChart()
|
const { config } = useChart();
|
||||||
|
|
||||||
const tooltipLabel = React.useMemo(() => {
|
const tooltipLabel = React.useMemo(() => {
|
||||||
if (hideLabel || !payload?.length) {
|
if (hideLabel || !payload?.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [item] = payload
|
const [item] = payload;
|
||||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
const value =
|
const value =
|
||||||
!labelKey && typeof label === "string"
|
!labelKey && typeof label === "string"
|
||||||
? config[label as keyof typeof config]?.label || label
|
? config[label as keyof typeof config]?.label || label
|
||||||
: itemConfig?.label
|
: itemConfig?.label;
|
||||||
|
|
||||||
if (labelFormatter) {
|
if (labelFormatter) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("font-medium", labelClassName)}>
|
<div className={cn("font-medium", labelClassName)}>
|
||||||
{labelFormatter(value, payload)}
|
{labelFormatter(value, payload)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
|
||||||
}, [
|
}, [
|
||||||
label,
|
label,
|
||||||
labelFormatter,
|
labelFormatter,
|
||||||
@@ -162,19 +163,19 @@ function ChartTooltipContent({
|
|||||||
labelClassName,
|
labelClassName,
|
||||||
config,
|
config,
|
||||||
labelKey,
|
labelKey,
|
||||||
])
|
]);
|
||||||
|
|
||||||
if (!active || !payload?.length) {
|
if (!active || !payload?.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
const nestLabel = payload.length === 1 && indicator !== "dot";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!nestLabel ? tooltipLabel : null}
|
{!nestLabel ? tooltipLabel : null}
|
||||||
@@ -182,16 +183,16 @@ function ChartTooltipContent({
|
|||||||
{payload
|
{payload
|
||||||
.filter((item) => item.type !== "none")
|
.filter((item) => item.type !== "none")
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
const indicatorColor = color || item.payload.fill || item.color
|
const indicatorColor = color || item.payload.fill || item.color;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.dataKey}
|
key={item.dataKey}
|
||||||
className={cn(
|
className={cn(
|
||||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||||
indicator === "dot" && "items-center"
|
indicator === "dot" && "items-center",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{formatter && item?.value !== undefined && item.name ? (
|
{formatter && item?.value !== undefined && item.name ? (
|
||||||
@@ -211,7 +212,7 @@ function ChartTooltipContent({
|
|||||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||||
indicator === "dashed",
|
indicator === "dashed",
|
||||||
"my-0.5": nestLabel && indicator === "dashed",
|
"my-0.5": nestLabel && indicator === "dashed",
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
@@ -225,7 +226,7 @@ function ChartTooltipContent({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 justify-between leading-none",
|
"flex flex-1 justify-between leading-none",
|
||||||
nestLabel ? "items-end" : "items-center"
|
nestLabel ? "items-end" : "items-center",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="grid gap-1.5">
|
<div className="grid gap-1.5">
|
||||||
@@ -243,14 +244,14 @@ function ChartTooltipContent({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChartLegend = RechartsPrimitive.Legend
|
const ChartLegend = RechartsPrimitive.Legend;
|
||||||
|
|
||||||
function ChartLegendContent({
|
function ChartLegendContent({
|
||||||
className,
|
className,
|
||||||
@@ -260,13 +261,13 @@ function ChartLegendContent({
|
|||||||
nameKey,
|
nameKey,
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||||
hideIcon?: boolean
|
hideIcon?: boolean;
|
||||||
nameKey?: string
|
nameKey?: string;
|
||||||
}) {
|
}) {
|
||||||
const { config } = useChart()
|
const { config } = useChart();
|
||||||
|
|
||||||
if (!payload?.length) {
|
if (!payload?.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -274,20 +275,20 @@ function ChartLegendContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-4",
|
"flex items-center justify-center gap-4",
|
||||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{payload
|
{payload
|
||||||
.filter((item) => item.type !== "none")
|
.filter((item) => item.type !== "none")
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const key = `${nameKey || item.dataKey || "value"}`
|
const key = `${nameKey || item.dataKey || "value"}`;
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.value}
|
key={item.value}
|
||||||
className={cn(
|
className={cn(
|
||||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{itemConfig?.icon && !hideIcon ? (
|
{itemConfig?.icon && !hideIcon ? (
|
||||||
@@ -302,20 +303,20 @@ function ChartLegendContent({
|
|||||||
)}
|
)}
|
||||||
{itemConfig?.label}
|
{itemConfig?.label}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to extract item config from a payload.
|
// Helper to extract item config from a payload.
|
||||||
function getPayloadConfigFromPayload(
|
function getPayloadConfigFromPayload(
|
||||||
config: ChartConfig,
|
config: ChartConfig,
|
||||||
payload: unknown,
|
payload: unknown,
|
||||||
key: string
|
key: string,
|
||||||
) {
|
) {
|
||||||
if (typeof payload !== "object" || payload === null) {
|
if (typeof payload !== "object" || payload === null) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payloadPayload =
|
const payloadPayload =
|
||||||
@@ -323,28 +324,26 @@ function getPayloadConfigFromPayload(
|
|||||||
typeof payload.payload === "object" &&
|
typeof payload.payload === "object" &&
|
||||||
payload.payload !== null
|
payload.payload !== null
|
||||||
? payload.payload
|
? payload.payload
|
||||||
: undefined
|
: undefined;
|
||||||
|
|
||||||
let configLabelKey: string = key
|
let configLabelKey: string = key;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
key in payload &&
|
key in payload &&
|
||||||
typeof payload[key as keyof typeof payload] === "string"
|
typeof payload[key as keyof typeof payload] === "string"
|
||||||
) {
|
) {
|
||||||
configLabelKey = payload[key as keyof typeof payload] as string
|
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||||
} else if (
|
} else if (
|
||||||
payloadPayload &&
|
payloadPayload &&
|
||||||
key in payloadPayload &&
|
key in payloadPayload &&
|
||||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||||
) {
|
) {
|
||||||
configLabelKey = payloadPayload[
|
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
|
||||||
key as keyof typeof payloadPayload
|
|
||||||
] as string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return configLabelKey in config
|
return configLabelKey in config
|
||||||
? config[configLabelKey]
|
? config[configLabelKey]
|
||||||
: config[key as keyof typeof config]
|
: config[key as keyof typeof config];
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -354,4 +353,4 @@ export {
|
|||||||
ChartLegend,
|
ChartLegend,
|
||||||
ChartLegendContent,
|
ChartLegendContent,
|
||||||
ChartStyle,
|
ChartStyle,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
import { CheckIcon } from "lucide-react";
|
||||||
import { CheckIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Checkbox({
|
function Checkbox({
|
||||||
className,
|
className,
|
||||||
@@ -15,7 +15,7 @@ function Checkbox({
|
|||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"peer 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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,7 @@ function Checkbox({
|
|||||||
<CheckIcon className="size-3.5" />
|
<CheckIcon className="size-3.5" />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Checkbox }
|
export { Checkbox };
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
||||||
|
|
||||||
function Collapsible({
|
function Collapsible({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleTrigger({
|
function CollapsibleTrigger({
|
||||||
@@ -16,7 +16,7 @@ function CollapsibleTrigger({
|
|||||||
data-slot="collapsible-trigger"
|
data-slot="collapsible-trigger"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleContent({
|
function CollapsibleContent({
|
||||||
@@ -27,7 +27,7 @@ function CollapsibleContent({
|
|||||||
data-slot="collapsible-content"
|
data-slot="collapsible-content"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { SearchIcon } from "lucide-react";
|
||||||
import { SearchIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Command({
|
function Command({
|
||||||
className,
|
className,
|
||||||
@@ -22,11 +21,11 @@ function Command({
|
|||||||
data-slot="command"
|
data-slot="command"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandDialog({
|
function CommandDialog({
|
||||||
@@ -37,10 +36,10 @@ function CommandDialog({
|
|||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Dialog> & {
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
title?: string
|
title?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
className?: string
|
className?: string;
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
@@ -57,7 +56,7 @@ function CommandDialog({
|
|||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandInput({
|
function CommandInput({
|
||||||
@@ -74,12 +73,12 @@ function CommandInput({
|
|||||||
data-slot="command-input"
|
data-slot="command-input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandList({
|
function CommandList({
|
||||||
@@ -91,11 +90,11 @@ function CommandList({
|
|||||||
data-slot="command-list"
|
data-slot="command-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandEmpty({
|
function CommandEmpty({
|
||||||
@@ -107,7 +106,7 @@ function CommandEmpty({
|
|||||||
className="py-6 text-center text-sm"
|
className="py-6 text-center text-sm"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandGroup({
|
function CommandGroup({
|
||||||
@@ -119,11 +118,11 @@ function CommandGroup({
|
|||||||
data-slot="command-group"
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandSeparator({
|
function CommandSeparator({
|
||||||
@@ -136,7 +135,7 @@ function CommandSeparator({
|
|||||||
className={cn("bg-border -mx-1 h-px", className)}
|
className={cn("bg-border -mx-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandItem({
|
function CommandItem({
|
||||||
@@ -148,11 +147,11 @@ function CommandItem({
|
|||||||
data-slot="command-item"
|
data-slot="command-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandShortcut({
|
function CommandShortcut({
|
||||||
@@ -164,11 +163,11 @@ function CommandShortcut({
|
|||||||
data-slot="command-shortcut"
|
data-slot="command-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -181,4 +180,4 @@ export {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function ContextMenu({
|
function ContextMenu({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuTrigger({
|
function ContextMenuTrigger({
|
||||||
@@ -17,7 +17,7 @@ function ContextMenuTrigger({
|
|||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuGroup({
|
function ContextMenuGroup({
|
||||||
@@ -25,7 +25,7 @@ function ContextMenuGroup({
|
|||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuPortal({
|
function ContextMenuPortal({
|
||||||
@@ -33,13 +33,13 @@ function ContextMenuPortal({
|
|||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuSub({
|
function ContextMenuSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuRadioGroup({
|
function ContextMenuRadioGroup({
|
||||||
@@ -50,7 +50,7 @@ function ContextMenuRadioGroup({
|
|||||||
data-slot="context-menu-radio-group"
|
data-slot="context-menu-radio-group"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuSubTrigger({
|
function ContextMenuSubTrigger({
|
||||||
@@ -59,7 +59,7 @@ function ContextMenuSubTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.SubTrigger
|
<ContextMenuPrimitive.SubTrigger
|
||||||
@@ -67,14 +67,14 @@ function ContextMenuSubTrigger({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronRightIcon className="ml-auto" />
|
<ChevronRightIcon className="ml-auto" />
|
||||||
</ContextMenuPrimitive.SubTrigger>
|
</ContextMenuPrimitive.SubTrigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuSubContent({
|
function ContextMenuSubContent({
|
||||||
@@ -86,11 +86,11 @@ function ContextMenuSubContent({
|
|||||||
data-slot="context-menu-sub-content"
|
data-slot="context-menu-sub-content"
|
||||||
className={cn(
|
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-context-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-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuContent({
|
function ContextMenuContent({
|
||||||
@@ -103,12 +103,12 @@ function ContextMenuContent({
|
|||||||
data-slot="context-menu-content"
|
data-slot="context-menu-content"
|
||||||
className={cn(
|
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-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-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-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</ContextMenuPrimitive.Portal>
|
</ContextMenuPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuItem({
|
function ContextMenuItem({
|
||||||
@@ -117,8 +117,8 @@ function ContextMenuItem({
|
|||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
variant?: "default" | "destructive"
|
variant?: "default" | "destructive";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Item
|
<ContextMenuPrimitive.Item
|
||||||
@@ -127,11 +127,11 @@ function ContextMenuItem({
|
|||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuCheckboxItem({
|
function ContextMenuCheckboxItem({
|
||||||
@@ -145,7 +145,7 @@ function ContextMenuCheckboxItem({
|
|||||||
data-slot="context-menu-checkbox-item"
|
data-slot="context-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -157,7 +157,7 @@ function ContextMenuCheckboxItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.CheckboxItem>
|
</ContextMenuPrimitive.CheckboxItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuRadioItem({
|
function ContextMenuRadioItem({
|
||||||
@@ -170,7 +170,7 @@ function ContextMenuRadioItem({
|
|||||||
data-slot="context-menu-radio-item"
|
data-slot="context-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -181,7 +181,7 @@ function ContextMenuRadioItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.RadioItem>
|
</ContextMenuPrimitive.RadioItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuLabel({
|
function ContextMenuLabel({
|
||||||
@@ -189,7 +189,7 @@ function ContextMenuLabel({
|
|||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Label
|
<ContextMenuPrimitive.Label
|
||||||
@@ -197,11 +197,11 @@ function ContextMenuLabel({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuSeparator({
|
function ContextMenuSeparator({
|
||||||
@@ -214,7 +214,7 @@ function ContextMenuSeparator({
|
|||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuShortcut({
|
function ContextMenuShortcut({
|
||||||
@@ -226,11 +226,11 @@ function ContextMenuShortcut({
|
|||||||
data-slot="context-menu-shortcut"
|
data-slot="context-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -249,4 +249,4 @@ export {
|
|||||||
ContextMenuSubContent,
|
ContextMenuSubContent,
|
||||||
ContextMenuSubTrigger,
|
ContextMenuSubTrigger,
|
||||||
ContextMenuRadioGroup,
|
ContextMenuRadioGroup,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import { XIcon } from "lucide-react";
|
||||||
import { XIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Dialog({
|
function Dialog({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTrigger({
|
function DialogTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogPortal({
|
function DialogPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogClose({
|
function DialogClose({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogOverlay({
|
function DialogOverlay({
|
||||||
@@ -39,11 +39,11 @@ function DialogOverlay({
|
|||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogContent({
|
function DialogContent({
|
||||||
@@ -52,7 +52,7 @@ function DialogContent({
|
|||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DialogPortal data-slot="dialog-portal">
|
<DialogPortal data-slot="dialog-portal">
|
||||||
@@ -61,7 +61,7 @@ function DialogContent({
|
|||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -77,7 +77,7 @@ function DialogContent({
|
|||||||
)}
|
)}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -87,7 +87,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -96,11 +96,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="dialog-footer"
|
data-slot="dialog-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTitle({
|
function DialogTitle({
|
||||||
@@ -113,7 +113,7 @@ function DialogTitle({
|
|||||||
className={cn("text-lg leading-none font-semibold", className)}
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogDescription({
|
function DialogDescription({
|
||||||
@@ -126,7 +126,7 @@ function DialogDescription({
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -140,4 +140,4 @@ export {
|
|||||||
DialogPortal,
|
DialogPortal,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import type * as React from "react";
|
||||||
import { Drawer as DrawerPrimitive } from "vaul"
|
import { Drawer as DrawerPrimitive } from "vaul";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Drawer({
|
function Drawer({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||||
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerTrigger({
|
function DrawerTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
||||||
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerPortal({
|
function DrawerPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
||||||
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerClose({
|
function DrawerClose({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
||||||
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerOverlay({
|
function DrawerOverlay({
|
||||||
@@ -38,11 +38,11 @@ function DrawerOverlay({
|
|||||||
data-slot="drawer-overlay"
|
data-slot="drawer-overlay"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerContent({
|
function DrawerContent({
|
||||||
@@ -61,7 +61,7 @@ function DrawerContent({
|
|||||||
"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=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=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",
|
"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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -69,7 +69,7 @@ function DrawerContent({
|
|||||||
{children}
|
{children}
|
||||||
</DrawerPrimitive.Content>
|
</DrawerPrimitive.Content>
|
||||||
</DrawerPortal>
|
</DrawerPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -78,11 +78,11 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="drawer-header"
|
data-slot="drawer-header"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -92,7 +92,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
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}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerTitle({
|
function DrawerTitle({
|
||||||
@@ -105,7 +105,7 @@ function DrawerTitle({
|
|||||||
className={cn("text-foreground font-semibold", className)}
|
className={cn("text-foreground font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerDescription({
|
function DrawerDescription({
|
||||||
@@ -118,7 +118,7 @@ function DrawerDescription({
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -132,4 +132,4 @@ export {
|
|||||||
DrawerFooter,
|
DrawerFooter,
|
||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerDescription,
|
DrawerDescription,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function DropdownMenu({
|
function DropdownMenu({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuPortal({
|
function DropdownMenuPortal({
|
||||||
@@ -17,18 +17,15 @@ function DropdownMenuPortal({
|
|||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuTrigger({
|
function DropdownMenuTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
|
||||||
data-slot="dropdown-menu-trigger"
|
);
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuContent({
|
function DropdownMenuContent({
|
||||||
@@ -43,12 +40,12 @@ function DropdownMenuContent({
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuGroup({
|
function DropdownMenuGroup({
|
||||||
@@ -56,7 +53,7 @@ function DropdownMenuGroup({
|
|||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuItem({
|
function DropdownMenuItem({
|
||||||
@@ -65,8 +62,8 @@ function DropdownMenuItem({
|
|||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
variant?: "default" | "destructive"
|
variant?: "default" | "destructive";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
@@ -75,11 +72,11 @@ function DropdownMenuItem({
|
|||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuCheckboxItem({
|
function DropdownMenuCheckboxItem({
|
||||||
@@ -93,7 +90,7 @@ function DropdownMenuCheckboxItem({
|
|||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -105,7 +102,7 @@ function DropdownMenuCheckboxItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({
|
function DropdownMenuRadioGroup({
|
||||||
@@ -116,7 +113,7 @@ function DropdownMenuRadioGroup({
|
|||||||
data-slot="dropdown-menu-radio-group"
|
data-slot="dropdown-menu-radio-group"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioItem({
|
function DropdownMenuRadioItem({
|
||||||
@@ -129,7 +126,7 @@ function DropdownMenuRadioItem({
|
|||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -140,7 +137,7 @@ function DropdownMenuRadioItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuLabel({
|
function DropdownMenuLabel({
|
||||||
@@ -148,7 +145,7 @@ function DropdownMenuLabel({
|
|||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Label
|
<DropdownMenuPrimitive.Label
|
||||||
@@ -156,11 +153,11 @@ function DropdownMenuLabel({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSeparator({
|
function DropdownMenuSeparator({
|
||||||
@@ -173,7 +170,7 @@ function DropdownMenuSeparator({
|
|||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuShortcut({
|
function DropdownMenuShortcut({
|
||||||
@@ -185,17 +182,17 @@ function DropdownMenuShortcut({
|
|||||||
data-slot="dropdown-menu-shortcut"
|
data-slot="dropdown-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSub({
|
function DropdownMenuSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSubTrigger({
|
function DropdownMenuSubTrigger({
|
||||||
@@ -204,7 +201,7 @@ function DropdownMenuSubTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
@@ -212,14 +209,14 @@ function DropdownMenuSubTrigger({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronRightIcon className="ml-auto size-4" />
|
<ChevronRightIcon className="ml-auto size-4" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSubContent({
|
function DropdownMenuSubContent({
|
||||||
@@ -231,11 +228,11 @@ function DropdownMenuSubContent({
|
|||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -254,4 +251,4 @@ export {
|
|||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,11 +8,11 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty"
|
data-slot="empty"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -21,11 +21,11 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty-header"
|
data-slot="empty-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex max-w-sm flex-col items-center gap-2 text-center",
|
"flex max-w-sm flex-col items-center gap-2 text-center",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyMediaVariants = cva(
|
const emptyMediaVariants = cva(
|
||||||
@@ -34,14 +34,15 @@ const emptyMediaVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
icon:
|
||||||
|
"bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function EmptyMedia({
|
function EmptyMedia({
|
||||||
className,
|
className,
|
||||||
@@ -55,7 +56,7 @@ function EmptyMedia({
|
|||||||
className={cn(emptyMediaVariants({ variant, className }))}
|
className={cn(emptyMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -65,7 +66,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("text-lg font-medium tracking-tight", className)}
|
className={cn("text-lg font-medium tracking-tight", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -74,11 +75,11 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
data-slot="empty-description"
|
data-slot="empty-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -87,11 +88,11 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="empty-content"
|
data-slot="empty-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -101,4 +102,4 @@ export {
|
|||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
EmptyContent,
|
EmptyContent,
|
||||||
EmptyMedia,
|
EmptyMedia,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { useMemo } from "react";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import { cn } from "@/lib/utils"
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Label } from "@/components/ui/label"
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
|
|
||||||
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
||||||
return (
|
return (
|
||||||
@@ -14,11 +13,11 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-6",
|
"flex flex-col gap-6",
|
||||||
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldLegend({
|
function FieldLegend({
|
||||||
@@ -34,11 +33,11 @@ function FieldLegend({
|
|||||||
"mb-3 font-medium",
|
"mb-3 font-medium",
|
||||||
"data-[variant=legend]:text-base",
|
"data-[variant=legend]:text-base",
|
||||||
"data-[variant=label]:text-sm",
|
"data-[variant=label]:text-sm",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -47,11 +46,11 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-group"
|
data-slot="field-group"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldVariants = cva(
|
const fieldVariants = cva(
|
||||||
@@ -75,8 +74,8 @@ const fieldVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
orientation: "vertical",
|
orientation: "vertical",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Field({
|
function Field({
|
||||||
className,
|
className,
|
||||||
@@ -91,7 +90,7 @@ function Field({
|
|||||||
className={cn(fieldVariants({ orientation }), className)}
|
className={cn(fieldVariants({ orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -100,11 +99,11 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-content"
|
data-slot="field-content"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldLabel({
|
function FieldLabel({
|
||||||
@@ -118,11 +117,11 @@ function FieldLabel({
|
|||||||
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
"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-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",
|
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -131,11 +130,11 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="field-label"
|
data-slot="field-label"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -146,11 +145,11 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
|
"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",
|
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
||||||
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldSeparator({
|
function FieldSeparator({
|
||||||
@@ -158,7 +157,7 @@ function FieldSeparator({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -166,7 +165,7 @@ function FieldSeparator({
|
|||||||
data-content={!!children}
|
data-content={!!children}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -180,7 +179,7 @@ function FieldSeparator({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FieldError({
|
function FieldError({
|
||||||
@@ -189,37 +188,36 @@ function FieldError({
|
|||||||
errors,
|
errors,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
errors?: Array<{ message?: string } | undefined>
|
errors?: Array<{ message?: string } | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (children) {
|
if (children) {
|
||||||
return children
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errors?.length) {
|
if (!errors?.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueErrors = [
|
const uniqueErrors = [
|
||||||
...new Map(errors.map((error) => [error?.message, error])).values(),
|
...new Map(errors.map((error) => [error?.message, error])).values(),
|
||||||
]
|
];
|
||||||
|
|
||||||
if (uniqueErrors?.length == 1) {
|
if (uniqueErrors?.length === 1) {
|
||||||
return uniqueErrors[0]?.message
|
return uniqueErrors[0]?.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="ml-4 flex list-disc flex-col gap-1">
|
<ul className="ml-4 flex list-disc flex-col gap-1">
|
||||||
{uniqueErrors.map(
|
{uniqueErrors.map(
|
||||||
(error, index) =>
|
(error) => error?.message && <li key={error.message}>{error.message}</li>,
|
||||||
error?.message && <li key={index}>{error.message}</li>
|
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)
|
);
|
||||||
}, [children, errors])
|
}, [children, errors]);
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -231,7 +229,7 @@ function FieldError({
|
|||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -245,4 +243,4 @@ export {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
FieldContent,
|
FieldContent,
|
||||||
FieldTitle,
|
FieldTitle,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import type * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
import type * as LabelPrimitive from "@radix-ui/react-label"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
useFormState,
|
|
||||||
type ControllerProps,
|
type ControllerProps,
|
||||||
type FieldPath,
|
type FieldPath,
|
||||||
type FieldValues,
|
type FieldValues,
|
||||||
} from "react-hook-form"
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
useFormState,
|
||||||
|
} from "react-hook-form";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
const Form = FormProvider;
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
|
|
||||||
const Form = FormProvider
|
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
{} as FormFieldContextValue
|
{} as FormFieldContextValue,
|
||||||
)
|
);
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
@@ -39,21 +38,21 @@ const FormField = <
|
|||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
<Controller {...props} />
|
<Controller {...props} />
|
||||||
</FormFieldContext.Provider>
|
</FormFieldContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const useFormField = () => {
|
const useFormField = () => {
|
||||||
const fieldContext = React.useContext(FormFieldContext)
|
const fieldContext = React.useContext(FormFieldContext);
|
||||||
const itemContext = React.useContext(FormItemContext)
|
const itemContext = React.useContext(FormItemContext);
|
||||||
const { getFieldState } = useFormContext()
|
const { getFieldState } = useFormContext();
|
||||||
const formState = useFormState({ name: fieldContext.name })
|
const formState = useFormState({ name: fieldContext.name });
|
||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
const fieldState = getFieldState(fieldContext.name, formState);
|
||||||
|
|
||||||
if (!fieldContext) {
|
if (!fieldContext) {
|
||||||
throw new Error("useFormField should be used within <FormField>")
|
throw new Error("useFormField should be used within <FormField>");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = itemContext
|
const { id } = itemContext;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -62,19 +61,19 @@ const useFormField = () => {
|
|||||||
formDescriptionId: `${id}-form-item-description`,
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
formMessageId: `${id}-form-item-message`,
|
formMessageId: `${id}-form-item-message`,
|
||||||
...fieldState,
|
...fieldState,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
type FormItemContextValue = {
|
type FormItemContextValue = {
|
||||||
id: string
|
id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
{} as FormItemContextValue
|
{} as FormItemContextValue,
|
||||||
)
|
);
|
||||||
|
|
||||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const id = React.useId()
|
const id = React.useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItemContext.Provider value={{ id }}>
|
<FormItemContext.Provider value={{ id }}>
|
||||||
@@ -84,14 +83,14 @@ function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</FormItemContext.Provider>
|
</FormItemContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormLabel({
|
function FormLabel({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
const { error, formItemId } = useFormField()
|
const { error, formItemId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
@@ -101,29 +100,27 @@ function FormLabel({
|
|||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
data-slot="form-control"
|
data-slot="form-control"
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={
|
||||||
!error
|
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
}
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
const { formDescriptionId } = useFormField()
|
const { formDescriptionId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
@@ -132,15 +129,15 @@ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField();
|
||||||
const body = error ? String(error?.message ?? "") : props.children
|
const body = error ? String(error?.message ?? "") : props.children;
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -152,7 +149,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -164,4 +161,4 @@ export {
|
|||||||
FormDescription,
|
FormDescription,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
FormField,
|
FormField,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
||||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function HoverCard({
|
function HoverCard({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
||||||
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
|
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HoverCardTrigger({
|
function HoverCardTrigger({
|
||||||
@@ -16,7 +16,7 @@ function HoverCardTrigger({
|
|||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HoverCardContent({
|
function HoverCardContent({
|
||||||
@@ -33,12 +33,12 @@ function HoverCardContent({
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</HoverCardPrimitive.Portal>
|
</HoverCardPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils"
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Input } from "@/components/ui/input"
|
import { cn } from "@/lib/utils";
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
|
||||||
|
|
||||||
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -29,11 +28,11 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
// Error state.
|
// Error state.
|
||||||
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
||||||
|
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupAddonVariants = cva(
|
const inputGroupAddonVariants = cva(
|
||||||
@@ -54,8 +53,8 @@ const inputGroupAddonVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
align: "inline-start",
|
align: "inline-start",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function InputGroupAddon({
|
function InputGroupAddon({
|
||||||
className,
|
className,
|
||||||
@@ -70,13 +69,22 @@ function InputGroupAddon({
|
|||||||
className={cn(inputGroupAddonVariants({ align }), className)}
|
className={cn(inputGroupAddonVariants({ align }), className)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if ((e.target as HTMLElement).closest("button")) {
|
if ((e.target as HTMLElement).closest("button")) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
if ((e.target as HTMLElement).closest("button")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
||||||
}
|
}
|
||||||
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupButtonVariants = cva(
|
const inputGroupButtonVariants = cva(
|
||||||
@@ -86,16 +94,15 @@ const inputGroupButtonVariants = cva(
|
|||||||
size: {
|
size: {
|
||||||
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||||
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
||||||
"icon-xs":
|
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||||
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
|
||||||
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: "xs",
|
size: "xs",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function InputGroupButton({
|
function InputGroupButton({
|
||||||
className,
|
className,
|
||||||
@@ -113,7 +120,7 @@ function InputGroupButton({
|
|||||||
className={cn(inputGroupButtonVariants({ size }), className)}
|
className={cn(inputGroupButtonVariants({ size }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
@@ -121,11 +128,11 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputGroupInput({
|
function InputGroupInput({
|
||||||
@@ -137,11 +144,11 @@ function InputGroupInput({
|
|||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputGroupTextarea({
|
function InputGroupTextarea({
|
||||||
@@ -153,11 +160,11 @@ function InputGroupTextarea({
|
|||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -167,4 +174,4 @@ export {
|
|||||||
InputGroupText,
|
InputGroupText,
|
||||||
InputGroupInput,
|
InputGroupInput,
|
||||||
InputGroupTextarea,
|
InputGroupTextarea,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import { OTPInput, OTPInputContext } from "input-otp";
|
||||||
import { OTPInput, OTPInputContext } from "input-otp"
|
import { MinusIcon } from "lucide-react";
|
||||||
import { MinusIcon } from "lucide-react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function InputOTP({
|
function InputOTP({
|
||||||
className,
|
className,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof OTPInput> & {
|
}: React.ComponentProps<typeof OTPInput> & {
|
||||||
containerClassName?: string
|
containerClassName?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<OTPInput
|
<OTPInput
|
||||||
data-slot="input-otp"
|
data-slot="input-otp"
|
||||||
containerClassName={cn(
|
containerClassName={cn(
|
||||||
"flex items-center gap-2 has-disabled:opacity-50",
|
"flex items-center gap-2 has-disabled:opacity-50",
|
||||||
containerClassName
|
containerClassName,
|
||||||
)}
|
)}
|
||||||
className={cn("disabled:cursor-not-allowed", className)}
|
className={cn("disabled:cursor-not-allowed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -33,7 +33,7 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex items-center", className)}
|
className={cn("flex items-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputOTPSlot({
|
function InputOTPSlot({
|
||||||
@@ -41,10 +41,10 @@ function InputOTPSlot({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
index: number
|
index: number;
|
||||||
}) {
|
}) {
|
||||||
const inputOTPContext = React.useContext(OTPInputContext)
|
const inputOTPContext = React.useContext(OTPInputContext);
|
||||||
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -52,7 +52,7 @@ function InputOTPSlot({
|
|||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -63,7 +63,7 @@ function InputOTPSlot({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -71,7 +71,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|||||||
<div data-slot="input-otp-separator" role="separator" {...props}>
|
<div data-slot="input-otp-separator" role="separator" {...props}>
|
||||||
<MinusIcon />
|
<MinusIcon />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
@@ -11,11 +11,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input 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",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input 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]",
|
"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",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as React from "react"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
|
|
||||||
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -13,7 +12,7 @@ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("group/item-group flex flex-col", className)}
|
className={cn("group/item-group flex flex-col", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemSeparator({
|
function ItemSeparator({
|
||||||
@@ -27,7 +26,7 @@ function ItemSeparator({
|
|||||||
className={cn("my-0", className)}
|
className={cn("my-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemVariants = cva(
|
const itemVariants = cva(
|
||||||
@@ -48,8 +47,8 @@ const itemVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Item({
|
function Item({
|
||||||
className,
|
className,
|
||||||
@@ -59,7 +58,7 @@ function Item({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "div"
|
const Comp = asChild ? Slot : "div";
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="item"
|
data-slot="item"
|
||||||
@@ -68,7 +67,7 @@ function Item({
|
|||||||
className={cn(itemVariants({ variant, size, className }))}
|
className={cn(itemVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemMediaVariants = cva(
|
const itemMediaVariants = cva(
|
||||||
@@ -77,7 +76,8 @@ const itemMediaVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
|
icon:
|
||||||
|
"size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
|
||||||
image:
|
image:
|
||||||
"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
|
"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
|
||||||
},
|
},
|
||||||
@@ -85,8 +85,8 @@ const itemMediaVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function ItemMedia({
|
function ItemMedia({
|
||||||
className,
|
className,
|
||||||
@@ -100,7 +100,7 @@ function ItemMedia({
|
|||||||
className={cn(itemMediaVariants({ variant, className }))}
|
className={cn(itemMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -109,11 +109,11 @@ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-content"
|
data-slot="item-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -122,11 +122,11 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-title"
|
data-slot="item-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
|
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
@@ -136,11 +136,11 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
|
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
|
||||||
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -150,7 +150,7 @@ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex items-center gap-2", className)}
|
className={cn("flex items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -159,11 +159,11 @@ function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-header"
|
data-slot="item-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -172,11 +172,11 @@ function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="item-footer"
|
data-slot="item-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -190,4 +190,4 @@ export {
|
|||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemHeader,
|
ItemHeader,
|
||||||
ItemFooter,
|
ItemFooter,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,11 +8,11 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|||||||
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
|
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
|
||||||
"[&_svg:not([class*='size-'])]:size-3",
|
"[&_svg:not([class*='size-'])]:size-3",
|
||||||
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
|
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -22,7 +22,7 @@ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("inline-flex items-center gap-1", className)}
|
className={cn("inline-flex items-center gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Kbd, KbdGroup }
|
export { Kbd, KbdGroup };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Label({
|
function Label({
|
||||||
className,
|
className,
|
||||||
@@ -14,11 +14,11 @@ function Label({
|
|||||||
data-slot="label"
|
data-slot="label"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Label }
|
export { Label };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as MenubarPrimitive from "@radix-ui/react-menubar";
|
||||||
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Menubar({
|
function Menubar({
|
||||||
className,
|
className,
|
||||||
@@ -15,29 +15,29 @@ function Menubar({
|
|||||||
data-slot="menubar"
|
data-slot="menubar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
|
"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarMenu({
|
function MenubarMenu({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
||||||
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
|
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarGroup({
|
function MenubarGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
||||||
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
|
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarPortal({
|
function MenubarPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
||||||
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
|
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarRadioGroup({
|
function MenubarRadioGroup({
|
||||||
@@ -45,7 +45,7 @@ function MenubarRadioGroup({
|
|||||||
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarTrigger({
|
function MenubarTrigger({
|
||||||
@@ -57,11 +57,11 @@ function MenubarTrigger({
|
|||||||
data-slot="menubar-trigger"
|
data-slot="menubar-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
|
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarContent({
|
function MenubarContent({
|
||||||
@@ -80,12 +80,12 @@ function MenubarContent({
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in 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-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in 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-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MenubarPortal>
|
</MenubarPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarItem({
|
function MenubarItem({
|
||||||
@@ -94,8 +94,8 @@ function MenubarItem({
|
|||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
|
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
variant?: "default" | "destructive"
|
variant?: "default" | "destructive";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Item
|
<MenubarPrimitive.Item
|
||||||
@@ -104,11 +104,11 @@ function MenubarItem({
|
|||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarCheckboxItem({
|
function MenubarCheckboxItem({
|
||||||
@@ -122,7 +122,7 @@ function MenubarCheckboxItem({
|
|||||||
data-slot="menubar-checkbox-item"
|
data-slot="menubar-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -134,7 +134,7 @@ function MenubarCheckboxItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</MenubarPrimitive.CheckboxItem>
|
</MenubarPrimitive.CheckboxItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarRadioItem({
|
function MenubarRadioItem({
|
||||||
@@ -147,7 +147,7 @@ function MenubarRadioItem({
|
|||||||
data-slot="menubar-radio-item"
|
data-slot="menubar-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -158,7 +158,7 @@ function MenubarRadioItem({
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</MenubarPrimitive.RadioItem>
|
</MenubarPrimitive.RadioItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarLabel({
|
function MenubarLabel({
|
||||||
@@ -166,7 +166,7 @@ function MenubarLabel({
|
|||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
|
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Label
|
<MenubarPrimitive.Label
|
||||||
@@ -174,11 +174,11 @@ function MenubarLabel({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSeparator({
|
function MenubarSeparator({
|
||||||
@@ -191,7 +191,7 @@ function MenubarSeparator({
|
|||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarShortcut({
|
function MenubarShortcut({
|
||||||
@@ -203,17 +203,17 @@ function MenubarShortcut({
|
|||||||
data-slot="menubar-shortcut"
|
data-slot="menubar-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSub({
|
function MenubarSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
||||||
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
|
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSubTrigger({
|
function MenubarSubTrigger({
|
||||||
@@ -222,7 +222,7 @@ function MenubarSubTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
|
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.SubTrigger
|
<MenubarPrimitive.SubTrigger
|
||||||
@@ -230,14 +230,14 @@ function MenubarSubTrigger({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
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-none 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-none select-none data-[inset]:pl-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
</MenubarPrimitive.SubTrigger>
|
</MenubarPrimitive.SubTrigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSubContent({
|
function MenubarSubContent({
|
||||||
@@ -249,11 +249,11 @@ function MenubarSubContent({
|
|||||||
data-slot="menubar-sub-content"
|
data-slot="menubar-sub-content"
|
||||||
className={cn(
|
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-menubar-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-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -273,4 +273,4 @@ export {
|
|||||||
MenubarSub,
|
MenubarSub,
|
||||||
MenubarSubTrigger,
|
MenubarSubTrigger,
|
||||||
MenubarSubContent,
|
MenubarSubContent,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
||||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
import { cva } from "class-variance-authority";
|
||||||
import { cva } from "class-variance-authority"
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function NavigationMenu({
|
function NavigationMenu({
|
||||||
className,
|
className,
|
||||||
@@ -11,7 +11,7 @@ function NavigationMenu({
|
|||||||
viewport = true,
|
viewport = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
||||||
viewport?: boolean
|
viewport?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Root
|
<NavigationMenuPrimitive.Root
|
||||||
@@ -19,14 +19,14 @@ function NavigationMenu({
|
|||||||
data-viewport={viewport}
|
data-viewport={viewport}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{viewport && <NavigationMenuViewport />}
|
{viewport && <NavigationMenuViewport />}
|
||||||
</NavigationMenuPrimitive.Root>
|
</NavigationMenuPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuList({
|
function NavigationMenuList({
|
||||||
@@ -38,11 +38,11 @@ function NavigationMenuList({
|
|||||||
data-slot="navigation-menu-list"
|
data-slot="navigation-menu-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex flex-1 list-none items-center justify-center gap-1",
|
"group flex flex-1 list-none items-center justify-center gap-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuItem({
|
function NavigationMenuItem({
|
||||||
@@ -55,12 +55,12 @@ function NavigationMenuItem({
|
|||||||
className={cn("relative", className)}
|
className={cn("relative", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
const navigationMenuTriggerStyle = cva(
|
||||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
|
||||||
)
|
);
|
||||||
|
|
||||||
function NavigationMenuTrigger({
|
function NavigationMenuTrigger({
|
||||||
className,
|
className,
|
||||||
@@ -79,7 +79,7 @@ function NavigationMenuTrigger({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</NavigationMenuPrimitive.Trigger>
|
</NavigationMenuPrimitive.Trigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuContent({
|
function NavigationMenuContent({
|
||||||
@@ -92,11 +92,11 @@ function NavigationMenuContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
||||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuViewport({
|
function NavigationMenuViewport({
|
||||||
@@ -105,20 +105,18 @@ function NavigationMenuViewport({
|
|||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}
|
||||||
"absolute top-full left-0 isolate z-50 flex justify-center"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<NavigationMenuPrimitive.Viewport
|
<NavigationMenuPrimitive.Viewport
|
||||||
data-slot="navigation-menu-viewport"
|
data-slot="navigation-menu-viewport"
|
||||||
className={cn(
|
className={cn(
|
||||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuLink({
|
function NavigationMenuLink({
|
||||||
@@ -130,11 +128,11 @@ function NavigationMenuLink({
|
|||||||
data-slot="navigation-menu-link"
|
data-slot="navigation-menu-link"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuIndicator({
|
function NavigationMenuIndicator({
|
||||||
@@ -146,13 +144,13 @@ function NavigationMenuIndicator({
|
|||||||
data-slot="navigation-menu-indicator"
|
data-slot="navigation-menu-indicator"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
||||||
</NavigationMenuPrimitive.Indicator>
|
</NavigationMenuPrimitive.Indicator>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -165,4 +163,4 @@ export {
|
|||||||
NavigationMenuIndicator,
|
NavigationMenuIndicator,
|
||||||
NavigationMenuViewport,
|
NavigationMenuViewport,
|
||||||
navigationMenuTriggerStyle,
|
navigationMenuTriggerStyle,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import * as React from "react"
|
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react";
|
||||||
|
import type * as React from "react";
|
||||||
import { cn } from "@/lib/utils"
|
import { type Button, buttonVariants } from "@/components/ui/button";
|
||||||
import { buttonVariants, type Button } from "@/components/ui/button"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
role="navigation"
|
|
||||||
aria-label="pagination"
|
aria-label="pagination"
|
||||||
data-slot="pagination"
|
data-slot="pagination"
|
||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationContent({
|
function PaginationContent({
|
||||||
@@ -30,17 +28,17 @@ function PaginationContent({
|
|||||||
className={cn("flex flex-row items-center gap-1", className)}
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
||||||
return <li data-slot="pagination-item" {...props} />
|
return <li data-slot="pagination-item" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
||||||
React.ComponentProps<"a">
|
React.ComponentProps<"a">;
|
||||||
|
|
||||||
function PaginationLink({
|
function PaginationLink({
|
||||||
className,
|
className,
|
||||||
@@ -58,11 +56,11 @@ function PaginationLink({
|
|||||||
variant: isActive ? "outline" : "ghost",
|
variant: isActive ? "outline" : "ghost",
|
||||||
size,
|
size,
|
||||||
}),
|
}),
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationPrevious({
|
function PaginationPrevious({
|
||||||
@@ -79,7 +77,7 @@ function PaginationPrevious({
|
|||||||
<ChevronLeftIcon />
|
<ChevronLeftIcon />
|
||||||
<span className="hidden sm:block">Previous</span>
|
<span className="hidden sm:block">Previous</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationNext({
|
function PaginationNext({
|
||||||
@@ -96,7 +94,7 @@ function PaginationNext({
|
|||||||
<span className="hidden sm:block">Next</span>
|
<span className="hidden sm:block">Next</span>
|
||||||
<ChevronRightIcon />
|
<ChevronRightIcon />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationEllipsis({
|
function PaginationEllipsis({
|
||||||
@@ -113,7 +111,7 @@ function PaginationEllipsis({
|
|||||||
<MoreHorizontalIcon className="size-4" />
|
<MoreHorizontalIcon className="size-4" />
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">More pages</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -124,4 +122,4 @@ export {
|
|||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationEllipsis,
|
PaginationEllipsis,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Popover({
|
function Popover({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverTrigger({
|
function PopoverTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverContent({
|
function PopoverContent({
|
||||||
@@ -31,18 +31,18 @@ function PopoverContent({
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</PopoverPrimitive.Portal>
|
</PopoverPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverAnchor({
|
function PopoverAnchor({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Progress({
|
function Progress({
|
||||||
className,
|
className,
|
||||||
@@ -15,7 +15,7 @@ function Progress({
|
|||||||
data-slot="progress"
|
data-slot="progress"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -25,7 +25,7 @@ function Progress({
|
|||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Progress }
|
export { Progress };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
import { CircleIcon } from "lucide-react";
|
||||||
import { CircleIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function RadioGroup({
|
function RadioGroup({
|
||||||
className,
|
className,
|
||||||
@@ -16,7 +16,7 @@ function RadioGroup({
|
|||||||
className={cn("grid gap-3", className)}
|
className={cn("grid gap-3", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RadioGroupItem({
|
function RadioGroupItem({
|
||||||
@@ -28,7 +28,7 @@ function RadioGroupItem({
|
|||||||
data-slot="radio-group-item"
|
data-slot="radio-group-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -39,7 +39,7 @@ function RadioGroupItem({
|
|||||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
</RadioGroupPrimitive.Indicator>
|
</RadioGroupPrimitive.Indicator>
|
||||||
</RadioGroupPrimitive.Item>
|
</RadioGroupPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { RadioGroup, RadioGroupItem }
|
export { RadioGroup, RadioGroupItem };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function ScrollArea({
|
function ScrollArea({
|
||||||
className,
|
className,
|
||||||
@@ -25,7 +25,7 @@ function ScrollArea({
|
|||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScrollBar({
|
function ScrollBar({
|
||||||
@@ -39,11 +39,10 @@ function ScrollBar({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none p-px transition-colors select-none",
|
"flex touch-none p-px transition-colors select-none",
|
||||||
orientation === "vertical" &&
|
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
|
||||||
"h-full w-2.5 border-l border-l-transparent",
|
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent",
|
"h-2.5 flex-col border-t border-t-transparent",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -52,7 +51,7 @@ function ScrollBar({
|
|||||||
className="bg-border relative flex-1 rounded-full"
|
className="bg-border relative flex-1 rounded-full"
|
||||||
/>
|
/>
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
export { ScrollArea, ScrollBar };
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Select({
|
function Select({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectValue({
|
function SelectValue({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectTrigger({
|
function SelectTrigger({
|
||||||
@@ -30,7 +30,7 @@ function SelectTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default"
|
size?: "sm" | "default";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
@@ -38,7 +38,7 @@ function SelectTrigger({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -47,7 +47,7 @@ function SelectTrigger({
|
|||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectContent({
|
function SelectContent({
|
||||||
@@ -65,7 +65,7 @@ function SelectContent({
|
|||||||
"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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
align={align}
|
align={align}
|
||||||
@@ -76,7 +76,7 @@ function SelectContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -84,7 +84,7 @@ function SelectContent({
|
|||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectLabel({
|
function SelectLabel({
|
||||||
@@ -97,7 +97,7 @@ function SelectLabel({
|
|||||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectItem({
|
function SelectItem({
|
||||||
@@ -110,7 +110,7 @@ function SelectItem({
|
|||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -124,7 +124,7 @@ function SelectItem({
|
|||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectSeparator({
|
function SelectSeparator({
|
||||||
@@ -137,7 +137,7 @@ function SelectSeparator({
|
|||||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({
|
||||||
@@ -149,13 +149,13 @@ function SelectScrollUpButton({
|
|||||||
data-slot="select-scroll-up-button"
|
data-slot="select-scroll-up-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<ChevronUpIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
function SelectScrollDownButton({
|
||||||
@@ -167,13 +167,13 @@ function SelectScrollDownButton({
|
|||||||
data-slot="select-scroll-down-button"
|
data-slot="select-scroll-down-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -187,4 +187,4 @@ export {
|
|||||||
SelectSeparator,
|
SelectSeparator,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Separator({
|
function Separator({
|
||||||
className,
|
className,
|
||||||
@@ -18,11 +18,11 @@ function Separator({
|
|||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Separator }
|
export { Separator };
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
import { XIcon } from "lucide-react";
|
||||||
import { XIcon } from "lucide-react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetTrigger({
|
function SheetTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetClose({
|
function SheetClose({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetPortal({
|
function SheetPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetOverlay({
|
function SheetOverlay({
|
||||||
@@ -37,11 +37,11 @@ function SheetOverlay({
|
|||||||
data-slot="sheet-overlay"
|
data-slot="sheet-overlay"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetContent({
|
function SheetContent({
|
||||||
@@ -50,7 +50,7 @@ function SheetContent({
|
|||||||
side = "right",
|
side = "right",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
side?: "top" | "right" | "bottom" | "left"
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SheetPortal>
|
<SheetPortal>
|
||||||
@@ -67,7 +67,7 @@ function SheetContent({
|
|||||||
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
side === "bottom" &&
|
side === "bottom" &&
|
||||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -78,7 +78,7 @@ function SheetContent({
|
|||||||
</SheetPrimitive.Close>
|
</SheetPrimitive.Close>
|
||||||
</SheetPrimitive.Content>
|
</SheetPrimitive.Content>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -88,7 +88,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -98,7 +98,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
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}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetTitle({
|
function SheetTitle({
|
||||||
@@ -111,7 +111,7 @@ function SheetTitle({
|
|||||||
className={cn("text-foreground font-semibold", className)}
|
className={cn("text-foreground font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetDescription({
|
function SheetDescription({
|
||||||
@@ -124,7 +124,7 @@ function SheetDescription({
|
|||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -136,4 +136,4 @@ export {
|
|||||||
SheetFooter,
|
SheetFooter,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetDescription,
|
SheetDescription,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,56 +1,55 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { PanelLeftIcon } from "lucide-react";
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
import * as React from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { useIsMobile } from "@/hooks/use-mobile"
|
import { Input } from "@/components/ui/input";
|
||||||
import { cn } from "@/lib/utils"
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetDescription,
|
SheetDescription,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from "@/components/ui/sheet"
|
} from "@/components/ui/sheet";
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip"
|
} from "@/components/ui/tooltip";
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
const SIDEBAR_WIDTH = "16rem"
|
const SIDEBAR_WIDTH = "16rem";
|
||||||
const SIDEBAR_WIDTH_MOBILE = "18rem"
|
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||||
const SIDEBAR_WIDTH_ICON = "3rem"
|
const SIDEBAR_WIDTH_ICON = "3rem";
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||||
|
|
||||||
type SidebarContextProps = {
|
type SidebarContextProps = {
|
||||||
state: "expanded" | "collapsed"
|
state: "expanded" | "collapsed";
|
||||||
open: boolean
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void;
|
||||||
openMobile: boolean
|
openMobile: boolean;
|
||||||
setOpenMobile: (open: boolean) => void
|
setOpenMobile: (open: boolean) => void;
|
||||||
isMobile: boolean
|
isMobile: boolean;
|
||||||
toggleSidebar: () => void
|
toggleSidebar: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
|
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
|
||||||
|
|
||||||
function useSidebar() {
|
function useSidebar() {
|
||||||
const context = React.useContext(SidebarContext)
|
const context = React.useContext(SidebarContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useSidebar must be used within a SidebarProvider.")
|
throw new Error("useSidebar must be used within a SidebarProvider.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarProvider({
|
function SidebarProvider({
|
||||||
@@ -62,36 +61,36 @@ function SidebarProvider({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
defaultOpen?: boolean
|
defaultOpen?: boolean;
|
||||||
open?: boolean
|
open?: boolean;
|
||||||
onOpenChange?: (open: boolean) => void
|
onOpenChange?: (open: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile();
|
||||||
const [openMobile, setOpenMobile] = React.useState(false)
|
const [openMobile, setOpenMobile] = React.useState(false);
|
||||||
|
|
||||||
// This is the internal state of the sidebar.
|
// This is the internal state of the sidebar.
|
||||||
// We use openProp and setOpenProp for control from outside the component.
|
// We use openProp and setOpenProp for control from outside the component.
|
||||||
const [_open, _setOpen] = React.useState(defaultOpen)
|
const [_open, _setOpen] = React.useState(defaultOpen);
|
||||||
const open = openProp ?? _open
|
const open = openProp ?? _open;
|
||||||
const setOpen = React.useCallback(
|
const setOpen = React.useCallback(
|
||||||
(value: boolean | ((value: boolean) => boolean)) => {
|
(value: boolean | ((value: boolean) => boolean)) => {
|
||||||
const openState = typeof value === "function" ? value(open) : value
|
const openState = typeof value === "function" ? value(open) : value;
|
||||||
if (setOpenProp) {
|
if (setOpenProp) {
|
||||||
setOpenProp(openState)
|
setOpenProp(openState);
|
||||||
} else {
|
} else {
|
||||||
_setOpen(openState)
|
_setOpen(openState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This sets the cookie to keep the sidebar state.
|
// This sets the cookie to keep the sidebar state.
|
||||||
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
||||||
},
|
},
|
||||||
[setOpenProp, open]
|
[setOpenProp, open],
|
||||||
)
|
);
|
||||||
|
|
||||||
// Helper to toggle the sidebar.
|
// Helper to toggle the sidebar.
|
||||||
const toggleSidebar = React.useCallback(() => {
|
const toggleSidebar = React.useCallback(() => {
|
||||||
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
||||||
}, [isMobile, setOpen, setOpenMobile])
|
}, [isMobile, setOpen]);
|
||||||
|
|
||||||
// Adds a keyboard shortcut to toggle the sidebar.
|
// Adds a keyboard shortcut to toggle the sidebar.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -100,18 +99,18 @@ function SidebarProvider({
|
|||||||
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
||||||
(event.metaKey || event.ctrlKey)
|
(event.metaKey || event.ctrlKey)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
toggleSidebar()
|
toggleSidebar();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown)
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [toggleSidebar])
|
}, [toggleSidebar]);
|
||||||
|
|
||||||
// We add a state so that we can do data-state="expanded" or "collapsed".
|
// We add a state so that we can do data-state="expanded" or "collapsed".
|
||||||
// This makes it easier to style the sidebar with Tailwind classes.
|
// This makes it easier to style the sidebar with Tailwind classes.
|
||||||
const state = open ? "expanded" : "collapsed"
|
const state = open ? "expanded" : "collapsed";
|
||||||
|
|
||||||
const contextValue = React.useMemo<SidebarContextProps>(
|
const contextValue = React.useMemo<SidebarContextProps>(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -123,8 +122,8 @@ function SidebarProvider({
|
|||||||
setOpenMobile,
|
setOpenMobile,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
}),
|
}),
|
||||||
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarContext.Provider value={contextValue}>
|
<SidebarContext.Provider value={contextValue}>
|
||||||
@@ -140,7 +139,7 @@ function SidebarProvider({
|
|||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -148,7 +147,7 @@ function SidebarProvider({
|
|||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</SidebarContext.Provider>
|
</SidebarContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sidebar({
|
function Sidebar({
|
||||||
@@ -159,11 +158,11 @@ function Sidebar({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
side?: "left" | "right"
|
side?: "left" | "right";
|
||||||
variant?: "sidebar" | "floating" | "inset"
|
variant?: "sidebar" | "floating" | "inset";
|
||||||
collapsible?: "offcanvas" | "icon" | "none"
|
collapsible?: "offcanvas" | "icon" | "none";
|
||||||
}) {
|
}) {
|
||||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
||||||
|
|
||||||
if (collapsible === "none") {
|
if (collapsible === "none") {
|
||||||
return (
|
return (
|
||||||
@@ -171,13 +170,13 @@ function Sidebar({
|
|||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
|
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
@@ -202,7 +201,7 @@ function Sidebar({
|
|||||||
<div className="flex h-full w-full flex-col">{children}</div>
|
<div className="flex h-full w-full flex-col">{children}</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -223,7 +222,7 @@ function Sidebar({
|
|||||||
"group-data-[side=right]:rotate-180",
|
"group-data-[side=right]:rotate-180",
|
||||||
variant === "floating" || variant === "inset"
|
variant === "floating" || variant === "inset"
|
||||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
||||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
|
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -237,7 +236,7 @@ function Sidebar({
|
|||||||
variant === "floating" || variant === "inset"
|
variant === "floating" || variant === "inset"
|
||||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -250,7 +249,7 @@ function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarTrigger({
|
function SidebarTrigger({
|
||||||
@@ -258,7 +257,7 @@ function SidebarTrigger({
|
|||||||
onClick,
|
onClick,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { toggleSidebar } = useSidebar()
|
const { toggleSidebar } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -268,19 +267,19 @@ function SidebarTrigger({
|
|||||||
size="icon"
|
size="icon"
|
||||||
className={cn("size-7", className)}
|
className={cn("size-7", className)}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
onClick?.(event)
|
onClick?.(event);
|
||||||
toggleSidebar()
|
toggleSidebar();
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<PanelLeftIcon />
|
<PanelLeftIcon />
|
||||||
<span className="sr-only">Toggle Sidebar</span>
|
<span className="sr-only">Toggle Sidebar</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||||
const { toggleSidebar } = useSidebar()
|
const { toggleSidebar } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -297,11 +296,11 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
|||||||
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
||||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
||||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
||||||
@@ -311,11 +310,11 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"bg-background relative flex w-full flex-1 flex-col",
|
"bg-background relative flex w-full flex-1 flex-col",
|
||||||
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarInput({
|
function SidebarInput({
|
||||||
@@ -329,7 +328,7 @@ function SidebarInput({
|
|||||||
className={cn("bg-background h-8 w-full shadow-none", className)}
|
className={cn("bg-background h-8 w-full shadow-none", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -340,7 +339,7 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex flex-col gap-2 p-2", className)}
|
className={cn("flex flex-col gap-2 p-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -351,7 +350,7 @@ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("flex flex-col gap-2 p-2", className)}
|
className={cn("flex flex-col gap-2 p-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarSeparator({
|
function SidebarSeparator({
|
||||||
@@ -365,7 +364,7 @@ function SidebarSeparator({
|
|||||||
className={cn("bg-sidebar-border mx-2 w-auto", className)}
|
className={cn("bg-sidebar-border mx-2 w-auto", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -375,11 +374,11 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-sidebar="content"
|
data-sidebar="content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
@@ -390,7 +389,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroupLabel({
|
function SidebarGroupLabel({
|
||||||
@@ -398,7 +397,7 @@ function SidebarGroupLabel({
|
|||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "div"
|
const Comp = asChild ? Slot : "div";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -407,11 +406,11 @@ function SidebarGroupLabel({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroupAction({
|
function SidebarGroupAction({
|
||||||
@@ -419,7 +418,7 @@ function SidebarGroupAction({
|
|||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -430,11 +429,11 @@ function SidebarGroupAction({
|
|||||||
// Increases the hit area of the button on mobile.
|
// Increases the hit area of the button on mobile.
|
||||||
"after:absolute after:-inset-2 md:after:hidden",
|
"after:absolute after:-inset-2 md:after:hidden",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroupContent({
|
function SidebarGroupContent({
|
||||||
@@ -448,7 +447,7 @@ function SidebarGroupContent({
|
|||||||
className={cn("w-full text-sm", className)}
|
className={cn("w-full text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
||||||
@@ -459,7 +458,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
|||||||
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
@@ -470,7 +469,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|||||||
className={cn("group/menu-item relative", className)}
|
className={cn("group/menu-item relative", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebarMenuButtonVariants = cva(
|
const sidebarMenuButtonVariants = cva(
|
||||||
@@ -492,8 +491,8 @@ const sidebarMenuButtonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function SidebarMenuButton({
|
function SidebarMenuButton({
|
||||||
asChild = false,
|
asChild = false,
|
||||||
@@ -504,12 +503,12 @@ function SidebarMenuButton({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & {
|
}: React.ComponentProps<"button"> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
||||||
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
const { isMobile, state } = useSidebar()
|
const { isMobile, state } = useSidebar();
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -520,16 +519,16 @@ function SidebarMenuButton({
|
|||||||
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!tooltip) {
|
if (!tooltip) {
|
||||||
return button
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof tooltip === "string") {
|
if (typeof tooltip === "string") {
|
||||||
tooltip = {
|
tooltip = {
|
||||||
children: tooltip,
|
children: tooltip,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -542,7 +541,7 @@ function SidebarMenuButton({
|
|||||||
{...tooltip}
|
{...tooltip}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuAction({
|
function SidebarMenuAction({
|
||||||
@@ -551,10 +550,10 @@ function SidebarMenuAction({
|
|||||||
showOnHover = false,
|
showOnHover = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & {
|
}: React.ComponentProps<"button"> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
showOnHover?: boolean
|
showOnHover?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -570,11 +569,11 @@ function SidebarMenuAction({
|
|||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
showOnHover &&
|
showOnHover &&
|
||||||
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
|
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuBadge({
|
function SidebarMenuBadge({
|
||||||
@@ -592,11 +591,11 @@ function SidebarMenuBadge({
|
|||||||
"peer-data-[size=default]/menu-button:top-1.5",
|
"peer-data-[size=default]/menu-button:top-1.5",
|
||||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuSkeleton({
|
function SidebarMenuSkeleton({
|
||||||
@@ -604,12 +603,12 @@ function SidebarMenuSkeleton({
|
|||||||
showIcon = false,
|
showIcon = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
showIcon?: boolean
|
showIcon?: boolean;
|
||||||
}) {
|
}) {
|
||||||
// Random width between 50 to 90%.
|
// Random width between 50 to 90%.
|
||||||
const width = React.useMemo(() => {
|
const width = React.useMemo(() => {
|
||||||
return `${Math.floor(Math.random() * 40) + 50}%`
|
return `${Math.floor(Math.random() * 40) + 50}%`;
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -619,10 +618,7 @@ function SidebarMenuSkeleton({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{showIcon && (
|
{showIcon && (
|
||||||
<Skeleton
|
<Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />
|
||||||
className="size-4 rounded-md"
|
|
||||||
data-sidebar="menu-skeleton-icon"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className="h-4 max-w-(--skeleton-width) flex-1"
|
className="h-4 max-w-(--skeleton-width) flex-1"
|
||||||
@@ -634,7 +630,7 @@ function SidebarMenuSkeleton({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
||||||
@@ -645,11 +641,11 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
|
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuSubItem({
|
function SidebarMenuSubItem({
|
||||||
@@ -663,7 +659,7 @@ function SidebarMenuSubItem({
|
|||||||
className={cn("group/menu-sub-item relative", className)}
|
className={cn("group/menu-sub-item relative", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuSubButton({
|
function SidebarMenuSubButton({
|
||||||
@@ -673,11 +669,11 @@ function SidebarMenuSubButton({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"a"> & {
|
}: React.ComponentProps<"a"> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
size?: "sm" | "md"
|
size?: "sm" | "md";
|
||||||
isActive?: boolean
|
isActive?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "a"
|
const Comp = asChild ? Slot : "a";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -691,11 +687,11 @@ function SidebarMenuSubButton({
|
|||||||
size === "sm" && "text-xs",
|
size === "sm" && "text-xs",
|
||||||
size === "md" && "text-sm",
|
size === "md" && "text-sm",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -723,4 +719,4 @@ export {
|
|||||||
SidebarSeparator,
|
SidebarSeparator,
|
||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
className={cn("bg-accent animate-pulse rounded-md", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Skeleton }
|
export { Skeleton };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Slider({
|
function Slider({
|
||||||
className,
|
className,
|
||||||
@@ -20,8 +20,8 @@ function Slider({
|
|||||||
: Array.isArray(defaultValue)
|
: Array.isArray(defaultValue)
|
||||||
? defaultValue
|
? defaultValue
|
||||||
: [min, max],
|
: [min, max],
|
||||||
[value, defaultValue, min, max]
|
[value, defaultValue, min, max],
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SliderPrimitive.Root
|
<SliderPrimitive.Root
|
||||||
@@ -32,32 +32,32 @@ function Slider({
|
|||||||
max={max}
|
max={max}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track
|
<SliderPrimitive.Track
|
||||||
data-slot="slider-track"
|
data-slot="slider-track"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Range
|
<SliderPrimitive.Range
|
||||||
data-slot="slider-range"
|
data-slot="slider-range"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
{Array.from({ length: _values.length }, (_, index) => (
|
{_values.map((val, index) => (
|
||||||
<SliderPrimitive.Thumb
|
<SliderPrimitive.Thumb
|
||||||
data-slot="slider-thumb"
|
data-slot="slider-thumb"
|
||||||
key={index}
|
key={`thumb-${index}-${val}`}
|
||||||
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SliderPrimitive.Root>
|
</SliderPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Slider }
|
export { Slider };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
Loader2Icon,
|
Loader2Icon,
|
||||||
OctagonXIcon,
|
OctagonXIcon,
|
||||||
TriangleAlertIcon,
|
TriangleAlertIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
@@ -34,7 +34,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||||||
}
|
}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export { Toaster }
|
export { Toaster };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Loader2Icon } from "lucide-react"
|
import { Loader2Icon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||||
return (
|
return (
|
||||||
@@ -10,7 +10,7 @@ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|||||||
className={cn("size-4 animate-spin", className)}
|
className={cn("size-4 animate-spin", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Spinner }
|
export { Spinner };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Switch({
|
function Switch({
|
||||||
className,
|
className,
|
||||||
@@ -14,18 +14,18 @@ function Switch({
|
|||||||
data-slot="switch"
|
data-slot="switch"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SwitchPrimitive.Thumb
|
<SwitchPrimitive.Thumb
|
||||||
data-slot="switch-thumb"
|
data-slot="switch-thumb"
|
||||||
className={cn(
|
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>
|
</SwitchPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Switch }
|
export { Switch };
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
||||||
data-slot="table-container"
|
|
||||||
className="relative w-full overflow-x-auto"
|
|
||||||
>
|
|
||||||
<table
|
<table
|
||||||
data-slot="table"
|
data-slot="table"
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||||
@@ -26,7 +23,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|||||||
className={cn("[&_tr]:border-b", className)}
|
className={cn("[&_tr]:border-b", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||||
@@ -36,7 +33,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|||||||
className={cn("[&_tr:last-child]:border-0", className)}
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||||
@@ -45,11 +42,11 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|||||||
data-slot="table-footer"
|
data-slot="table-footer"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||||
@@ -58,11 +55,11 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|||||||
data-slot="table-row"
|
data-slot="table-row"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||||
@@ -71,11 +68,11 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|||||||
data-slot="table-head"
|
data-slot="table-head"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||||
@@ -84,11 +81,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|||||||
data-slot="table-cell"
|
data-slot="table-cell"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableCaption({
|
function TableCaption({
|
||||||
@@ -101,7 +98,7 @@ function TableCaption({
|
|||||||
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -113,4 +110,4 @@ export {
|
|||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableCaption,
|
TableCaption,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Tabs({
|
function Tabs({
|
||||||
className,
|
className,
|
||||||
@@ -15,7 +15,7 @@ function Tabs({
|
|||||||
className={cn("flex flex-col gap-2", className)}
|
className={cn("flex flex-col gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsList({
|
function TabsList({
|
||||||
@@ -27,11 +27,11 @@ function TabsList({
|
|||||||
data-slot="tabs-list"
|
data-slot="tabs-list"
|
||||||
className={cn(
|
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
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsTrigger({
|
function TabsTrigger({
|
||||||
@@ -43,11 +43,11 @@ function TabsTrigger({
|
|||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsContent({
|
function TabsContent({
|
||||||
@@ -60,7 +60,7 @@ function TabsContent({
|
|||||||
className={cn("flex-1 outline-none", className)}
|
className={cn("flex-1 outline-none", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,11 +8,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Textarea }
|
export { Textarea };
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
import type { VariantProps } from "class-variance-authority";
|
||||||
import { type VariantProps } from "class-variance-authority"
|
import * as React from "react";
|
||||||
|
import { toggleVariants } from "@/components/ui/toggle";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { toggleVariants } from "@/components/ui/toggle"
|
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
VariantProps<typeof toggleVariants> & {
|
VariantProps<typeof toggleVariants> & {
|
||||||
spacing?: number
|
spacing?: number;
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
size: "default",
|
size: "default",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
spacing: 0,
|
spacing: 0,
|
||||||
})
|
});
|
||||||
|
|
||||||
function ToggleGroup({
|
function ToggleGroup({
|
||||||
className,
|
className,
|
||||||
@@ -26,7 +25,7 @@ function ToggleGroup({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
||||||
VariantProps<typeof toggleVariants> & {
|
VariantProps<typeof toggleVariants> & {
|
||||||
spacing?: number
|
spacing?: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive.Root
|
<ToggleGroupPrimitive.Root
|
||||||
@@ -37,7 +36,7 @@ function ToggleGroup({
|
|||||||
style={{ "--gap": spacing } as React.CSSProperties}
|
style={{ "--gap": spacing } as React.CSSProperties}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -45,7 +44,7 @@ function ToggleGroup({
|
|||||||
{children}
|
{children}
|
||||||
</ToggleGroupContext.Provider>
|
</ToggleGroupContext.Provider>
|
||||||
</ToggleGroupPrimitive.Root>
|
</ToggleGroupPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToggleGroupItem({
|
function ToggleGroupItem({
|
||||||
@@ -56,7 +55,7 @@ function ToggleGroupItem({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
||||||
VariantProps<typeof toggleVariants>) {
|
VariantProps<typeof toggleVariants>) {
|
||||||
const context = React.useContext(ToggleGroupContext)
|
const context = React.useContext(ToggleGroupContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive.Item
|
<ToggleGroupPrimitive.Item
|
||||||
@@ -71,13 +70,13 @@ function ToggleGroupItem({
|
|||||||
}),
|
}),
|
||||||
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
||||||
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupPrimitive.Item>
|
</ToggleGroupPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ToggleGroup, ToggleGroupItem }
|
export { ToggleGroup, ToggleGroupItem };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const toggleVariants = cva(
|
const toggleVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||||
@@ -25,8 +25,8 @@ const toggleVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Toggle({
|
function Toggle({
|
||||||
className,
|
className,
|
||||||
@@ -41,7 +41,7 @@ function Toggle({
|
|||||||
className={cn(toggleVariants({ variant, size, className }))}
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Toggle, toggleVariants }
|
export { Toggle, toggleVariants };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function TooltipProvider({
|
function TooltipProvider({
|
||||||
delayDuration = 0,
|
delayDuration = 0,
|
||||||
@@ -15,7 +15,7 @@ function TooltipProvider({
|
|||||||
delayDuration={delayDuration}
|
delayDuration={delayDuration}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tooltip({
|
function Tooltip({
|
||||||
@@ -25,13 +25,13 @@ function Tooltip({
|
|||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipTrigger({
|
function TooltipTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
@@ -47,7 +47,7 @@ function TooltipContent({
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -55,7 +55,7 @@ function TooltipContent({
|
|||||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768
|
const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
export function useIsMobile() {
|
export function useIsMobile() {
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
}
|
};
|
||||||
mql.addEventListener("change", onChange)
|
mql.addEventListener("change", onChange);
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
return () => mql.removeEventListener("change", onChange)
|
return () => mql.removeEventListener("change", onChange);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return !!isMobile
|
return !!isMobile;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ api.interceptors.response.use(
|
|||||||
|
|
||||||
errorCache.set(url, now);
|
errorCache.set(url, now);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { clsx, type ClassValue } from "clsx"
|
import { type ClassValue, clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { UserService } from "@/services/user.service";
|
|
||||||
import { AuthService } from "@/services/auth.service";
|
|
||||||
import type { User } from "@/types/user";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import * as React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
import { UserService } from "@/services/user.service";
|
||||||
|
import type { RegisterPayload } from "@/types/auth";
|
||||||
|
import type { User } from "@/types/user";
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
login: (email: string, password: string) => Promise<void>;
|
login: (email: string, password: string) => Promise<void>;
|
||||||
register: (payload: any) => Promise<void>;
|
register: (payload: RegisterPayload) => Promise<void>;
|
||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
refreshUser: () => Promise<void>;
|
refreshUser: () => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -28,7 +29,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
try {
|
try {
|
||||||
const userData = await UserService.getMe();
|
const userData = await UserService.getMe();
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -45,19 +46,51 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
await refreshUser();
|
await refreshUser();
|
||||||
toast.success("Connexion réussie !");
|
toast.success("Connexion réussie !");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
toast.error(error.response?.data?.message || "Erreur de connexion");
|
let errorMessage = "Erreur de connexion";
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"response" in error &&
|
||||||
|
error.response &&
|
||||||
|
typeof error.response === "object" &&
|
||||||
|
"data" in error.response &&
|
||||||
|
error.response.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"message" in error.response.data &&
|
||||||
|
typeof error.response.data.message === "string"
|
||||||
|
) {
|
||||||
|
errorMessage = error.response.data.message;
|
||||||
|
}
|
||||||
|
toast.error(errorMessage);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (payload: any) => {
|
const register = async (payload: RegisterPayload) => {
|
||||||
try {
|
try {
|
||||||
await AuthService.register(payload);
|
await AuthService.register(payload);
|
||||||
toast.success("Inscription réussie ! Vous pouvez maintenant vous connecter.");
|
toast.success(
|
||||||
|
"Inscription réussie ! Vous pouvez maintenant vous connecter.",
|
||||||
|
);
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
toast.error(error.response?.data?.message || "Erreur d'inscription");
|
let errorMessage = "Erreur d'inscription";
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"response" in error &&
|
||||||
|
error.response &&
|
||||||
|
typeof error.response === "object" &&
|
||||||
|
"data" in error.response &&
|
||||||
|
error.response.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"message" in error.response.data &&
|
||||||
|
typeof error.response.data.message === "string"
|
||||||
|
) {
|
||||||
|
errorMessage = error.response.data.message;
|
||||||
|
}
|
||||||
|
toast.error(errorMessage);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -68,7 +101,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setUser(null);
|
setUser(null);
|
||||||
toast.success("Déconnexion réussie");
|
toast.success("Déconnexion réussie");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Erreur lors de la déconnexion");
|
toast.error("Erreur lors de la déconnexion");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import api from "@/lib/api";
|
import api from "@/lib/api";
|
||||||
import type { LoginResponse } from "@/types/auth";
|
import type { LoginResponse, RegisterPayload } from "@/types/auth";
|
||||||
|
|
||||||
export const AuthService = {
|
export const AuthService = {
|
||||||
async login(email: string, password: string): Promise<LoginResponse> {
|
async login(email: string, password: string): Promise<LoginResponse> {
|
||||||
const { data } = await api.post<LoginResponse>("/auth/login", { email, password });
|
const { data } = await api.post<LoginResponse>("/auth/login", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async register(payload: any): Promise<any> {
|
async register(payload: RegisterPayload): Promise<void> {
|
||||||
const { data } = await api.post("/auth/register", payload);
|
await api.post("/auth/register", payload);
|
||||||
return data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
|
|||||||
@@ -11,23 +11,32 @@ export const ContentService = {
|
|||||||
query?: string;
|
query?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
}): Promise<PaginatedResponse<Content>> {
|
}): Promise<PaginatedResponse<Content>> {
|
||||||
const { data } = await api.get<PaginatedResponse<Content>>("/contents/explore", {
|
const { data } = await api.get<PaginatedResponse<Content>>(
|
||||||
|
"/contents/explore",
|
||||||
|
{
|
||||||
params,
|
params,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getTrends(limit = 10, offset = 0): Promise<PaginatedResponse<Content>> {
|
async getTrends(limit = 10, offset = 0): Promise<PaginatedResponse<Content>> {
|
||||||
const { data } = await api.get<PaginatedResponse<Content>>("/contents/trends", {
|
const { data } = await api.get<PaginatedResponse<Content>>(
|
||||||
|
"/contents/trends",
|
||||||
|
{
|
||||||
params: { limit, offset },
|
params: { limit, offset },
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getRecent(limit = 10, offset = 0): Promise<PaginatedResponse<Content>> {
|
async getRecent(limit = 10, offset = 0): Promise<PaginatedResponse<Content>> {
|
||||||
const { data } = await api.get<PaginatedResponse<Content>>("/contents/recent", {
|
const { data } = await api.get<PaginatedResponse<Content>>(
|
||||||
|
"/contents/recent",
|
||||||
|
{
|
||||||
params: { limit, offset },
|
params: { limit, offset },
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,13 @@ export const FavoriteService = {
|
|||||||
await api.delete(`/favorites/${contentId}`);
|
await api.delete(`/favorites/${contentId}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
async list(params: { limit: number; offset: number }): Promise<PaginatedResponse<Content>> {
|
async list(params: {
|
||||||
const { data } = await api.get<PaginatedResponse<Content>>("/favorites", { params });
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
}): Promise<PaginatedResponse<Content>> {
|
||||||
|
const { data } = await api.get<PaginatedResponse<Content>>("/favorites", {
|
||||||
|
params,
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import api from "@/lib/api";
|
import api from "@/lib/api";
|
||||||
import type { User, UserProfile } from "@/types/user";
|
import type { User } from "@/types/user";
|
||||||
|
|
||||||
export const UserService = {
|
export const UserService = {
|
||||||
async getMe(): Promise<User> {
|
async getMe(): Promise<User> {
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ export interface LoginResponse {
|
|||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegisterPayload {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthStatus {
|
export interface AuthStatus {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
user: null | {
|
user: null | {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export interface Category {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
export interface PaginatedResponse<T> {
|
||||||
data: T[];
|
data: T[];
|
||||||
total: number;
|
total: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user