style: refine shared UI primitives and management flows
This commit is contained in:
@@ -33,7 +33,7 @@ function ResetPasswordContent() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{!hasToken ? (
|
{!hasToken ? (
|
||||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-destructive">
|
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-destructive">
|
||||||
Password reset token is missing.
|
Password reset token is missing.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function ModuleShellHeader() {
|
|||||||
const { isMobile } = useSidebar();
|
const { isMobile } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-0 z-50 flex items-center justify-between gap-4 border-b border-border bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
<div className="sticky top-0 z-50 flex h-14 items-center justify-between gap-4 border-b border-border bg-background/95 px-6 backdrop-blur supports-backdrop-filter:bg-background/80">
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FormField, PhoneInput } from '@/components/form';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -71,12 +72,12 @@ export function ClientSheet({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-3xl">
|
<DialogContent className="sm:max-w-3xl">
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{clientId ? 'Edit Client' : 'Create Client'}
|
{clientId ? 'Edit Client' : 'Create Client'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Manage company and primary contact details.
|
Manage company and primary contact details.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -86,7 +87,7 @@ export function ClientSheet({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[58vh] space-y-5 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-5">
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
id="client-first-name"
|
id="client-first-name"
|
||||||
@@ -240,9 +241,9 @@ export function ClientSheet({
|
|||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ProjectSelect } from '@/components/lookups/ProjectSelect';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -62,14 +63,14 @@ export function PackageDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-2xl"
|
className="sm:max-w-2xl"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{packageId ? 'Edit Package' : 'Create Package'}
|
{packageId ? 'Edit Package' : 'Create Package'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Manage package details and optional chainage range.
|
Manage package details and optional chainage range.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -79,7 +80,7 @@ export function PackageDialog({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-6">
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||||
@@ -173,9 +174,9 @@ export function PackageDialog({
|
|||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Package as PackageIcon, Plus } from 'lucide-react';
|
import { Package as PackageIcon, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Package } from '@/types';
|
import type { Package } from '@/types';
|
||||||
|
|
||||||
@@ -20,6 +30,7 @@ import {
|
|||||||
|
|
||||||
export default function PackagePage() {
|
export default function PackagePage() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [packageToDelete, setPackageToDelete] = useState<Package | null>(null);
|
||||||
const { skip, setSkip, limit, setLimit } = usePackageFilters();
|
const { skip, setSkip, limit, setLimit } = usePackageFilters();
|
||||||
const packagesQuery = usePackagesQuery({ skip, limit });
|
const packagesQuery = usePackagesQuery({ skip, limit });
|
||||||
const projectsQuery = useProjectOptionsQuery();
|
const projectsQuery = useProjectOptionsQuery();
|
||||||
@@ -67,14 +78,25 @@ export default function PackagePage() {
|
|||||||
|
|
||||||
const deletePackage = useCallback(
|
const deletePackage = useCallback(
|
||||||
(pkg: Package) => {
|
(pkg: Package) => {
|
||||||
if (!confirm(`Are you sure you want to delete package "${pkg.name}"?`)) {
|
setPackageToDelete(pkg);
|
||||||
return;
|
|
||||||
}
|
|
||||||
deletePackageMutation.mutate(pkg);
|
|
||||||
},
|
},
|
||||||
[deletePackageMutation],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteDialogOpenChange = useCallback((open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
setPackageToDelete(null);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmDeletePackage = useCallback(() => {
|
||||||
|
if (!packageToDelete) return;
|
||||||
|
|
||||||
|
deletePackageMutation.mutate(packageToDelete, {
|
||||||
|
onSettled: () => setPackageToDelete(null),
|
||||||
|
});
|
||||||
|
}, [deletePackageMutation, packageToDelete]);
|
||||||
|
|
||||||
const projects = projectsQuery.data?.items ?? [];
|
const projects = projectsQuery.data?.items ?? [];
|
||||||
const packages = packagesQuery.data?.items ?? [];
|
const packages = packagesQuery.data?.items ?? [];
|
||||||
const total = packagesQuery.data?.totalItems ?? 0;
|
const total = packagesQuery.data?.totalItems ?? 0;
|
||||||
@@ -122,6 +144,36 @@ export default function PackagePage() {
|
|||||||
canSubmit={canSubmit}
|
canSubmit={canSubmit}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
open={Boolean(packageToDelete)}
|
||||||
|
onOpenChange={handleDeleteDialogOpenChange}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Delete package?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will permanently delete{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{packageToDelete?.name}
|
||||||
|
</span>
|
||||||
|
. This action cannot be undone.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={deletePackageMutation.isPending}>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
variant="destructive"
|
||||||
|
disabled={deletePackageMutation.isPending}
|
||||||
|
onClick={confirmDeletePackage}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ export function PlanSheet({
|
|||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="size-4 rounded border-border accent-primary"
|
className="size-4 rounded-lg border-border accent-primary"
|
||||||
{...register('is_active')}
|
{...register('is_active')}
|
||||||
/>
|
/>
|
||||||
Active
|
Active
|
||||||
@@ -250,7 +250,7 @@ export function PlanSheet({
|
|||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="size-4 rounded border-border accent-primary"
|
className="size-4 rounded-lg border-border accent-primary"
|
||||||
{...register('is_custom')}
|
{...register('is_custom')}
|
||||||
/>
|
/>
|
||||||
Custom plan
|
Custom plan
|
||||||
@@ -269,7 +269,7 @@ export function PlanSheet({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isPermissionsLoading ? (
|
{isPermissionsLoading ? (
|
||||||
<div className="rounded-md border p-6 text-muted-foreground">
|
<div className="rounded-lg border p-6 text-muted-foreground">
|
||||||
Loading permissions...
|
Loading permissions...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { FormField } from '@/components/form';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -54,14 +55,14 @@ export function ProjectDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-2xl"
|
className="sm:max-w-2xl"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{projectId ? 'Edit Project' : 'Create Project'}
|
{projectId ? 'Edit Project' : 'Create Project'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Manage road project identity and optional coordinate boundaries.
|
Manage road project identity and optional coordinate boundaries.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -71,7 +72,7 @@ export function ProjectDialog({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-6">
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||||
@@ -192,9 +193,9 @@ export function ProjectDialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Layers, Plus } from 'lucide-react';
|
import { Layers, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Project } from '@/types';
|
import type { Project } from '@/types';
|
||||||
|
|
||||||
@@ -19,6 +29,7 @@ import {
|
|||||||
|
|
||||||
export default function ProjectPage() {
|
export default function ProjectPage() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [projectToDelete, setProjectToDelete] = useState<Project | null>(null);
|
||||||
const { skip, setSkip, limit, setLimit } = useProjectFilters();
|
const { skip, setSkip, limit, setLimit } = useProjectFilters();
|
||||||
const projectsQuery = useProjectsQuery({ skip, limit });
|
const projectsQuery = useProjectsQuery({ skip, limit });
|
||||||
const deleteProjectMutation = useDeleteProjectMutation();
|
const deleteProjectMutation = useDeleteProjectMutation();
|
||||||
@@ -63,16 +74,25 @@ export default function ProjectPage() {
|
|||||||
|
|
||||||
const deleteProject = useCallback(
|
const deleteProject = useCallback(
|
||||||
(project: Project) => {
|
(project: Project) => {
|
||||||
if (
|
setProjectToDelete(project);
|
||||||
!confirm(`Are you sure you want to delete project "${project.name}"?`)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deleteProjectMutation.mutate(project);
|
|
||||||
},
|
},
|
||||||
[deleteProjectMutation],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteDialogOpenChange = useCallback((open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
setProjectToDelete(null);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmDeleteProject = useCallback(() => {
|
||||||
|
if (!projectToDelete) return;
|
||||||
|
|
||||||
|
deleteProjectMutation.mutate(projectToDelete, {
|
||||||
|
onSettled: () => setProjectToDelete(null),
|
||||||
|
});
|
||||||
|
}, [deleteProjectMutation, projectToDelete]);
|
||||||
|
|
||||||
const columns = useProjectColumns();
|
const columns = useProjectColumns();
|
||||||
const projects = projectsQuery.data?.items ?? [];
|
const projects = projectsQuery.data?.items ?? [];
|
||||||
const total = projectsQuery.data?.totalItems ?? 0;
|
const total = projectsQuery.data?.totalItems ?? 0;
|
||||||
@@ -117,6 +137,36 @@ export default function ProjectPage() {
|
|||||||
canSubmit={canSubmit}
|
canSubmit={canSubmit}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
open={Boolean(projectToDelete)}
|
||||||
|
onOpenChange={handleDeleteDialogOpenChange}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Delete project?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will permanently delete{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{projectToDelete?.name}
|
||||||
|
</span>
|
||||||
|
. This action cannot be undone.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={deleteProjectMutation.isPending}>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
variant="destructive"
|
||||||
|
disabled={deleteProjectMutation.isPending}
|
||||||
|
onClick={confirmDeleteProject}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
|||||||
function StatSkeleton() {
|
function StatSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center rounded-lg border border-muted bg-muted/30 p-4 text-center">
|
<div className="flex flex-col items-center rounded-lg border border-muted bg-muted/30 p-4 text-center">
|
||||||
<Skeleton className="mb-2 size-9 rounded-md" />
|
<Skeleton className="mb-2 size-9 rounded-lg" />
|
||||||
<Skeleton className="h-6 w-12" />
|
<Skeleton className="h-6 w-12" />
|
||||||
<Skeleton className="mt-2 h-3 w-20" />
|
<Skeleton className="mt-2 h-3 w-20" />
|
||||||
</div>
|
</div>
|
||||||
@@ -14,9 +14,9 @@ function StatSkeleton() {
|
|||||||
|
|
||||||
function DetectionLogSkeleton() {
|
function DetectionLogSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border bg-card p-3">
|
<div className="rounded-lg border bg-card p-3">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Skeleton className="size-20 shrink-0 rounded-md" />
|
<Skeleton className="size-20 shrink-0 rounded-lg" />
|
||||||
<div className="min-w-0 flex-1 space-y-2">
|
<div className="min-w-0 flex-1 space-y-2">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -41,7 +41,7 @@ export function VideoResultsSkeleton() {
|
|||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex min-w-0 items-start gap-3">
|
<div className="flex min-w-0 items-start gap-3">
|
||||||
<Skeleton className="size-10 rounded-md" />
|
<Skeleton className="size-10 rounded-lg" />
|
||||||
<div className="min-w-0 space-y-2">
|
<div className="min-w-0 space-y-2">
|
||||||
<Skeleton className="h-7 w-60 max-w-full" />
|
<Skeleton className="h-7 w-60 max-w-full" />
|
||||||
<Skeleton className="h-3 w-80 max-w-full" />
|
<Skeleton className="h-3 w-80 max-w-full" />
|
||||||
@@ -50,10 +50,10 @@ export function VideoResultsSkeleton() {
|
|||||||
<Skeleton className="h-8 w-20" />
|
<Skeleton className="h-8 w-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section className="overflow-hidden rounded-xl border bg-card">
|
<section className="overflow-hidden rounded-lg border bg-card">
|
||||||
<div className="border-b p-6 pb-4">
|
<div className="border-b p-6 pb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Skeleton className="size-10 rounded" />
|
<Skeleton className="size-10 rounded-lg" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Skeleton className="h-5 w-44" />
|
<Skeleton className="h-5 w-44" />
|
||||||
<Skeleton className="h-3 w-64" />
|
<Skeleton className="h-3 w-64" />
|
||||||
@@ -76,7 +76,7 @@ export function VideoResultsSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 gap-2 p-3 sm:grid-cols-3">
|
<div className="grid grid-cols-1 gap-2 p-3 sm:grid-cols-3">
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<Skeleton key={index} className="h-16 w-full rounded-md" />
|
<Skeleton key={index} className="h-16 w-full rounded-lg" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function RoleSheet({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isPermissionsLoading ? (
|
{isPermissionsLoading ? (
|
||||||
<div className="rounded-md border p-6 text-muted-foreground">
|
<div className="rounded-lg border p-6 text-muted-foreground">
|
||||||
Loading permissions...
|
Loading permissions...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ProjectSelect } from '@/components/lookups/ProjectSelect';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -71,14 +72,14 @@ export function SegmentDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-2xl"
|
className="sm:max-w-2xl"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{segmentId ? 'Edit Segment' : 'Create Segment'}
|
{segmentId ? 'Edit Segment' : 'Create Segment'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Manage segment binding, chainage values, direction, and coordinates.
|
Manage segment binding, chainage values, direction, and coordinates.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -88,7 +89,7 @@ export function SegmentDialog({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-6">
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||||
@@ -300,9 +301,9 @@ export function SegmentDialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Milestone, Plus } from 'lucide-react';
|
import { Milestone, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Chainage } from '@/types';
|
import type { Chainage } from '@/types';
|
||||||
|
|
||||||
@@ -21,6 +31,7 @@ import {
|
|||||||
|
|
||||||
export default function SegmentPage() {
|
export default function SegmentPage() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [segmentToDelete, setSegmentToDelete] = useState<Chainage | null>(null);
|
||||||
const { skip, setSkip, limit, setLimit } = useSegmentFilters();
|
const { skip, setSkip, limit, setLimit } = useSegmentFilters();
|
||||||
const segmentsQuery = useSegmentsQuery({ skip, limit });
|
const segmentsQuery = useSegmentsQuery({ skip, limit });
|
||||||
const projectsQuery = useProjectOptionsQuery();
|
const projectsQuery = useProjectOptionsQuery();
|
||||||
@@ -75,18 +86,25 @@ export default function SegmentPage() {
|
|||||||
|
|
||||||
const deleteSegment = useCallback(
|
const deleteSegment = useCallback(
|
||||||
(segment: Chainage) => {
|
(segment: Chainage) => {
|
||||||
if (
|
setSegmentToDelete(segment);
|
||||||
!confirm(
|
|
||||||
`Are you sure you want to delete segment "${segment.segment_name}"?`,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deleteSegmentMutation.mutate(segment);
|
|
||||||
},
|
},
|
||||||
[deleteSegmentMutation],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteDialogOpenChange = useCallback((open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
setSegmentToDelete(null);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmDeleteSegment = useCallback(() => {
|
||||||
|
if (!segmentToDelete) return;
|
||||||
|
|
||||||
|
deleteSegmentMutation.mutate(segmentToDelete, {
|
||||||
|
onSettled: () => setSegmentToDelete(null),
|
||||||
|
});
|
||||||
|
}, [deleteSegmentMutation, segmentToDelete]);
|
||||||
|
|
||||||
const projects = projectsQuery.data?.items ?? [];
|
const projects = projectsQuery.data?.items ?? [];
|
||||||
const allPackages = allPackagesQuery.data?.items ?? [];
|
const allPackages = allPackagesQuery.data?.items ?? [];
|
||||||
const segments = segmentsQuery.data?.items ?? [];
|
const segments = segmentsQuery.data?.items ?? [];
|
||||||
@@ -139,6 +157,36 @@ export default function SegmentPage() {
|
|||||||
canSubmit={canSubmit}
|
canSubmit={canSubmit}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
open={Boolean(segmentToDelete)}
|
||||||
|
onOpenChange={handleDeleteDialogOpenChange}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Delete segment?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will permanently delete{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{segmentToDelete?.segment_name}
|
||||||
|
</span>
|
||||||
|
. This action cannot be undone.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={deleteSegmentMutation.isPending}>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
variant="destructive"
|
||||||
|
disabled={deleteSegmentMutation.isPending}
|
||||||
|
onClick={confirmDeleteSegment}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FormField, PhoneInput, SelectPopover } from '@/components/form';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -98,12 +99,12 @@ export function TenantSheet({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-3xl">
|
<DialogContent className="sm:max-w-3xl">
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{tenantId ? 'Edit Tenant' : 'Create Tenant'}
|
{tenantId ? 'Edit Tenant' : 'Create Tenant'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Bind a client and subscription plan, then invite the tenant
|
Bind a client and subscription plan, then invite the tenant
|
||||||
administrator.
|
administrator.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -114,7 +115,7 @@ export function TenantSheet({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[50vh] space-y-5 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-5">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p>Basic Information</p>
|
<p>Basic Information</p>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
@@ -227,7 +228,7 @@ export function TenantSheet({
|
|||||||
id="tenant-description"
|
id="tenant-description"
|
||||||
placeholder="North zone operations"
|
placeholder="North zone operations"
|
||||||
{...register('description')}
|
{...register('description')}
|
||||||
className="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 outline-none focus-visible:border-ring"
|
className="min-h-20 w-full rounded-lg border border-input bg-background px-3 py-2 outline-none focus-visible:border-ring"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,7 +315,7 @@ export function TenantSheet({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : adminEmail ? (
|
) : adminEmail ? (
|
||||||
<div className="rounded-md border bg-muted/40 p-4 text-muted-foreground">
|
<div className="rounded-lg border bg-muted/40 p-4 text-muted-foreground">
|
||||||
Admin invitation details cannot be changed after tenant
|
Admin invitation details cannot be changed after tenant
|
||||||
creation.
|
creation.
|
||||||
<p className="mt-1 text-foreground">
|
<p className="mt-1 text-foreground">
|
||||||
@@ -322,9 +323,9 @@ export function TenantSheet({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export function TicketClassContentSkeleton() {
|
|||||||
<Skeleton className="h-5 w-40" />
|
<Skeleton className="h-5 w-40" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
<CardContent className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||||
<Skeleton className="aspect-video w-full rounded-md" />
|
<Skeleton className="aspect-video w-full rounded-lg" />
|
||||||
<Skeleton className="aspect-video w-full rounded-md" />
|
<Skeleton className="aspect-video w-full rounded-lg" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export function TicketClassContentSkeleton() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Skeleton className="size-5 rounded" />
|
<Skeleton className="size-5 rounded-lg" />
|
||||||
<Skeleton className="h-5 w-56" />
|
<Skeleton className="h-5 w-56" />
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -89,7 +89,7 @@ export function TicketClassActionsSkeleton() {
|
|||||||
<Card aria-label="Loading defect class actions">
|
<Card aria-label="Loading defect class actions">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Skeleton className="size-4 rounded" />
|
<Skeleton className="size-4 rounded-lg" />
|
||||||
<Skeleton className="h-5 w-32" />
|
<Skeleton className="h-5 w-32" />
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export function TicketClassDetectionPreview({
|
|||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex size-10 shrink-0 items-center justify-center rounded-full',
|
'flex size-10 shrink-0 self-start items-center justify-center rounded-lg',
|
||||||
visual.cardClassName,
|
visual.cardClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -157,13 +157,13 @@ export function TicketClassDetectionPreview({
|
|||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 space-y-1">
|
<div className="min-w-0 space-y-1">
|
||||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||||
<h3 className="truncate text-lg font-semibold text-foreground">
|
<h3 >
|
||||||
{displayName}
|
{displayName}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-full border-0 px-2.5 py-1 text-xs font-semibold',
|
'rounded-lg',
|
||||||
visual.cardClassName,
|
visual.cardClassName,
|
||||||
visual.colorClassName,
|
visual.colorClassName,
|
||||||
)}
|
)}
|
||||||
@@ -171,7 +171,7 @@ export function TicketClassDetectionPreview({
|
|||||||
{detectionCount} Detections
|
{detectionCount} Detections
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Review detections in ascending timestamp order.
|
Review detections in ascending timestamp order.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,8 +180,7 @@ export function TicketClassDetectionPreview({
|
|||||||
<div className="flex items-center gap-2 self-start sm:self-auto">
|
<div className="flex items-center gap-2 self-start sm:self-auto">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
|
||||||
onClick={() => setCurrentIndex((index) => Math.max(index - 1, 0))}
|
onClick={() => setCurrentIndex((index) => Math.max(index - 1, 0))}
|
||||||
disabled={isPreviousDisabled}
|
disabled={isPreviousDisabled}
|
||||||
aria-label="Previous detection"
|
aria-label="Previous detection"
|
||||||
@@ -195,8 +194,7 @@ export function TicketClassDetectionPreview({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
|
||||||
onClick={() => setCurrentIndex((index) => index + 1)}
|
onClick={() => setCurrentIndex((index) => index + 1)}
|
||||||
disabled={isNextDisabled}
|
disabled={isNextDisabled}
|
||||||
aria-label="Next detection"
|
aria-label="Next detection"
|
||||||
@@ -233,7 +231,7 @@ export function TicketClassDetectionPreview({
|
|||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 items-start gap-4 xl:grid-cols-2">
|
<div className="grid grid-cols-1 items-start gap-4 xl:grid-cols-2">
|
||||||
<div className="min-w-0 space-y-2">
|
<div className="min-w-0 space-y-2">
|
||||||
<p className="text-xs font-medium text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Detection Image
|
Detection Image
|
||||||
</p>
|
</p>
|
||||||
<div className="overflow-hidden rounded-lg">
|
<div className="overflow-hidden rounded-lg">
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ export function TicketDefectClassTabs({
|
|||||||
|
|
||||||
if (defectClassesQuery.isLoading) {
|
if (defectClassesQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
<Card className="w-full">
|
<Card className="w-full rounded-lg">
|
||||||
<CardContent className="space-y-4 p-5">
|
<CardContent className="space-y-4 p-5">
|
||||||
<div className="h-5 w-24 animate-pulse rounded bg-muted" />
|
<div className="h-5 w-24 animate-pulse rounded-lg bg-muted" />
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -104,7 +104,7 @@ export function TicketDefectClassTabs({
|
|||||||
>
|
>
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardContent className="grid gap-0 p-0 lg:grid-cols-[minmax(220px,280px)_1fr] lg:divide-x lg:divide-border">
|
<CardContent className="grid gap-0 p-0 lg:grid-cols-[minmax(220px,280px)_1fr] lg:divide-x lg:divide-border">
|
||||||
<div className="space-y-4 p-5">
|
<div className="space-y-4 px-5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ListChecks className="size-5 shrink-0 text-primary" />
|
<ListChecks className="size-5 shrink-0 text-primary" />
|
||||||
<h2 className="text-base font-semibold">Detected Issues</h2>
|
<h2 className="text-base font-semibold">Detected Issues</h2>
|
||||||
@@ -126,7 +126,7 @@ export function TicketDefectClassTabs({
|
|||||||
key={item.class_name}
|
key={item.class_name}
|
||||||
value={item.class_name}
|
value={item.class_name}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group h-auto w-full flex-none items-center justify-between! gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2.5 text-sm text-muted-foreground shadow-none after:hidden',
|
'group h-auto w-full flex-none items-center justify-between! gap-2 rounded-lg border bg-muted/30 px-3 py-2.5 text-sm text-muted-foreground shadow-none after:hidden',
|
||||||
'data-[state=active]:bg-muted data-[state=active]:text-foreground',
|
'data-[state=active]:bg-muted data-[state=active]:text-foreground',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -135,7 +135,7 @@ export function TicketDefectClassTabs({
|
|||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
'shrink-0 gap-1.5 rounded-full border-0 px-2.5 py-1 text-xs font-medium',
|
'shrink-0 rounded-full',
|
||||||
statusConfig.badgeClassName,
|
statusConfig.badgeClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -148,7 +148,7 @@ export function TicketDefectClassTabs({
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0 p-5">
|
<div className="min-w-0 px-5">
|
||||||
<TicketClassDetectionPreview
|
<TicketClassDetectionPreview
|
||||||
videoId={previewVideoId}
|
videoId={previewVideoId}
|
||||||
defectClassName={selectedClass}
|
defectClassName={selectedClass}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ export function TicketDetailHeader({
|
|||||||
|
|
||||||
<div className="flex shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
|
||||||
onClick={() => router.push('/ticket')}
|
onClick={() => router.push('/ticket')}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="size-4" />
|
<ArrowLeft className="size-4" />
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function HistoryNote({ item }: { item: TicketHistoryItem }) {
|
|||||||
if (!noteText) return null;
|
if (!noteText) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-3 rounded-lg border border-amber-300/80 bg-amber-50/90 px-3 py-2.5 dark:border-amber-500/30 dark:bg-amber-500/10">
|
<div className="mt-3 rounded-lg border border-amber-300/80 bg-amber-50/90 px-3 py-2.5 dark:border-amber-500/30 dark:bg-amber-500/10">
|
||||||
<div className="flex items-center gap-1.5 text-xs font-medium text-amber-900 dark:text-amber-200">
|
<div className="flex items-center gap-1.5 text-xs font-medium text-amber-900 dark:text-amber-200">
|
||||||
<MessageSquareText className="size-3.5 shrink-0 text-amber-700 dark:text-amber-300" />
|
<MessageSquareText className="size-3.5 shrink-0 text-amber-700 dark:text-amber-300" />
|
||||||
<span>
|
<span>
|
||||||
@@ -48,10 +48,10 @@ function SystemHistoryEntry({ item }: { item: TicketHistoryItem }) {
|
|||||||
const noteText = item.note?.text?.trim();
|
const noteText = item.note?.text?.trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-dashed border-primary/25 bg-primary/5 px-4 py-3.5">
|
<div className="rounded-lg border border-dashed border-primary/25 bg-primary/5 px-4 py-3.5">
|
||||||
<Badge
|
<Badge
|
||||||
|
|
||||||
className="rounded-xs px-2.5 py-0.5 text-[10px] font-semibold tracking-wider uppercase"
|
className="rounded-lg px-2.5 py-0.5 text-[10px] font-semibold tracking-wider uppercase"
|
||||||
>
|
>
|
||||||
{getSourceLabel(item.source)}
|
{getSourceLabel(item.source)}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -70,7 +70,7 @@ function UserHistoryEntry({ item }: { item: TicketHistoryItem }) {
|
|||||||
const isAssigned = item.event_type === 'assigned';
|
const isAssigned = item.event_type === 'assigned';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-border bg-card p-4">
|
<div className="rounded-lg border bg-card p-4">
|
||||||
<h3 className="mb-3 text-sm font-semibold text-foreground">{item.title}</h3>
|
<h3 className="mb-3 text-sm font-semibold text-foreground">{item.title}</h3>
|
||||||
<PersonInfo person={item.actor} />
|
<PersonInfo person={item.actor} />
|
||||||
{isAssigned && item.target_user ? (
|
{isAssigned && item.target_user ? (
|
||||||
@@ -124,7 +124,7 @@ export function TicketHistoryCard({ ticket }: { ticket: TicketDetail }) {
|
|||||||
))}
|
))}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-md border border-dashed border-border/80 bg-muted/10 px-4 py-8 text-center">
|
<div className="rounded-lg border border-dashed border-border/80 bg-muted/10 px-4 py-8 text-center">
|
||||||
<p className="text-sm font-medium text-muted-foreground">
|
<p className="text-sm font-medium text-muted-foreground">
|
||||||
No timeline entries yet
|
No timeline entries yet
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
MapPin,
|
MapPin,
|
||||||
Monitor,
|
Monitor,
|
||||||
UserRound,
|
UserRound,
|
||||||
|
Play
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
@@ -77,7 +79,7 @@ function SummaryCard({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl bg-muted/40 p-4 transition-colors hover:bg-muted/60',
|
'rounded-lg bg-muted/40 p-4 hover:scale-105 transition-transform ease-in-out',
|
||||||
cardClassName,
|
cardClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -180,11 +182,10 @@ export function TicketOverviewCard({
|
|||||||
<CardAction>
|
<CardAction>
|
||||||
<PermissionGuard permissions={PERMISSIONS.TICKET.ASSIGN}>
|
<PermissionGuard permissions={PERMISSIONS.TICKET.ASSIGN}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="secondary"
|
||||||
size="sm"
|
|
||||||
className="text-primary hover:bg-primary/10 hover:text-primary"
|
|
||||||
onClick={() => setIsAnalysisOpen(true)}
|
onClick={() => setIsAnalysisOpen(true)}
|
||||||
>
|
>
|
||||||
|
<Play className="mr-2 size-4" />
|
||||||
View Video
|
View Video
|
||||||
</Button>
|
</Button>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
@@ -232,16 +233,16 @@ export function TicketOverviewCard({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Dialog open={isAnalysisOpen} onOpenChange={setIsAnalysisOpen}>
|
<Dialog open={isAnalysisOpen} onOpenChange={setIsAnalysisOpen}>
|
||||||
<DialogContent className="gap-3 p-3 sm:max-w-5xl sm:p-4">
|
<DialogContent className="sm:max-w-5xl">
|
||||||
<DialogHeader className="min-w-0 pr-8">
|
<DialogHeader>
|
||||||
<DialogTitle className="truncate text-left text-base">
|
<DialogTitle>{analysisTitle}</DialogTitle>
|
||||||
{analysisTitle}
|
<DialogDescription>
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className="truncate text-xs">
|
|
||||||
Video analysis with annotation overlay
|
Video analysis with annotation overlay
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<TicketInlineAnalysisVideo ticket={ticket} />
|
<DialogBody>
|
||||||
|
<TicketInlineAnalysisVideo ticket={ticket} />
|
||||||
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function ClosedTicketAction({ ticket }: { ticket: TicketDetail }) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-5">
|
<CardContent className="space-y-5">
|
||||||
<div className="space-y-4 rounded-xl border bg-muted/20 p-4">
|
<div className="space-y-4 rounded-lg border bg-muted/20 p-4">
|
||||||
<MetaField
|
<MetaField
|
||||||
label="Reviewed By"
|
label="Reviewed By"
|
||||||
value={<PersonInfo person={reviewer} size="lg" />}
|
value={<PersonInfo person={reviewer} size="lg" />}
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ function MediaSectionHeader({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
className="h-8 shrink-0 border-primary/30 text-primary hover:bg-primary/5 hover:text-primary"
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<Plus className="size-3.5" />
|
||||||
{addLabel}
|
{addLabel}
|
||||||
@@ -71,7 +71,7 @@ function MediaSectionHeader({
|
|||||||
|
|
||||||
function MediaHint({ children }: { children: ReactNode }) {
|
function MediaHint({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<p className="rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground">
|
<p className="rounded-lg bg-muted/50 px-3 py-2 text-xs text-muted-foreground">
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
@@ -182,7 +182,7 @@ function VideoThumbnail({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{duration ? (
|
{duration ? (
|
||||||
<span className="pointer-events-none absolute bottom-1.5 right-1.5 rounded bg-black/75 px-1.5 py-0.5 text-[10px] font-medium text-white">
|
<span className="pointer-events-none absolute bottom-1.5 right-1.5 rounded-lg bg-black/75 px-1.5 py-0.5 text-[10px] font-medium text-white">
|
||||||
{duration}
|
{duration}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -382,7 +382,7 @@ export function RepairEvidenceForm({
|
|||||||
description="Tap to upload repair photos"
|
description="Tap to upload repair photos"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onClick={openImagePicker}
|
onClick={openImagePicker}
|
||||||
className="min-h-[104px]"
|
className="min-h-26"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex min-w-min gap-3">
|
<div className="flex min-w-min gap-3">
|
||||||
@@ -457,7 +457,7 @@ export function RepairEvidenceForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function ReviewRepairAction({ ticket }: { ticket: TicketDetail }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
variant={'secondary'}
|
||||||
disabled={!reviewComment || reviewRepairMutation.isPending}
|
disabled={!reviewComment || reviewRepairMutation.isPending}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
reviewRepairMutation.mutate({
|
reviewRepairMutation.mutate({
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function TicketActionCard({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Icon className={cn('size-4 text-primary', iconClassName)} />
|
<Icon className={cn('text-primary', iconClassName)} />
|
||||||
{title}
|
{title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ export function RepairProofGallery({ imageUrls }: RepairProofGalleryProps) {
|
|||||||
|
|
||||||
if (queries.some((query) => query.isLoading)) {
|
if (queries.some((query) => query.isLoading)) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 gap-1 overflow-hidden rounded-xl">
|
<div className="grid grid-cols-4 gap-1 overflow-hidden rounded-lg">
|
||||||
{imageUrls.slice(0, MAX_VISIBLE_IMAGES).map((url, index) => (
|
{imageUrls.slice(0, MAX_VISIBLE_IMAGES).map((url, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${url}-${index}`}
|
key={`${url}-${index}`}
|
||||||
className="aspect-square rounded-none"
|
className="aspect-square rounded-lg"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@ export function RepairProofGallery({ imageUrls }: RepairProofGalleryProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LightGallery
|
<LightGallery
|
||||||
elementClassNames="grid grid-cols-4 gap-1 overflow-hidden rounded-xl"
|
elementClassNames="grid grid-cols-4 gap-1 overflow-hidden rounded-lg"
|
||||||
mode="lg-fade"
|
mode="lg-fade"
|
||||||
speed={400}
|
speed={400}
|
||||||
plugins={[lgThumbnail, lgZoom]}
|
plugins={[lgThumbnail, lgZoom]}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CheckCircle2, HardHat, Wrench } from 'lucide-react';
|
import { CheckCircle2, HardHat, Wrench, ToolCase } from 'lucide-react';
|
||||||
|
|
||||||
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -72,7 +72,7 @@ export function RepairProofSummary({ ticket }: { ticket: TicketDetail }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Wrench className="size-4 text-primary" />
|
<ToolCase className="text-primary" />
|
||||||
Repair Proof
|
Repair Proof
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Loader2, Play } from 'lucide-react';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -89,7 +90,7 @@ export function CreateUploadDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-5xl"
|
className="sm:max-w-5xl"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -99,7 +100,7 @@ export function CreateUploadDialog({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-5">
|
<DialogBody className="space-y-5">
|
||||||
<LocationSection
|
<LocationSection
|
||||||
value={session}
|
value={session}
|
||||||
onChange={setSession}
|
onChange={setSession}
|
||||||
@@ -128,11 +129,11 @@ export function CreateUploadDialog({
|
|||||||
portalContainer={dropdownPortalRef}
|
portalContainer={dropdownPortalRef}
|
||||||
/>
|
/>
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function UploadFileDropzone({
|
|||||||
handleFiles(event.dataTransfer.files);
|
handleFiles(event.dataTransfer.files);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group flex min-h-32 cursor-pointer flex-col justify-between rounded-md border border-dashed bg-card/60 p-4 transition-colors',
|
'group flex min-h-32 cursor-pointer flex-col justify-between rounded-lg border border-dashed bg-card/60 p-4 transition-colors',
|
||||||
'hover:border-primary/60 hover:bg-secondary/40',
|
'hover:border-primary/60 hover:bg-secondary/40',
|
||||||
isDragging && 'border-primary bg-secondary/60',
|
isDragging && 'border-primary bg-secondary/60',
|
||||||
disabled && 'pointer-events-none cursor-not-allowed opacity-60',
|
disabled && 'pointer-events-none cursor-not-allowed opacity-60',
|
||||||
@@ -72,7 +72,7 @@ export function UploadFileDropzone({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex size-9 items-center justify-center rounded-md border bg-background">
|
<div className="flex size-9 items-center justify-center rounded-lg border bg-background">
|
||||||
<UploadCloud className="size-4 text-muted-foreground" />
|
<UploadCloud className="size-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function useUploadListColumns(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex aspect-video w-20 items-center justify-center overflow-hidden rounded-md border bg-muted bg-cover bg-center"
|
className="flex aspect-video w-20 items-center justify-center overflow-hidden rounded-lg border bg-muted bg-cover bg-center"
|
||||||
style={
|
style={
|
||||||
thumbnail
|
thumbnail
|
||||||
? { backgroundImage: `url("${thumbnail}")` }
|
? { backgroundImage: `url("${thumbnail}")` }
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import type { UploadListItem } from '@/types';
|
import type { UploadListItem } from '@/types';
|
||||||
|
|
||||||
interface VideoPreviewDialogProps {
|
interface VideoPreviewDialogProps {
|
||||||
@@ -22,11 +28,13 @@ export function VideoPreviewDialog({
|
|||||||
<DialogTitle>Video Preview</DialogTitle>
|
<DialogTitle>Video Preview</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<ProtectedVideoPlayer
|
<DialogBody>
|
||||||
url={open ? upload?.raw_video_url : undefined}
|
<ProtectedVideoPlayer
|
||||||
className="rounded-lg"
|
url={open ? upload?.raw_video_url : undefined}
|
||||||
unavailableTitle="Video preview unavailable"
|
className="rounded-lg"
|
||||||
/>
|
unavailableTitle="Video preview unavailable"
|
||||||
|
/>
|
||||||
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { RoleSelect } from '@/components/lookups/RoleSelect';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -67,13 +68,13 @@ export function UserSheet({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-xl">
|
<DialogContent className="sm:max-w-xl">
|
||||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
<DialogTitle>
|
||||||
{userId ? 'Edit User' : 'Create User'}
|
{userId ? 'Edit User' : 'Create User'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
Assign user details and a role.
|
Assign user details and a role.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -83,7 +84,7 @@ export function UserSheet({
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
>
|
>
|
||||||
<div className="max-h-[50vh] space-y-5 overflow-y-auto px-6 py-4">
|
<DialogBody className="space-y-5">
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
id="first-name"
|
id="first-name"
|
||||||
@@ -169,12 +170,12 @@ export function UserSheet({
|
|||||||
type="hidden"
|
type="hidden"
|
||||||
{...register('role_id')}
|
{...register('role_id')}
|
||||||
value={roleId}
|
value={roleId}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.225rem;
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import Image from 'next/image';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
sm: 'size-8 rounded-md border-2 p-0.5',
|
sm: 'size-8 rounded-lg border-2 p-0.5',
|
||||||
md: 'size-12 rounded-md border-2 p-1.5',
|
md: 'size-12 rounded-lg border-2 p-1.5',
|
||||||
lg: 'size-28 rounded-lg border-[5px] p-3',
|
lg: 'size-28 rounded-lg border-[5px] p-3',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@@ -27,7 +28,9 @@ export function BreadcrumbBasic() {
|
|||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
<BreadcrumbLink asChild>
|
||||||
|
<Link href="/">Home</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{segments.length > 0 && <BreadcrumbSeparator />}
|
{segments.length > 0 && <BreadcrumbSeparator />}
|
||||||
|
|
||||||
@@ -44,8 +47,8 @@ export function BreadcrumbBasic() {
|
|||||||
{isLast ? (
|
{isLast ? (
|
||||||
<BreadcrumbPage>{formatSegment(segment)}</BreadcrumbPage>
|
<BreadcrumbPage>{formatSegment(segment)}</BreadcrumbPage>
|
||||||
) : (
|
) : (
|
||||||
<BreadcrumbLink href={href}>
|
<BreadcrumbLink asChild>
|
||||||
{formatSegment(segment)}
|
<Link href={href}>{formatSegment(segment)}</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
)}
|
)}
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function AuthBackground({ className }: AuthBackgroundProps) {
|
|||||||
<div className="mb-9 inline-flex items-center gap-5 text-left">
|
<div className="mb-9 inline-flex items-center gap-5 text-left">
|
||||||
<LogoWrapper
|
<LogoWrapper
|
||||||
alt=""
|
alt=""
|
||||||
className="size-28 rounded-2xl border-[5px] p-4"
|
className="size-28 rounded-lg border-[5px] p-4"
|
||||||
priority
|
priority
|
||||||
variant="glass"
|
variant="glass"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const TableHeader = <TData, _>({ table }: { table: Table<TData> }) => {
|
|||||||
onMouseDown={header.getResizeHandler()}
|
onMouseDown={header.getResizeHandler()}
|
||||||
onTouchStart={header.getResizeHandler()}
|
onTouchStart={header.getResizeHandler()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute -right-2 top-0 h-full w-1 cursor-col-resize touch-none select-none rounded bg-border opacity-0 transition-opacity hover:opacity-100',
|
'absolute -right-2 top-0 h-full w-1 cursor-col-resize touch-none select-none rounded-lg bg-border opacity-0 transition-opacity hover:opacity-100',
|
||||||
header.column.getIsResizing() && 'opacity-100',
|
header.column.getIsResizing() && 'opacity-100',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ function DataTableContent<TData, TValue>({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="overflow-hidden rounded-lg border border-border bg-background">
|
<div className="overflow-hidden rounded-lg border bg-background">
|
||||||
<Table
|
<Table
|
||||||
containerClassName="max-h-[calc(100vh-350px)] overflow-auto"
|
containerClassName="max-h-[calc(100vh-350px)] overflow-auto"
|
||||||
className="table-fixed border-separate border-spacing-0"
|
className="table-fixed border-separate border-spacing-0"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ interface MultiSelectPopoverProps {
|
|||||||
|
|
||||||
function checkboxClass(checked: boolean) {
|
function checkboxClass(checked: boolean) {
|
||||||
return cn(
|
return cn(
|
||||||
'flex size-4 shrink-0 items-center justify-center rounded border',
|
'flex size-4 shrink-0 items-center justify-center rounded-lg border',
|
||||||
checked && 'border-primary bg-primary text-primary-foreground',
|
checked && 'border-primary bg-primary text-primary-foreground',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ export function MultiSelectPopover({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={toggleAll}
|
onClick={toggleAll}
|
||||||
aria-label={`Toggle all ${label}`}
|
aria-label={`Toggle all ${label}`}
|
||||||
className="shrink-0 cursor-pointer rounded-md p-0.5 hover:bg-muted"
|
className="shrink-0 cursor-pointer rounded-lg p-0.5 hover:bg-muted"
|
||||||
>
|
>
|
||||||
<span className={checkboxClass(areAllSelected)}>
|
<span className={checkboxClass(areAllSelected)}>
|
||||||
{areAllSelected ? <Check className="size-3" /> : null}
|
{areAllSelected ? <Check className="size-3" /> : null}
|
||||||
@@ -128,7 +128,7 @@ export function MultiSelectPopover({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className="flex items-center gap-2 rounded-md px-2 py-1.5 hover:bg-muted"
|
className="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-muted"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function SelectPopover({
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-muted',
|
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm hover:bg-muted',
|
||||||
option.disabled && 'cursor-not-allowed opacity-60',
|
option.disabled && 'cursor-not-allowed opacity-60',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function CountryCombobox({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="h-9 shrink-0 gap-1 rounded-none border-r px-2.5 shadow-none hover:bg-transparent"
|
className="h-9 shrink-0 gap-1 rounded-lg border-r px-2.5 shadow-none hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<CountryFlag country={value} />
|
<CountryFlag country={value} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function CountryFlag({ country }: { country: CountryCode }) {
|
|||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={cn(
|
className={cn(
|
||||||
'flag inline-block h-3.75 w-5.5 shrink-0 rounded-[2px] bg-size-[100%_auto]',
|
'flag inline-block h-3.75 w-5.5 shrink-0 rounded-lg bg-size-[100%_auto]',
|
||||||
`flag-${country.toLowerCase()}`,
|
`flag-${country.toLowerCase()}`,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function PhoneInput({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30',
|
'flex h-9 w-full min-w-0 rounded-lg border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30',
|
||||||
ariaInvalid &&
|
ariaInvalid &&
|
||||||
'border-destructive focus-within:border-destructive focus-within:ring-destructive/20',
|
'border-destructive focus-within:border-destructive focus-within:ring-destructive/20',
|
||||||
disabled && 'cursor-not-allowed opacity-50',
|
disabled && 'cursor-not-allowed opacity-50',
|
||||||
@@ -80,7 +80,7 @@ export function PhoneInput({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
aria-invalid={ariaInvalid}
|
aria-invalid={ariaInvalid}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="h-9 flex-1 rounded-none border-0 bg-transparent px-3 py-1 shadow-none focus-visible:ring-0"
|
className="h-9 flex-1 rounded-lg border-0 bg-transparent px-3 py-1 shadow-none focus-visible:ring-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default function DetectionLocationMap({
|
|||||||
return (
|
return (
|
||||||
<div className={cn('space-y-2', className)}>
|
<div className={cn('space-y-2', className)}>
|
||||||
{title ? (
|
{title ? (
|
||||||
<p className="text-xs font-medium text-muted-foreground">{title}</p>
|
<p className="text-muted-foreground">{title}</p>
|
||||||
) : null}
|
) : null}
|
||||||
{mapContent}
|
{mapContent}
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +127,7 @@ export default function DetectionLocationMap({
|
|||||||
<Card className={cn('overflow-hidden', className)}>
|
<Card className={cn('overflow-hidden', className)}>
|
||||||
<CardHeader className="border-b pb-4">
|
<CardHeader className="border-b pb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="rounded bg-secondary p-2">
|
<div className="rounded-lg bg-secondary p-2">
|
||||||
<MapPin className="h-5 w-5 text-primary" />
|
<MapPin className="h-5 w-5 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function ProtectedVideoPlayer({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex aspect-video w-full flex-col items-center justify-center gap-3 rounded-md border bg-muted/30 text-foreground',
|
'flex aspect-video w-full flex-col items-center justify-center gap-3 rounded-lg border bg-muted/30 text-foreground',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -69,7 +69,7 @@ export function ProtectedVideoPlayer({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'group relative overflow-hidden rounded-md border bg-black/90',
|
'group relative overflow-hidden rounded-lg border bg-black/90',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -84,7 +84,7 @@ export function ProtectedVideoPlayer({
|
|||||||
</video>
|
</video>
|
||||||
|
|
||||||
{label ? (
|
{label ? (
|
||||||
<span className="absolute top-3 left-3 rounded bg-black/65 px-2.5 py-1 text-[11px] font-semibold text-white backdrop-blur-sm">
|
<span className="absolute top-3 left-3 rounded-lg bg-black/65 px-2.5 py-1 text-[11px] font-semibold text-white backdrop-blur-sm">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -110,7 +110,7 @@ function VideoState({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex aspect-video flex-col items-center justify-center rounded-md border border-dashed border-border/80 bg-muted/10 px-4 py-8 text-center',
|
'flex aspect-video flex-col items-center justify-center rounded-lg border border-dashed border-border/80 bg-muted/10 px-4 py-8 text-center',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
199
src/components/ui/alert-dialog.tsx
Normal file
199
src/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
|
function AlertDialog({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||||
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
data-slot="alert-dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogContent({
|
||||||
|
className,
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
||||||
|
size?: "default" | "sm"
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
data-slot="alert-dialog-content"
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-lg bg-popover p-6 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogHeader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-header"
|
||||||
|
className={cn(
|
||||||
|
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogFooter({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogMedia({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-media"
|
||||||
|
className={cn(
|
||||||
|
"mb-2 inline-flex size-16 items-center justify-center rounded-lg bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
data-slot="alert-dialog-title"
|
||||||
|
className={cn(
|
||||||
|
"font-heading text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
data-slot="alert-dialog-description"
|
||||||
|
className={cn(
|
||||||
|
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogAction({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
||||||
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
|
return (
|
||||||
|
<Button variant={variant} size={size} asChild>
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
data-slot="alert-dialog-action"
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogCancel({
|
||||||
|
className,
|
||||||
|
variant = "outline",
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
||||||
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
|
return (
|
||||||
|
<Button variant={variant} size={size} asChild>
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
data-slot="alert-dialog-cancel"
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogMedia,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-lg border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -22,15 +22,15 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default:
|
default:
|
||||||
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
xs: "h-6 gap-1 rounded-lg px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
sm: "h-8 gap-1 rounded-lg px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
||||||
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
"icon-xs":
|
"icon-xs":
|
||||||
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
|
"size-6 rounded-lg in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||||
"icon-sm":
|
"icon-sm":
|
||||||
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
|
"size-8 rounded-lg in-data-[slot=button-group]:rounded-lg",
|
||||||
"icon-lg": "size-10",
|
"icon-lg": "size-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const PICKER_HEADER_CLASS =
|
|||||||
'grid min-h-8 grid-cols-[2rem_1fr_2rem] items-center gap-1 border-b border-border pb-2';
|
'grid min-h-8 grid-cols-[2rem_1fr_2rem] items-center gap-1 border-b border-border pb-2';
|
||||||
const PICKER_NAV_BUTTON_CLASS = 'size-8 shrink-0 p-0';
|
const PICKER_NAV_BUTTON_CLASS = 'size-8 shrink-0 p-0';
|
||||||
const GRID_BUTTON_CLASS =
|
const GRID_BUTTON_CLASS =
|
||||||
'h-9 w-full rounded-md font-medium text-foreground hover:bg-accent/60 hover:text-accent-foreground';
|
'h-9 w-full rounded-lg font-medium text-foreground hover:bg-accent/60 hover:text-accent-foreground';
|
||||||
const GRID_BUTTON_ACTIVE_CLASS =
|
const GRID_BUTTON_ACTIVE_CLASS =
|
||||||
'bg-muted text-foreground hover:bg-muted hover:text-foreground';
|
'bg-muted text-foreground hover:bg-muted hover:text-foreground';
|
||||||
const GRID_BUTTON_DISABLED_CLASS =
|
const GRID_BUTTON_DISABLED_CLASS =
|
||||||
@@ -134,7 +134,7 @@ function PickerNav({
|
|||||||
{onLabelClick ? (
|
{onLabelClick ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-sm px-2 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="rounded-lg px-2 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
onClick={onLabelClick}
|
onClick={onLabelClick}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -277,7 +277,7 @@ function CalendarMonthCaption({
|
|||||||
<div className="flex items-center justify-center gap-1 text-sm font-semibold">
|
<div className="flex items-center justify-center gap-1 text-sm font-semibold">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-sm px-1 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="rounded-lg px-1 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPickerYear(year);
|
setPickerYear(year);
|
||||||
setView('months');
|
setView('months');
|
||||||
@@ -287,7 +287,7 @@ function CalendarMonthCaption({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-sm px-1 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="rounded-lg px-1 text-center text-sm font-semibold text-foreground hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDecadeStart(Math.floor(year / 10) * 10);
|
setDecadeStart(Math.floor(year / 10) * 10);
|
||||||
setPickerYear(year);
|
setPickerYear(year);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function Calendar({
|
|||||||
defaultClassNames.dropdowns
|
defaultClassNames.dropdowns
|
||||||
),
|
),
|
||||||
dropdown_root: cn(
|
dropdown_root: cn(
|
||||||
"relative rounded-(--cell-radius)",
|
"relative rounded-lg",
|
||||||
defaultClassNames.dropdown_root
|
defaultClassNames.dropdown_root
|
||||||
),
|
),
|
||||||
dropdown: cn(
|
dropdown: cn(
|
||||||
@@ -84,13 +84,13 @@ function Calendar({
|
|||||||
"font-medium select-none",
|
"font-medium select-none",
|
||||||
captionLayout === "label"
|
captionLayout === "label"
|
||||||
? "text-sm"
|
? "text-sm"
|
||||||
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
: "flex items-center gap-1 rounded-lg text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
||||||
defaultClassNames.caption_label
|
defaultClassNames.caption_label
|
||||||
),
|
),
|
||||||
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
|
"flex-1 rounded-lg text-[0.8rem] font-normal text-muted-foreground select-none",
|
||||||
defaultClassNames.weekday
|
defaultClassNames.weekday
|
||||||
),
|
),
|
||||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||||
@@ -103,23 +103,23 @@ function Calendar({
|
|||||||
defaultClassNames.week_number
|
defaultClassNames.week_number
|
||||||
),
|
),
|
||||||
day: cn(
|
day: cn(
|
||||||
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
|
"group/day relative aspect-square h-full w-full rounded-lg p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-lg",
|
||||||
props.showWeekNumber
|
props.showWeekNumber
|
||||||
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-lg"
|
||||||
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
|
: "[&:first-child[data-selected=true]_button]:rounded-lg",
|
||||||
defaultClassNames.day
|
defaultClassNames.day
|
||||||
),
|
),
|
||||||
range_start: cn(
|
range_start: cn(
|
||||||
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
|
"relative isolate z-0 rounded-lg bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
|
||||||
defaultClassNames.range_start
|
defaultClassNames.range_start
|
||||||
),
|
),
|
||||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
range_middle: cn("rounded-lg", defaultClassNames.range_middle),
|
||||||
range_end: cn(
|
range_end: cn(
|
||||||
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
|
"relative isolate z-0 rounded-lg bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
|
||||||
defaultClassNames.range_end
|
defaultClassNames.range_end
|
||||||
),
|
),
|
||||||
today: cn(
|
today: cn(
|
||||||
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
|
"rounded-lg bg-muted text-foreground data-[selected=true]:rounded-lg",
|
||||||
defaultClassNames.today
|
defaultClassNames.today
|
||||||
),
|
),
|
||||||
outside: cn(
|
outside: cn(
|
||||||
@@ -210,7 +210,7 @@ function CalendarDayButton({
|
|||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-lg data-[range-end=true]:rounded-lg data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-lg data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-lg data-[range-start=true]:rounded-lg data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
||||||
defaultClassNames.day,
|
defaultClassNames.day,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function Card({
|
|||||||
data-slot="card"
|
data-slot="card"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-lg bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-lg *:[img:last-child]:rounded-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-lg px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
|
"flex items-center rounded-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ function ChartTooltipContent({
|
|||||||
!hideIndicator && (
|
!hideIndicator && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
"shrink-0 rounded-lg border-(--color-border) bg-(--color-bg)",
|
||||||
{
|
{
|
||||||
"h-2.5 w-2.5": indicator === "dot",
|
"h-2.5 w-2.5": indicator === "dot",
|
||||||
"w-1": indicator === "line",
|
"w-1": indicator === "line",
|
||||||
@@ -313,7 +313,7 @@ function ChartLegendContent({
|
|||||||
<itemConfig.icon />
|
<itemConfig.icon />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
className="h-2 w-2 shrink-0 rounded-lg"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: item.color,
|
backgroundColor: item.color,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ function ComboboxContent({
|
|||||||
<ComboboxPrimitive.Popup
|
<ComboboxPrimitive.Popup
|
||||||
data-slot="combobox-content"
|
data-slot="combobox-content"
|
||||||
data-chips={!!anchor}
|
data-chips={!!anchor}
|
||||||
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</ComboboxPrimitive.Positioner>
|
</ComboboxPrimitive.Positioner>
|
||||||
@@ -145,7 +145,7 @@ function ComboboxItem({
|
|||||||
<ComboboxPrimitive.Item
|
<ComboboxPrimitive.Item
|
||||||
data-slot="combobox-item"
|
data-slot="combobox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex w-full cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -226,7 +226,7 @@ function ComboboxChips({
|
|||||||
<ComboboxPrimitive.Chips
|
<ComboboxPrimitive.Chips
|
||||||
data-slot="combobox-chips"
|
data-slot="combobox-chips"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -246,7 +246,7 @@ function ComboboxChip({
|
|||||||
<ComboboxPrimitive.Chip
|
<ComboboxPrimitive.Chip
|
||||||
data-slot="combobox-chip"
|
data-slot="combobox-chip"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-lg bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function DialogContent({
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100vh-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg bg-popover text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -89,7 +89,17 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-header"
|
data-slot="dialog-header"
|
||||||
className={cn("flex flex-col gap-2", className)}
|
className={cn("flex shrink-0 flex-col gap-2 border-b px-6 py-4 pr-12", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogBody({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-body"
|
||||||
|
className={cn("min-h-0 flex-1 overflow-y-auto px-6 py-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -107,7 +117,7 @@ function DialogFooter({
|
|||||||
<div
|
<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 shrink-0 flex-col-reverse gap-2 border-t px-6 py-4 sm:flex-row sm:justify-end",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -153,6 +163,7 @@ function DialogDescription({
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ function DrawerContent({
|
|||||||
<DrawerPrimitive.Content
|
<DrawerPrimitive.Content
|
||||||
data-slot="drawer-content"
|
data-slot="drawer-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-sm text-popover-foreground 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-xl data-[vaul-drawer-direction=bottom]:border-t 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]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r 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]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-sm text-popover-foreground 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-lg data-[vaul-drawer-direction=bottom]:border-t 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]:rounded-lg data-[vaul-drawer-direction=left]:border-r 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]:rounded-lg data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-lg data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function DropdownMenuContent({
|
|||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
align={align}
|
align={align}
|
||||||
className={cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
className={cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
@@ -73,7 +73,7 @@ function DropdownMenuItem({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -95,7 +95,7 @@ function DropdownMenuCheckboxItem({
|
|||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 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}
|
||||||
@@ -139,7 +139,7 @@ function DropdownMenuRadioItem({
|
|||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 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}
|
||||||
@@ -226,7 +226,7 @@ function DropdownMenuSubTrigger({
|
|||||||
data-slot="dropdown-menu-sub-trigger"
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -244,7 +244,7 @@ function DropdownMenuSubContent({
|
|||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn("z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
className={cn("z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="input-group"
|
data-slot="input-group"
|
||||||
role="group"
|
role="group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
|
"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-lg border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -23,7 +23,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupAddonVariants = cva(
|
const inputGroupAddonVariants = cva(
|
||||||
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-lg [&>svg:not([class*='size-'])]:size-4",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
align: {
|
align: {
|
||||||
@@ -70,10 +70,10 @@ const inputGroupButtonVariants = cva(
|
|||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
xs: "h-6 gap-1 rounded-lg px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
||||||
sm: "",
|
sm: "",
|
||||||
"icon-xs":
|
"icon-xs":
|
||||||
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
"size-6 rounded-lg 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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,7 +122,7 @@ function InputGroupInput({
|
|||||||
<Input
|
<Input
|
||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
"flex-1 rounded-lg border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -138,7 +138,7 @@ function InputGroupTextarea({
|
|||||||
<Textarea
|
<Textarea
|
||||||
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-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
"flex-1 resize-none rounded-lg border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
"h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function PopoverContent({
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-md bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-lg bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function ScrollArea({
|
|||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport
|
<ScrollAreaPrimitive.Viewport
|
||||||
data-slot="scroll-area-viewport"
|
data-slot="scroll-area-viewport"
|
||||||
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
className="size-full rounded-lg transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -69,7 +69,7 @@ function SelectContent({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
data-align-trigger={position === "item-aligned"}
|
data-align-trigger={position === "item-aligned"}
|
||||||
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
|
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
|
||||||
position={position}
|
position={position}
|
||||||
align={align}
|
align={align}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -112,7 +112,7 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"relative flex w-full cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|||||||
<main
|
<main
|
||||||
data-slot="sidebar-inset"
|
data-slot="sidebar-inset"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-lg md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -401,7 +401,7 @@ function SidebarGroupLabel({
|
|||||||
data-slot="sidebar-group-label"
|
data-slot="sidebar-group-label"
|
||||||
data-sidebar="group-label"
|
data-sidebar="group-label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
"flex h-8 shrink-0 items-center rounded-lg px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -421,7 +421,7 @@ function SidebarGroupAction({
|
|||||||
data-slot="sidebar-group-action"
|
data-slot="sidebar-group-action"
|
||||||
data-sidebar="group-action"
|
data-sidebar="group-action"
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-lg p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -466,7 +466,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sidebarMenuButtonVariants = cva(
|
const sidebarMenuButtonVariants = cva(
|
||||||
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
|
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-lg p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -553,7 +553,7 @@ function SidebarMenuAction({
|
|||||||
data-slot="sidebar-menu-action"
|
data-slot="sidebar-menu-action"
|
||||||
data-sidebar="menu-action"
|
data-sidebar="menu-action"
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-lg p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
showOnHover &&
|
showOnHover &&
|
||||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
|
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
|
||||||
className
|
className
|
||||||
@@ -572,7 +572,7 @@ function SidebarMenuBadge({
|
|||||||
data-slot="sidebar-menu-badge"
|
data-slot="sidebar-menu-badge"
|
||||||
data-sidebar="menu-badge"
|
data-sidebar="menu-badge"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
|
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-lg px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -596,12 +596,12 @@ function SidebarMenuSkeleton({
|
|||||||
<div
|
<div
|
||||||
data-slot="sidebar-menu-skeleton"
|
data-slot="sidebar-menu-skeleton"
|
||||||
data-sidebar="menu-skeleton"
|
data-sidebar="menu-skeleton"
|
||||||
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
|
className={cn("flex h-8 items-center gap-2 rounded-lg px-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{showIcon && (
|
{showIcon && (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className="size-4 rounded-md"
|
className="size-4 rounded-lg"
|
||||||
data-sidebar="menu-skeleton-icon"
|
data-sidebar="menu-skeleton-icon"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -666,7 +666,7 @@ function SidebarMenuSubButton({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="skeleton"
|
data-slot="skeleton"
|
||||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
className={cn("animate-pulse rounded-lg bg-muted", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function Tabs({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tabsListVariants = cva(
|
const tabsListVariants = cva(
|
||||||
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-lg",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -63,7 +63,7 @@ function TabsTrigger({
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-lg border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||||
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
||||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||||||
<textarea
|
<textarea
|
||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-lg bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-lg data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-lg bg-foreground fill-foreground" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function TreeCheckbox({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={(event) => onChange(event.target.checked)}
|
onChange={(event) => onChange(event.target.checked)}
|
||||||
className="size-4 shrink-0 rounded border-border accent-primary"
|
className="size-4 shrink-0 rounded-lg border-border accent-primary"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ function CheckboxTreeNode<TItem extends TreeItem>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="space-y-2">
|
<li className="space-y-2">
|
||||||
<div className="rounded-md border border-border/60 bg-background px-3 py-2">
|
<div className="rounded-lg border border-border/60 bg-background px-3 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{hasChildren ? (
|
{hasChildren ? (
|
||||||
<button
|
<button
|
||||||
@@ -79,7 +79,7 @@ function CheckboxTreeNode<TItem extends TreeItem>({
|
|||||||
aria-label={
|
aria-label={
|
||||||
isExpanded ? `Collapse ${item.label}` : `Expand ${item.label}`
|
isExpanded ? `Collapse ${item.label}` : `Expand ${item.label}`
|
||||||
}
|
}
|
||||||
className="inline-flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:bg-muted hover:text-foreground"
|
className="inline-flex size-5 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition hover:bg-muted hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`size-4 transition-transform ${isExpanded ? '' : '-rotate-90'}`}
|
className={`size-4 transition-transform ${isExpanded ? '' : '-rotate-90'}`}
|
||||||
@@ -155,7 +155,7 @@ export function CheckboxTree<TItem extends TreeItem>({
|
|||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground">
|
<div className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">
|
||||||
{emptyMessage}
|
{emptyMessage}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,17 +47,17 @@ export default function AnnotatedDetectionImage({
|
|||||||
{!detection ? (
|
{!detection ? (
|
||||||
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
||||||
<ImageIcon className="size-7" />
|
<ImageIcon className="size-7" />
|
||||||
<p className="text-sm">Select a detection to view its image.</p>
|
<p>Select a detection to view its image.</p>
|
||||||
</div>
|
</div>
|
||||||
) : isLoading ? (
|
) : isLoading ? (
|
||||||
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
||||||
<Loader2 className="size-7 animate-spin text-primary" />
|
<Loader2 className="size-7 animate-spin text-primary" />
|
||||||
<p className="text-sm">Loading detection image...</p>
|
<p>Loading detection image...</p>
|
||||||
</div>
|
</div>
|
||||||
) : isError || !objectUrl ? (
|
) : isError || !objectUrl ? (
|
||||||
<div className="flex flex-col items-center gap-2 text-destructive">
|
<div className="flex flex-col items-center gap-2 text-destructive">
|
||||||
<AlertTriangle className="size-7" />
|
<AlertTriangle className="size-7" />
|
||||||
<p className="text-sm">Failed to load the detection image.</p>
|
<p>Failed to load the detection image.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function CurrentDetectionBar({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{coordinates ? (
|
{coordinates ? (
|
||||||
<div className="flex max-w-full items-center gap-2 rounded-md bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground">
|
<div className="flex max-w-full items-center gap-2 rounded-lg bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground">
|
||||||
<MapPin className="size-3.5 shrink-0" />
|
<MapPin className="size-3.5 shrink-0" />
|
||||||
<span className="shrink-0 font-medium">Location</span>
|
<span className="shrink-0 font-medium">Location</span>
|
||||||
<span className="truncate font-mono font-semibold text-foreground">
|
<span className="truncate font-mono font-semibold text-foreground">
|
||||||
@@ -63,11 +63,11 @@ export default function CurrentDetectionBar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.class_name}
|
key={item.class_name}
|
||||||
className="flex items-center justify-between gap-3 rounded-md bg-muted/25 px-3 py-2.5 ring-1 ring-border/60"
|
className="flex items-center justify-between gap-3 rounded-lg bg-muted/25 px-3 py-2.5 ring-1 ring-border/60"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<span
|
<span
|
||||||
className={`flex size-8 shrink-0 items-center justify-center rounded-md ${visual.iconClassName}`}
|
className={`flex size-8 shrink-0 items-center justify-center rounded-lg ${visual.iconClassName}`}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
@@ -84,9 +84,9 @@ export default function CurrentDetectionBar({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/25 px-3 py-2.5 ring-1 ring-border/60">
|
<div className="flex items-center justify-between gap-3 rounded-lg bg-muted/25 px-3 py-2.5 ring-1 ring-border/60">
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-green-500/10 text-green-500">
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-green-500/10 text-green-500">
|
||||||
<Activity className="size-4" />
|
<Activity className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
<p className="truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
<p className="truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
|
|||||||
@@ -33,26 +33,26 @@ function DetectionLogThumbnail({
|
|||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return (
|
return (
|
||||||
<div className="flex size-20 shrink-0 items-center justify-center rounded-md border border-dashed border-border/70 bg-muted/20 text-muted-foreground/60">
|
<div className="flex size-20 shrink-0 items-center justify-center rounded-lg border border-dashed border-border/70 bg-muted/20 text-muted-foreground/60">
|
||||||
<ImageIcon className="size-5" />
|
<ImageIcon className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Skeleton className="size-20 shrink-0 rounded-md" />;
|
return <Skeleton className="size-20 shrink-0 rounded-lg" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError || !objectUrl) {
|
if (isError || !objectUrl) {
|
||||||
return (
|
return (
|
||||||
<div className="flex size-20 shrink-0 items-center justify-center rounded-md border border-dashed bg-muted/20 text-[10px] text-muted-foreground">
|
<div className="flex size-20 shrink-0 items-center justify-center rounded-lg border border-dashed bg-muted/20 text-[10px] text-muted-foreground">
|
||||||
N/A
|
N/A
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-20 shrink-0 overflow-hidden rounded-md border bg-muted/20">
|
<div className="size-20 shrink-0 overflow-hidden rounded-lg border bg-muted/20">
|
||||||
<img
|
<img
|
||||||
src={objectUrl}
|
src={objectUrl}
|
||||||
alt={label}
|
alt={label}
|
||||||
@@ -102,7 +102,7 @@ const DetectionLogs = ({ logs, activeLogId, onSelect }: DetectionLogsProps) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelect(log)}
|
onClick={() => onSelect(log)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border bg-card p-3 text-left transition-colors hover:border-primary',
|
'w-full rounded-lg border bg-card p-3 text-left transition-colors hover:border-primary',
|
||||||
isActive && 'border-primary bg-primary/5',
|
isActive && 'border-primary bg-primary/5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default function ResultStatsGrid({ data }: ResultStatsGridProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={stat.label}
|
key={stat.label}
|
||||||
className="rounded-xl bg-muted/40 p-4 transition-colors hover:bg-muted/60"
|
className="rounded-lg bg-muted/40 p-4 transition-colors hover:bg-muted/60"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default function VideoPlayerSection({ data }: VideoPlayerSectionProps) {
|
|||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
<CardHeader className="border-b pb-4">
|
<CardHeader className="border-b pb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="rounded bg-secondary p-2">
|
<div className="rounded-lg bg-secondary p-2">
|
||||||
<Film className="h-5 w-5 text-primary" />
|
<Film className="h-5 w-5 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user