Add UI improvements
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"@trpc/react-query": "next",
|
"@trpc/react-query": "next",
|
||||||
"@trpc/server": "next",
|
"@trpc/server": "next",
|
||||||
"@unsend/ui": "workspace:*",
|
"@unsend/ui": "workspace:*",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"lucide-react": "^0.359.0",
|
"lucide-react": "^0.359.0",
|
||||||
"next": "^14.1.3",
|
"next": "^14.1.3",
|
||||||
|
@@ -106,6 +106,8 @@ model Domain {
|
|||||||
teamId Int
|
teamId Int
|
||||||
status DomainStatus @default(PENDING)
|
status DomainStatus @default(PENDING)
|
||||||
region String @default("us-east-1")
|
region String @default("us-east-1")
|
||||||
|
clickTracking Boolean @default(false)
|
||||||
|
openTracking Boolean @default(false)
|
||||||
publicKey String
|
publicKey String
|
||||||
dkimStatus String?
|
dkimStatus String?
|
||||||
spfDetails String?
|
spfDetails String?
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { DomainStatus } from "@prisma/client";
|
||||||
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Switch } from "@unsend/ui/src/switch";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
|
|
||||||
export default function DomainsList() {
|
export default function DomainsList() {
|
||||||
@@ -8,15 +11,54 @@ export default function DomainsList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-6">
|
||||||
{!domainsQuery.isLoading && domainsQuery.data?.length ? (
|
{!domainsQuery.isLoading && domainsQuery.data?.length ? (
|
||||||
domainsQuery.data?.map((domain) => (
|
domainsQuery.data?.map((domain) => (
|
||||||
<Link key={domain.id} href={`/domains/${domain.id}`}>
|
<div key={domain.id}>
|
||||||
<div className="p-2 px-4 border rounded-lg flex justify-between">
|
<div className=" pr-8 border rounded-lg flex items-stretch">
|
||||||
<p>{domain.name}</p>
|
<StatusIndicator status={domain.status} />
|
||||||
<p className=" capitalize">{domain.status.toLowerCase()}</p>
|
<div className="flex justify-between w-full pl-8 py-4">
|
||||||
</div>
|
<div className="flex flex-col gap-4 w-1/5">
|
||||||
|
<Link
|
||||||
|
href={`/domains/${domain.id}`}
|
||||||
|
className="text-lg font-medium underline underline-offset-4 decoration-dashed"
|
||||||
|
>
|
||||||
|
{domain.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
<DomainStatusBadge status={domain.status} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Created at
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{formatDistanceToNow(new Date(domain.createdAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Region</p>
|
||||||
|
|
||||||
|
<p className="text-sm flex items-center gap-2">
|
||||||
|
<span className="text-2xl">🇺🇸</span> {domain.region}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<p className="text-sm">Click tracking</p>
|
||||||
|
<Switch className="data-[state=checked]:bg-emerald-500" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<p className="text-sm">Open tracking</p>
|
||||||
|
<Switch className="data-[state=checked]:bg-emerald-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div>No domains</div>
|
<div>No domains</div>
|
||||||
@@ -25,3 +67,55 @@ export default function DomainsList() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DomainStatusBadge: React.FC<{ status: DomainStatus }> = ({ status }) => {
|
||||||
|
let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color
|
||||||
|
switch (status) {
|
||||||
|
case DomainStatus.NOT_STARTED:
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
||||||
|
break;
|
||||||
|
case DomainStatus.SUCCESS:
|
||||||
|
badgeColor = "bg-emerald-500/10 text-emerald-500 border-emerald-600/10";
|
||||||
|
break;
|
||||||
|
case DomainStatus.FAILED:
|
||||||
|
badgeColor = "bg-red-500/10 text-red-800 border-red-600/10";
|
||||||
|
break;
|
||||||
|
case DomainStatus.TEMPORARY_FAILURE:
|
||||||
|
case DomainStatus.PENDING:
|
||||||
|
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`border text-center w-[120px] text-sm capitalize rounded-full py-0.5 ${badgeColor}`}
|
||||||
|
>
|
||||||
|
{status === "SUCCESS" ? "Verified" : status.toLowerCase()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusIndicator: React.FC<{ status: DomainStatus }> = ({ status }) => {
|
||||||
|
let badgeColor = "bg-gray-400"; // Default color
|
||||||
|
switch (status) {
|
||||||
|
case DomainStatus.NOT_STARTED:
|
||||||
|
badgeColor = "bg-gray-400";
|
||||||
|
break;
|
||||||
|
case DomainStatus.SUCCESS:
|
||||||
|
badgeColor = "bg-emerald-500";
|
||||||
|
break;
|
||||||
|
case DomainStatus.FAILED:
|
||||||
|
badgeColor = "bg-red-500";
|
||||||
|
break;
|
||||||
|
case DomainStatus.TEMPORARY_FAILURE:
|
||||||
|
case DomainStatus.PENDING:
|
||||||
|
badgeColor = "bg-yellow-500";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badgeColor = "bg-gray-400";
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={` w-[1px] ${badgeColor} my-1.5 rounded-full`}></div>;
|
||||||
|
};
|
||||||
|
@@ -1,31 +1,149 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableCaption,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
TableHead,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
} from "@unsend/ui/src/table";
|
||||||
|
import { Badge } from "@unsend/ui/src/badge";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
|
import {
|
||||||
|
Mail,
|
||||||
|
MailCheck,
|
||||||
|
MailOpen,
|
||||||
|
MailSearch,
|
||||||
|
MailWarning,
|
||||||
|
MailX,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { formatDistance, formatDistanceToNow } from "date-fns";
|
||||||
|
|
||||||
export default function DomainsList() {
|
export default function EmailsList() {
|
||||||
const emailsQuery = api.email.emails.useQuery();
|
const emailsQuery = api.email.emails.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex rounded-xl border shadow">
|
||||||
{!emailsQuery.isLoading && emailsQuery.data?.length ? (
|
<Table className="">
|
||||||
emailsQuery.data?.map((email) => (
|
<TableHeader className="">
|
||||||
<Link key={email.id} href={`/email/${email.id}`} className="w-full">
|
<TableRow className=" bg-muted/30">
|
||||||
<div className="p-2 px-4 border rounded-lg flex justify-between w-full">
|
<TableHead className="rounded-tl-xl">To</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Subject</TableHead>
|
||||||
|
<TableHead className="text-right rounded-tr-xl">
|
||||||
|
Sent at
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{emailsQuery.data?.map((email) => (
|
||||||
|
<TableRow key={email.id}>
|
||||||
|
<TableCell className="font-medium flex gap-4 items-center">
|
||||||
|
<EmailIcon status={email.latestStatus ?? "Send"} />
|
||||||
<p>{email.to}</p>
|
<p>{email.to}</p>
|
||||||
<p className=" capitalize">
|
</TableCell>
|
||||||
{email.latestStatus?.toLowerCase()}
|
<TableCell>
|
||||||
</p>
|
<EmailStatusBadge status={email.latestStatus ?? "Sent"} />
|
||||||
<p>{email.subject}</p>
|
{/* <Badge className="w-[100px] flex py-1 justify-center text-emerald-400 hover:bg-emerald-500/10 bg-emerald-500/10 rounded">
|
||||||
<p>{email.createdAt.toLocaleDateString()}</p>
|
{email.latestStatus ?? "Sent"}
|
||||||
</div>
|
</Badge> */}
|
||||||
</Link>
|
</TableCell>
|
||||||
))
|
<TableCell>{email.subject}</TableCell>
|
||||||
) : (
|
<TableCell className="text-right">
|
||||||
<div>No domains</div>
|
{formatDistanceToNow(email.createdAt, { addSuffix: true })}
|
||||||
)}
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EmailIcon: React.FC<{ status: string }> = ({ status }) => {
|
||||||
|
switch (status) {
|
||||||
|
case "Send":
|
||||||
|
return (
|
||||||
|
// <div className="border border-gray-400/60 p-2 rounded-lg bg-gray-400/10">
|
||||||
|
<Mail className="w-6 h-6 text-gray-500 " />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
case "Delivery":
|
||||||
|
case "Delayed":
|
||||||
|
return (
|
||||||
|
// <div className="border border-emerald-600/60 p-2 rounded-lg bg-emerald-500/10">
|
||||||
|
<MailCheck className="w-6 h-6 text-emerald-800" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
case "Bounced":
|
||||||
|
return (
|
||||||
|
// <div className="border border-red-600/60 p-2 rounded-lg bg-red-500/10">
|
||||||
|
<MailX className="w-6 h-6 text-red-900" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
case "Clicked":
|
||||||
|
return (
|
||||||
|
// <div className="border border-cyan-600/60 p-2 rounded-lg bg-cyan-500/10">
|
||||||
|
<MailSearch className="w-6 h-6 text-cyan-700" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
case "Opened":
|
||||||
|
return (
|
||||||
|
// <div className="border border-indigo-600/60 p-2 rounded-lg bg-indigo-500/10">
|
||||||
|
<MailOpen className="w-6 h-6 text-indigo-700" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
case "Complained":
|
||||||
|
return (
|
||||||
|
// <div className="border border-yellow-600/60 p-2 rounded-lg bg-yellow-500/10">
|
||||||
|
<MailWarning className="w-6 h-6 text-yellow-700" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
// <div className="border border-gray-400/60 p-2 rounded-lg">
|
||||||
|
<Mail className="w-6 h-6" />
|
||||||
|
// </div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const EmailStatusBadge: React.FC<{ status: string }> = ({ status }) => {
|
||||||
|
let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color
|
||||||
|
switch (status) {
|
||||||
|
case "Send":
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
||||||
|
break;
|
||||||
|
case "Delivery":
|
||||||
|
case "Delayed":
|
||||||
|
badgeColor = "bg-emerald-500/10 text-emerald-500 border-emerald-600/10";
|
||||||
|
break;
|
||||||
|
case "Bounced":
|
||||||
|
badgeColor = "bg-red-500/10 text-red-800 border-red-600/10";
|
||||||
|
break;
|
||||||
|
case "Clicked":
|
||||||
|
badgeColor = "bg-cyan-500/10 text-cyan-600 border-cyan-600/10";
|
||||||
|
break;
|
||||||
|
case "Opened":
|
||||||
|
badgeColor = "bg-indigo-500/10 text-indigo-600 border-indigo-600/10";
|
||||||
|
break;
|
||||||
|
case "Complained":
|
||||||
|
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`border text-center w-[120px] rounded-full py-0.5 ${badgeColor}`}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -176,7 +176,7 @@ export default async function AuthenticatedDashboardLayout({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</header>
|
</header>
|
||||||
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6">
|
<main className="flex flex-1 flex-col gap-4 p-4 w-full lg:max-w-6xl mx-auto lg:gap-6 lg:p-6">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
36
packages/ui/src/badge.tsx
Normal file
36
packages/ui/src/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||||
|
outline: "text-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants };
|
29
packages/ui/src/switch.tsx
Normal file
29
packages/ui/src/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||||
|
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-5 w-5 rounded-full bg-accent-foreground shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
));
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
|
export { Switch };
|
117
packages/ui/src/table.tsx
Normal file
117
packages/ui/src/table.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
|
const Table = React.forwardRef<
|
||||||
|
HTMLTableElement,
|
||||||
|
React.HTMLAttributes<HTMLTableElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="relative w-full overflow-auto">
|
||||||
|
<table
|
||||||
|
ref={ref}
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
Table.displayName = "Table";
|
||||||
|
|
||||||
|
const TableHeader = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||||
|
));
|
||||||
|
TableHeader.displayName = "TableHeader";
|
||||||
|
|
||||||
|
const TableBody = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tbody
|
||||||
|
ref={ref}
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableBody.displayName = "TableBody";
|
||||||
|
|
||||||
|
const TableFooter = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tfoot
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableFooter.displayName = "TableFooter";
|
||||||
|
|
||||||
|
const TableRow = React.forwardRef<
|
||||||
|
HTMLTableRowElement,
|
||||||
|
React.HTMLAttributes<HTMLTableRowElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tr
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableRow.displayName = "TableRow";
|
||||||
|
|
||||||
|
const TableHead = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<th
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableHead.displayName = "TableHead";
|
||||||
|
|
||||||
|
const TableCell = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<td
|
||||||
|
ref={ref}
|
||||||
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableCell.displayName = "TableCell";
|
||||||
|
|
||||||
|
const TableCaption = React.forwardRef<
|
||||||
|
HTMLTableCaptionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<caption
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableCaption.displayName = "TableCaption";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableCaption,
|
||||||
|
};
|
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 223 3% 3%;
|
--background: 223 3% 3%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 3% 82%;
|
||||||
|
|
||||||
--card: 222.2 84% 4.9%;
|
--card: 222.2 84% 4.9%;
|
||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
|
51
pnpm-lock.yaml
generated
51
pnpm-lock.yaml
generated
@@ -115,6 +115,9 @@ importers:
|
|||||||
'@unsend/ui':
|
'@unsend/ui':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/ui
|
version: link:../../packages/ui
|
||||||
|
date-fns:
|
||||||
|
specifier: ^3.6.0
|
||||||
|
version: 3.6.0
|
||||||
install:
|
install:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
@@ -257,6 +260,9 @@ importers:
|
|||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.0.2(@types/react@18.2.66)(react@18.2.0)
|
version: 1.0.2(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-switch':
|
||||||
|
specifier: ^1.0.3
|
||||||
|
version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
|
||||||
add:
|
add:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6
|
version: 2.0.6
|
||||||
@@ -2201,6 +2207,33 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.66
|
||||||
|
'@types/react-dom': 18.2.22
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.66)(react@18.2.0):
|
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.66)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2259,6 +2292,20 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-previous@1.0.1(@types/react@18.2.66)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
'@types/react': 18.2.66
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.66)(react@18.2.0):
|
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.66)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
|
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3661,6 +3708,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/date-fns@3.6.0:
|
||||||
|
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/debug@3.2.7:
|
/debug@3.2.7:
|
||||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
Reference in New Issue
Block a user