style: refine shared UI primitives and management flows
This commit is contained in:
@@ -33,7 +33,7 @@ function ResetPasswordContent() {
|
||||
</header>
|
||||
|
||||
{!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.
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -9,7 +9,7 @@ export function ModuleShellHeader() {
|
||||
const { isMobile } = useSidebar();
|
||||
|
||||
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">
|
||||
{isMobile ? (
|
||||
<>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FormField, PhoneInput } from '@/components/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -71,12 +72,12 @@ export function ClientSheet({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-3xl">
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogContent className="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{clientId ? 'Edit Client' : 'Create Client'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Manage company and primary contact details.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -86,7 +87,7 @@ export function ClientSheet({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<FormField
|
||||
id="client-first-name"
|
||||
@@ -240,9 +241,9 @@ export function ClientSheet({
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ProjectSelect } from '@/components/lookups/ProjectSelect';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -62,14 +63,14 @@ export function PackageDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<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()}
|
||||
>
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{packageId ? 'Edit Package' : 'Create Package'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Manage package details and optional chainage range.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -79,7 +80,7 @@ export function PackageDialog({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||
@@ -173,9 +174,9 @@ export function PackageDialog({
|
||||
</FormField>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
||||
import { Package as PackageIcon, Plus } from 'lucide-react';
|
||||
|
||||
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 type { Package } from '@/types';
|
||||
|
||||
@@ -20,6 +30,7 @@ import {
|
||||
|
||||
export default function PackagePage() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [packageToDelete, setPackageToDelete] = useState<Package | null>(null);
|
||||
const { skip, setSkip, limit, setLimit } = usePackageFilters();
|
||||
const packagesQuery = usePackagesQuery({ skip, limit });
|
||||
const projectsQuery = useProjectOptionsQuery();
|
||||
@@ -67,14 +78,25 @@ export default function PackagePage() {
|
||||
|
||||
const deletePackage = useCallback(
|
||||
(pkg: Package) => {
|
||||
if (!confirm(`Are you sure you want to delete package "${pkg.name}"?`)) {
|
||||
return;
|
||||
}
|
||||
deletePackageMutation.mutate(pkg);
|
||||
setPackageToDelete(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 packages = packagesQuery.data?.items ?? [];
|
||||
const total = packagesQuery.data?.totalItems ?? 0;
|
||||
@@ -122,6 +144,36 @@ export default function PackagePage() {
|
||||
canSubmit={canSubmit}
|
||||
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">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="size-4 rounded border-border accent-primary"
|
||||
className="size-4 rounded-lg border-border accent-primary"
|
||||
{...register('is_active')}
|
||||
/>
|
||||
Active
|
||||
@@ -250,7 +250,7 @@ export function PlanSheet({
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="size-4 rounded border-border accent-primary"
|
||||
className="size-4 rounded-lg border-border accent-primary"
|
||||
{...register('is_custom')}
|
||||
/>
|
||||
Custom plan
|
||||
@@ -269,7 +269,7 @@ export function PlanSheet({
|
||||
}
|
||||
>
|
||||
{isPermissionsLoading ? (
|
||||
<div className="rounded-md border p-6 text-muted-foreground">
|
||||
<div className="rounded-lg border p-6 text-muted-foreground">
|
||||
Loading permissions...
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -8,6 +8,7 @@ import { FormField } from '@/components/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -54,14 +55,14 @@ export function ProjectDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<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()}
|
||||
>
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{projectId ? 'Edit Project' : 'Create Project'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Manage road project identity and optional coordinate boundaries.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -71,7 +72,7 @@ export function ProjectDialog({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||
@@ -192,9 +193,9 @@ export function ProjectDialog({
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
||||
import { Layers, Plus } from 'lucide-react';
|
||||
|
||||
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 type { Project } from '@/types';
|
||||
|
||||
@@ -19,6 +29,7 @@ import {
|
||||
|
||||
export default function ProjectPage() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [projectToDelete, setProjectToDelete] = useState<Project | null>(null);
|
||||
const { skip, setSkip, limit, setLimit } = useProjectFilters();
|
||||
const projectsQuery = useProjectsQuery({ skip, limit });
|
||||
const deleteProjectMutation = useDeleteProjectMutation();
|
||||
@@ -63,16 +74,25 @@ export default function ProjectPage() {
|
||||
|
||||
const deleteProject = useCallback(
|
||||
(project: Project) => {
|
||||
if (
|
||||
!confirm(`Are you sure you want to delete project "${project.name}"?`)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
deleteProjectMutation.mutate(project);
|
||||
setProjectToDelete(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 projects = projectsQuery.data?.items ?? [];
|
||||
const total = projectsQuery.data?.totalItems ?? 0;
|
||||
@@ -117,6 +137,36 @@ export default function ProjectPage() {
|
||||
canSubmit={canSubmit}
|
||||
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() {
|
||||
return (
|
||||
<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="mt-2 h-3 w-20" />
|
||||
</div>
|
||||
@@ -14,9 +14,9 @@ function StatSkeleton() {
|
||||
|
||||
function DetectionLogSkeleton() {
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3">
|
||||
<div className="rounded-lg border bg-card p-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="flex items-start justify-between gap-3">
|
||||
<div className="space-y-2">
|
||||
@@ -41,7 +41,7 @@ export function VideoResultsSkeleton() {
|
||||
<div className="space-y-8">
|
||||
<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">
|
||||
<Skeleton className="size-10 rounded-md" />
|
||||
<Skeleton className="size-10 rounded-lg" />
|
||||
<div className="min-w-0 space-y-2">
|
||||
<Skeleton className="h-7 w-60 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" />
|
||||
</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="flex items-center gap-3">
|
||||
<Skeleton className="size-10 rounded" />
|
||||
<Skeleton className="size-10 rounded-lg" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-5 w-44" />
|
||||
<Skeleton className="h-3 w-64" />
|
||||
@@ -76,7 +76,7 @@ export function VideoResultsSkeleton() {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2 p-3 sm:grid-cols-3">
|
||||
{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>
|
||||
</section>
|
||||
|
||||
@@ -125,7 +125,7 @@ export function RoleSheet({
|
||||
}
|
||||
>
|
||||
{isPermissionsLoading ? (
|
||||
<div className="rounded-md border p-6 text-muted-foreground">
|
||||
<div className="rounded-lg border p-6 text-muted-foreground">
|
||||
Loading permissions...
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ProjectSelect } from '@/components/lookups/ProjectSelect';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -71,14 +72,14 @@ export function SegmentDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<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()}
|
||||
>
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{segmentId ? 'Edit Segment' : 'Create Segment'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Manage segment binding, chainage values, direction, and coordinates.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -88,7 +89,7 @@ export function SegmentDialog({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||
@@ -300,9 +301,9 @@ export function SegmentDialog({
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -4,6 +4,16 @@ import { useCallback, useState } from 'react';
|
||||
import { Milestone, Plus } from 'lucide-react';
|
||||
|
||||
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 type { Chainage } from '@/types';
|
||||
|
||||
@@ -21,6 +31,7 @@ import {
|
||||
|
||||
export default function SegmentPage() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [segmentToDelete, setSegmentToDelete] = useState<Chainage | null>(null);
|
||||
const { skip, setSkip, limit, setLimit } = useSegmentFilters();
|
||||
const segmentsQuery = useSegmentsQuery({ skip, limit });
|
||||
const projectsQuery = useProjectOptionsQuery();
|
||||
@@ -75,18 +86,25 @@ export default function SegmentPage() {
|
||||
|
||||
const deleteSegment = useCallback(
|
||||
(segment: Chainage) => {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to delete segment "${segment.segment_name}"?`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
deleteSegmentMutation.mutate(segment);
|
||||
setSegmentToDelete(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 allPackages = allPackagesQuery.data?.items ?? [];
|
||||
const segments = segmentsQuery.data?.items ?? [];
|
||||
@@ -139,6 +157,36 @@ export default function SegmentPage() {
|
||||
canSubmit={canSubmit}
|
||||
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 {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -98,12 +99,12 @@ export function TenantSheet({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-3xl">
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogContent className="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{tenantId ? 'Edit Tenant' : 'Create Tenant'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Bind a client and subscription plan, then invite the tenant
|
||||
administrator.
|
||||
</DialogDescription>
|
||||
@@ -114,7 +115,7 @@ export function TenantSheet({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<p>Basic Information</p>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -227,7 +228,7 @@ export function TenantSheet({
|
||||
id="tenant-description"
|
||||
placeholder="North zone operations"
|
||||
{...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>
|
||||
</div>
|
||||
@@ -314,7 +315,7 @@ export function TenantSheet({
|
||||
</div>
|
||||
</>
|
||||
) : 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
|
||||
creation.
|
||||
<p className="mt-1 text-foreground">
|
||||
@@ -322,9 +323,9 @@ export function TenantSheet({
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -33,8 +33,8 @@ export function TicketClassContentSkeleton() {
|
||||
<Skeleton className="h-5 w-40" />
|
||||
</CardHeader>
|
||||
<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-md" />
|
||||
<Skeleton className="aspect-video w-full rounded-lg" />
|
||||
<Skeleton className="aspect-video w-full rounded-lg" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function TicketClassContentSkeleton() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="size-5 rounded" />
|
||||
<Skeleton className="size-5 rounded-lg" />
|
||||
<Skeleton className="h-5 w-56" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -89,7 +89,7 @@ export function TicketClassActionsSkeleton() {
|
||||
<Card aria-label="Loading defect class actions">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="size-4 rounded" />
|
||||
<Skeleton className="size-4 rounded-lg" />
|
||||
<Skeleton className="h-5 w-32" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -149,7 +149,7 @@ export function TicketClassDetectionPreview({
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span
|
||||
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,
|
||||
)}
|
||||
>
|
||||
@@ -157,13 +157,13 @@ export function TicketClassDetectionPreview({
|
||||
</span>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<h3 className="truncate text-lg font-semibold text-foreground">
|
||||
<h3 >
|
||||
{displayName}
|
||||
</h3>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'rounded-full border-0 px-2.5 py-1 text-xs font-semibold',
|
||||
'rounded-lg',
|
||||
visual.cardClassName,
|
||||
visual.colorClassName,
|
||||
)}
|
||||
@@ -171,7 +171,7 @@ export function TicketClassDetectionPreview({
|
||||
{detectionCount} Detections
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground">
|
||||
Review detections in ascending timestamp order.
|
||||
</p>
|
||||
</div>
|
||||
@@ -180,8 +180,7 @@ export function TicketClassDetectionPreview({
|
||||
<div className="flex items-center gap-2 self-start sm:self-auto">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => setCurrentIndex((index) => Math.max(index - 1, 0))}
|
||||
disabled={isPreviousDisabled}
|
||||
aria-label="Previous detection"
|
||||
@@ -195,8 +194,7 @@ export function TicketClassDetectionPreview({
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => setCurrentIndex((index) => index + 1)}
|
||||
disabled={isNextDisabled}
|
||||
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="min-w-0 space-y-2">
|
||||
<p className="text-xs font-medium text-muted-foreground">
|
||||
<p className="text-muted-foreground">
|
||||
Detection Image
|
||||
</p>
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
|
||||
@@ -75,9 +75,9 @@ export function TicketDefectClassTabs({
|
||||
|
||||
if (defectClassesQuery.isLoading) {
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<Card className="w-full rounded-lg">
|
||||
<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">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div
|
||||
@@ -104,7 +104,7 @@ export function TicketDefectClassTabs({
|
||||
>
|
||||
<Card className="w-full">
|
||||
<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">
|
||||
<ListChecks className="size-5 shrink-0 text-primary" />
|
||||
<h2 className="text-base font-semibold">Detected Issues</h2>
|
||||
@@ -126,7 +126,7 @@ export function TicketDefectClassTabs({
|
||||
key={item.class_name}
|
||||
value={item.class_name}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
@@ -135,7 +135,7 @@ export function TicketDefectClassTabs({
|
||||
</span>
|
||||
<Badge
|
||||
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,
|
||||
)}
|
||||
>
|
||||
@@ -148,7 +148,7 @@ export function TicketDefectClassTabs({
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 p-5">
|
||||
<div className="min-w-0 px-5">
|
||||
<TicketClassDetectionPreview
|
||||
videoId={previewVideoId}
|
||||
defectClassName={selectedClass}
|
||||
|
||||
@@ -23,8 +23,7 @@ export function TicketDetailHeader({
|
||||
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => router.push('/ticket')}
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
|
||||
@@ -48,10 +48,10 @@ function SystemHistoryEntry({ item }: { item: TicketHistoryItem }) {
|
||||
const noteText = item.note?.text?.trim();
|
||||
|
||||
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
|
||||
|
||||
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)}
|
||||
</Badge>
|
||||
@@ -70,7 +70,7 @@ function UserHistoryEntry({ item }: { item: TicketHistoryItem }) {
|
||||
const isAssigned = item.event_type === 'assigned';
|
||||
|
||||
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>
|
||||
<PersonInfo person={item.actor} />
|
||||
{isAssigned && item.target_user ? (
|
||||
@@ -124,7 +124,7 @@ export function TicketHistoryCard({ ticket }: { ticket: TicketDetail }) {
|
||||
))}
|
||||
</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">
|
||||
No timeline entries yet
|
||||
</p>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
MapPin,
|
||||
Monitor,
|
||||
UserRound,
|
||||
Play
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
@@ -77,7 +79,7 @@ function SummaryCard({
|
||||
return (
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
>
|
||||
@@ -180,11 +182,10 @@ export function TicketOverviewCard({
|
||||
<CardAction>
|
||||
<PermissionGuard permissions={PERMISSIONS.TICKET.ASSIGN}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-primary hover:bg-primary/10 hover:text-primary"
|
||||
variant="secondary"
|
||||
onClick={() => setIsAnalysisOpen(true)}
|
||||
>
|
||||
<Play className="mr-2 size-4" />
|
||||
View Video
|
||||
</Button>
|
||||
</PermissionGuard>
|
||||
@@ -232,16 +233,16 @@ export function TicketOverviewCard({
|
||||
</Card>
|
||||
|
||||
<Dialog open={isAnalysisOpen} onOpenChange={setIsAnalysisOpen}>
|
||||
<DialogContent className="gap-3 p-3 sm:max-w-5xl sm:p-4">
|
||||
<DialogHeader className="min-w-0 pr-8">
|
||||
<DialogTitle className="truncate text-left text-base">
|
||||
{analysisTitle}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="truncate text-xs">
|
||||
<DialogContent className="sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{analysisTitle}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Video analysis with annotation overlay
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<TicketInlineAnalysisVideo ticket={ticket} />
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@ export function ClosedTicketAction({ ticket }: { ticket: TicketDetail }) {
|
||||
</CardHeader>
|
||||
|
||||
<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
|
||||
label="Reviewed By"
|
||||
value={<PersonInfo person={reviewer} size="lg" />}
|
||||
|
||||
@@ -56,11 +56,11 @@ function MediaSectionHeader({
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
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" />
|
||||
{addLabel}
|
||||
@@ -71,7 +71,7 @@ function MediaSectionHeader({
|
||||
|
||||
function MediaHint({ children }: { children: ReactNode }) {
|
||||
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}
|
||||
</p>
|
||||
);
|
||||
@@ -182,7 +182,7 @@ function VideoThumbnail({
|
||||
</span>
|
||||
</div>
|
||||
{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}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -382,7 +382,7 @@ export function RepairEvidenceForm({
|
||||
description="Tap to upload repair photos"
|
||||
disabled={isSubmitting}
|
||||
onClick={openImagePicker}
|
||||
className="min-h-[104px]"
|
||||
className="min-h-26"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex min-w-min gap-3">
|
||||
@@ -457,7 +457,7 @@ export function RepairEvidenceForm({
|
||||
</div>
|
||||
|
||||
{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}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -32,6 +32,7 @@ export function ReviewRepairAction({ ticket }: { ticket: TicketDetail }) {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
disabled={!reviewComment || reviewRepairMutation.isPending}
|
||||
onClick={() =>
|
||||
reviewRepairMutation.mutate({
|
||||
|
||||
@@ -22,7 +22,7 @@ export function TicketActionCard({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Icon className={cn('size-4 text-primary', iconClassName)} />
|
||||
<Icon className={cn('text-primary', iconClassName)} />
|
||||
{title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -43,11 +43,11 @@ export function RepairProofGallery({ imageUrls }: RepairProofGalleryProps) {
|
||||
|
||||
if (queries.some((query) => query.isLoading)) {
|
||||
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) => (
|
||||
<Skeleton
|
||||
key={`${url}-${index}`}
|
||||
className="aspect-square rounded-none"
|
||||
className="aspect-square rounded-lg"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@ export function RepairProofGallery({ imageUrls }: RepairProofGalleryProps) {
|
||||
|
||||
return (
|
||||
<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"
|
||||
speed={400}
|
||||
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -72,7 +72,7 @@ export function RepairProofSummary({ ticket }: { ticket: TicketDetail }) {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Wrench className="size-4 text-primary" />
|
||||
<ToolCase className="text-primary" />
|
||||
Repair Proof
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Loader2, Play } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -89,7 +90,7 @@ export function CreateUploadDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent
|
||||
className="max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-5xl"
|
||||
className="sm:max-w-5xl"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
@@ -99,7 +100,7 @@ export function CreateUploadDialog({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-5">
|
||||
<DialogBody className="space-y-5">
|
||||
<LocationSection
|
||||
value={session}
|
||||
onChange={setSession}
|
||||
@@ -128,11 +129,11 @@ export function CreateUploadDialog({
|
||||
portalContainer={dropdownPortalRef}
|
||||
/>
|
||||
{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}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@@ -56,7 +56,7 @@ export function UploadFileDropzone({
|
||||
handleFiles(event.dataTransfer.files);
|
||||
}}
|
||||
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',
|
||||
isDragging && 'border-primary bg-secondary/60',
|
||||
disabled && 'pointer-events-none cursor-not-allowed opacity-60',
|
||||
@@ -72,7 +72,7 @@ export function UploadFileDropzone({
|
||||
/>
|
||||
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function useUploadListColumns(
|
||||
|
||||
return (
|
||||
<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={
|
||||
thumbnail
|
||||
? { backgroundImage: `url("${thumbnail}")` }
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
'use client';
|
||||
|
||||
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';
|
||||
|
||||
interface VideoPreviewDialogProps {
|
||||
@@ -22,11 +28,13 @@ export function VideoPreviewDialog({
|
||||
<DialogTitle>Video Preview</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
<ProtectedVideoPlayer
|
||||
url={open ? upload?.raw_video_url : undefined}
|
||||
className="rounded-lg"
|
||||
unavailableTitle="Video preview unavailable"
|
||||
/>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { RoleSelect } from '@/components/lookups/RoleSelect';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -67,13 +68,13 @@ export function UserSheet({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-xl">
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{userId ? 'Edit User' : 'Create User'}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="text-sm">
|
||||
<DialogDescription>
|
||||
Assign user details and a role.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -83,7 +84,7 @@ export function UserSheet({
|
||||
onSubmit={onSubmit}
|
||||
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">
|
||||
<FormField
|
||||
id="first-name"
|
||||
@@ -172,9 +173,9 @@ export function UserSheet({
|
||||
readOnly
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--radius: 0.225rem;
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
|
||||
@@ -3,8 +3,8 @@ import Image from 'next/image';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'size-8 rounded-md border-2 p-0.5',
|
||||
md: 'size-12 rounded-md border-2 p-1.5',
|
||||
sm: 'size-8 rounded-lg border-2 p-0.5',
|
||||
md: 'size-12 rounded-lg border-2 p-1.5',
|
||||
lg: 'size-28 rounded-lg border-[5px] p-3',
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -27,7 +28,9 @@ export function BreadcrumbBasic() {
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="/">Home</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
{segments.length > 0 && <BreadcrumbSeparator />}
|
||||
|
||||
@@ -44,8 +47,8 @@ export function BreadcrumbBasic() {
|
||||
{isLast ? (
|
||||
<BreadcrumbPage>{formatSegment(segment)}</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink href={href}>
|
||||
{formatSegment(segment)}
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href={href}>{formatSegment(segment)}</Link>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
|
||||
@@ -86,7 +86,7 @@ export function AuthBackground({ className }: AuthBackgroundProps) {
|
||||
<div className="mb-9 inline-flex items-center gap-5 text-left">
|
||||
<LogoWrapper
|
||||
alt=""
|
||||
className="size-28 rounded-2xl border-[5px] p-4"
|
||||
className="size-28 rounded-lg border-[5px] p-4"
|
||||
priority
|
||||
variant="glass"
|
||||
/>
|
||||
|
||||
@@ -70,7 +70,7 @@ const TableHeader = <TData, _>({ table }: { table: Table<TData> }) => {
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
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',
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
containerClassName="max-h-[calc(100vh-350px)] overflow-auto"
|
||||
className="table-fixed border-separate border-spacing-0"
|
||||
|
||||
@@ -32,7 +32,7 @@ interface MultiSelectPopoverProps {
|
||||
|
||||
function checkboxClass(checked: boolean) {
|
||||
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',
|
||||
);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export function MultiSelectPopover({
|
||||
type="button"
|
||||
onClick={toggleAll}
|
||||
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)}>
|
||||
{areAllSelected ? <Check className="size-3" /> : null}
|
||||
@@ -128,7 +128,7 @@ export function MultiSelectPopover({
|
||||
return (
|
||||
<div
|
||||
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
|
||||
type="button"
|
||||
|
||||
@@ -101,7 +101,7 @@ export function SelectPopover({
|
||||
setOpen(false);
|
||||
}}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -109,7 +109,7 @@ export function CountryCombobox({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
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} />
|
||||
</Button>
|
||||
|
||||
@@ -7,7 +7,7 @@ export function CountryFlag({ country }: { country: CountryCode }) {
|
||||
<div
|
||||
aria-hidden
|
||||
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()}`,
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function PhoneInput({
|
||||
return (
|
||||
<div
|
||||
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 &&
|
||||
'border-destructive focus-within:border-destructive focus-within:ring-destructive/20',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
@@ -80,7 +80,7 @@ export function PhoneInput({
|
||||
disabled={disabled}
|
||||
aria-invalid={ariaInvalid}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function DetectionLocationMap({
|
||||
return (
|
||||
<div className={cn('space-y-2', className)}>
|
||||
{title ? (
|
||||
<p className="text-xs font-medium text-muted-foreground">{title}</p>
|
||||
<p className="text-muted-foreground">{title}</p>
|
||||
) : null}
|
||||
{mapContent}
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@ export default function DetectionLocationMap({
|
||||
<Card className={cn('overflow-hidden', className)}>
|
||||
<CardHeader className="border-b pb-4">
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function ProtectedVideoPlayer({
|
||||
return (
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
>
|
||||
@@ -69,7 +69,7 @@ export function ProtectedVideoPlayer({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'group relative overflow-hidden rounded-md border bg-black/90',
|
||||
'group relative overflow-hidden rounded-lg border bg-black/90',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@@ -84,7 +84,7 @@ export function ProtectedVideoPlayer({
|
||||
</video>
|
||||
|
||||
{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}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -110,7 +110,7 @@ function VideoState({
|
||||
return (
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
>
|
||||
|
||||
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"
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
@@ -22,15 +22,15 @@ const buttonVariants = cva(
|
||||
},
|
||||
size: {
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
"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-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-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",
|
||||
icon: "size-9",
|
||||
"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":
|
||||
"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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
const PICKER_NAV_BUTTON_CLASS = 'size-8 shrink-0 p-0';
|
||||
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 =
|
||||
'bg-muted text-foreground hover:bg-muted hover:text-foreground';
|
||||
const GRID_BUTTON_DISABLED_CLASS =
|
||||
@@ -134,7 +134,7 @@ function PickerNav({
|
||||
{onLabelClick ? (
|
||||
<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}
|
||||
>
|
||||
{label}
|
||||
@@ -277,7 +277,7 @@ function CalendarMonthCaption({
|
||||
<div className="flex items-center justify-center gap-1 text-sm font-semibold">
|
||||
<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={() => {
|
||||
setPickerYear(year);
|
||||
setView('months');
|
||||
@@ -287,7 +287,7 @@ function CalendarMonthCaption({
|
||||
</button>
|
||||
<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={() => {
|
||||
setDecadeStart(Math.floor(year / 10) * 10);
|
||||
setPickerYear(year);
|
||||
|
||||
@@ -73,7 +73,7 @@ function Calendar({
|
||||
defaultClassNames.dropdowns
|
||||
),
|
||||
dropdown_root: cn(
|
||||
"relative rounded-(--cell-radius)",
|
||||
"relative rounded-lg",
|
||||
defaultClassNames.dropdown_root
|
||||
),
|
||||
dropdown: cn(
|
||||
@@ -84,13 +84,13 @@ function Calendar({
|
||||
"font-medium select-none",
|
||||
captionLayout === "label"
|
||||
? "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
|
||||
),
|
||||
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||
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
|
||||
),
|
||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||
@@ -103,23 +103,23 @@ function Calendar({
|
||||
defaultClassNames.week_number
|
||||
),
|
||||
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
|
||||
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
|
||||
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
|
||||
? "[&:nth-child(2)[data-selected=true]_button]:rounded-lg"
|
||||
: "[&:first-child[data-selected=true]_button]:rounded-lg",
|
||||
defaultClassNames.day
|
||||
),
|
||||
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
|
||||
),
|
||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||
range_middle: cn("rounded-lg", defaultClassNames.range_middle),
|
||||
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
|
||||
),
|
||||
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
|
||||
),
|
||||
outside: cn(
|
||||
@@ -210,7 +210,7 @@ function CalendarDayButton({
|
||||
data-range-end={modifiers.range_end}
|
||||
data-range-middle={modifiers.range_middle}
|
||||
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,
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -12,7 +12,7 @@ function Card({
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-header"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -222,7 +222,7 @@ function ChartTooltipContent({
|
||||
!hideIndicator && (
|
||||
<div
|
||||
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",
|
||||
"w-1": indicator === "line",
|
||||
@@ -313,7 +313,7 @@ function ChartLegendContent({
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
className="h-2 w-2 shrink-0 rounded-lg"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
|
||||
@@ -115,7 +115,7 @@ function ComboboxContent({
|
||||
<ComboboxPrimitive.Popup
|
||||
data-slot="combobox-content"
|
||||
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}
|
||||
/>
|
||||
</ComboboxPrimitive.Positioner>
|
||||
@@ -145,7 +145,7 @@ function ComboboxItem({
|
||||
<ComboboxPrimitive.Item
|
||||
data-slot="combobox-item"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -226,7 +226,7 @@ function ComboboxChips({
|
||||
<ComboboxPrimitive.Chips
|
||||
data-slot="combobox-chips"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -246,7 +246,7 @@ function ComboboxChip({
|
||||
<ComboboxPrimitive.Chip
|
||||
data-slot="combobox-chip"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -61,7 +61,7 @@ function DialogContent({
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -89,7 +89,17 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
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}
|
||||
/>
|
||||
)
|
||||
@@ -107,7 +117,7 @@ function DialogFooter({
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -153,6 +163,7 @@ function DialogDescription({
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
|
||||
@@ -56,7 +56,7 @@ function DrawerContent({
|
||||
<DrawerPrimitive.Content
|
||||
data-slot="drawer-content"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -43,7 +43,7 @@ function DropdownMenuContent({
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
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}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
@@ -73,7 +73,7 @@ function DropdownMenuItem({
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -95,7 +95,7 @@ function DropdownMenuCheckboxItem({
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
data-inset={inset}
|
||||
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
|
||||
)}
|
||||
checked={checked}
|
||||
@@ -139,7 +139,7 @@ function DropdownMenuRadioItem({
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
data-inset={inset}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -226,7 +226,7 @@ function DropdownMenuSubTrigger({
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -244,7 +244,7 @@ function DropdownMenuSubContent({
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -23,7 +23,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
}
|
||||
|
||||
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: {
|
||||
align: {
|
||||
@@ -70,10 +70,10 @@ const inputGroupButtonVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
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: "",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
@@ -122,7 +122,7 @@ function InputGroupInput({
|
||||
<Input
|
||||
data-slot="input-group-control"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -138,7 +138,7 @@ function InputGroupTextarea({
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
type={type}
|
||||
data-slot="input"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -38,7 +38,7 @@ function PopoverContent({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -18,7 +18,7 @@ function ScrollArea({
|
||||
>
|
||||
<ScrollAreaPrimitive.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}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
|
||||
@@ -44,7 +44,7 @@ function SelectTrigger({
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -69,7 +69,7 @@ function SelectContent({
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
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}
|
||||
align={align}
|
||||
{...props}
|
||||
@@ -112,7 +112,7 @@ function SelectItem({
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -306,7 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
||||
<main
|
||||
data-slot="sidebar-inset"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -401,7 +401,7 @@ function SidebarGroupLabel({
|
||||
data-slot="sidebar-group-label"
|
||||
data-sidebar="group-label"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -421,7 +421,7 @@ function SidebarGroupAction({
|
||||
data-slot="sidebar-group-action"
|
||||
data-sidebar="group-action"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -466,7 +466,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||
}
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
@@ -553,7 +553,7 @@ function SidebarMenuAction({
|
||||
data-slot="sidebar-menu-action"
|
||||
data-sidebar="menu-action"
|
||||
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 &&
|
||||
"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
|
||||
@@ -572,7 +572,7 @@ function SidebarMenuBadge({
|
||||
data-slot="sidebar-menu-badge"
|
||||
data-sidebar="menu-badge"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
@@ -596,12 +596,12 @@ function SidebarMenuSkeleton({
|
||||
<div
|
||||
data-slot="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}
|
||||
>
|
||||
{showIcon && (
|
||||
<Skeleton
|
||||
className="size-4 rounded-md"
|
||||
className="size-4 rounded-lg"
|
||||
data-sidebar="menu-skeleton-icon"
|
||||
/>
|
||||
)}
|
||||
@@ -666,7 +666,7 @@ function SidebarMenuSubButton({
|
||||
data-size={size}
|
||||
data-active={isActive}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="skeleton"
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
className={cn("animate-pulse rounded-lg bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ function Tabs({
|
||||
}
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
@@ -63,7 +63,7 @@ function TabsTrigger({
|
||||
<TabsPrimitive.Trigger
|
||||
data-slot="tabs-trigger"
|
||||
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",
|
||||
"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",
|
||||
|
||||
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -42,13 +42,13 @@ function TooltipContent({
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
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
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{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.Portal>
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ function TreeCheckbox({
|
||||
type="checkbox"
|
||||
checked={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 (
|
||||
<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">
|
||||
{hasChildren ? (
|
||||
<button
|
||||
@@ -79,7 +79,7 @@ function CheckboxTreeNode<TItem extends TreeItem>({
|
||||
aria-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
|
||||
className={`size-4 transition-transform ${isExpanded ? '' : '-rotate-90'}`}
|
||||
@@ -155,7 +155,7 @@ export function CheckboxTree<TItem extends TreeItem>({
|
||||
|
||||
if (items.length === 0) {
|
||||
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}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -47,17 +47,17 @@ export default function AnnotatedDetectionImage({
|
||||
{!detection ? (
|
||||
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
||||
<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>
|
||||
) : isLoading ? (
|
||||
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
||||
<Loader2 className="size-7 animate-spin text-primary" />
|
||||
<p className="text-sm">Loading detection image...</p>
|
||||
<p>Loading detection image...</p>
|
||||
</div>
|
||||
) : isError || !objectUrl ? (
|
||||
<div className="flex flex-col items-center gap-2 text-destructive">
|
||||
<AlertTriangle className="size-7" />
|
||||
<p className="text-sm">Failed to load the detection image.</p>
|
||||
<p>Failed to load the detection image.</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function CurrentDetectionBar({
|
||||
</div>
|
||||
|
||||
{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" />
|
||||
<span className="shrink-0 font-medium">Location</span>
|
||||
<span className="truncate font-mono font-semibold text-foreground">
|
||||
@@ -63,11 +63,11 @@ export default function CurrentDetectionBar({
|
||||
return (
|
||||
<div
|
||||
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">
|
||||
<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" />
|
||||
</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">
|
||||
<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" />
|
||||
</span>
|
||||
<p className="truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
|
||||
@@ -33,26 +33,26 @@ function DetectionLogThumbnail({
|
||||
|
||||
if (!url) {
|
||||
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" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton className="size-20 shrink-0 rounded-md" />;
|
||||
return <Skeleton className="size-20 shrink-0 rounded-lg" />;
|
||||
}
|
||||
|
||||
if (isError || !objectUrl) {
|
||||
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
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
src={objectUrl}
|
||||
alt={label}
|
||||
@@ -102,7 +102,7 @@ const DetectionLogs = ({ logs, activeLogId, onSelect }: DetectionLogsProps) => {
|
||||
type="button"
|
||||
onClick={() => onSelect(log)}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function ResultStatsGrid({ data }: ResultStatsGridProps) {
|
||||
return (
|
||||
<div
|
||||
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">
|
||||
<p
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function VideoPlayerSection({ data }: VideoPlayerSectionProps) {
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b pb-4">
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user