diff --git a/services/frontend/src/app/(dashboard)/layout.tsx b/services/frontend/src/app/(dashboard)/layout.tsx index 0bd260e..d06d1fa 100644 --- a/services/frontend/src/app/(dashboard)/layout.tsx +++ b/services/frontend/src/app/(dashboard)/layout.tsx @@ -7,10 +7,15 @@ import { ChatbotProvider, useChatbot, } from "@/components/chatbot/chatbot-context"; -import { HiddenSidebar } from "@/components/layout/hidden-sidebar"; -import { MobileNav } from "@/components/layout/mobile-nav"; -import { TopNav } from "@/components/layout/top-nav"; +import { AppSidebar } from "@/components/layout/app-sidebar"; import { MiniPlayer } from "@/components/media/mini-player"; +import { GuildSelector } from "@/components/shared/guild-selector"; +import { Separator } from "@/components/ui/separator"; +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar"; import { MediaPlayerProvider } from "@/lib/hooks/use-media-player"; import { useWebSocket, WsProvider } from "@/lib/ws/context"; @@ -69,29 +74,41 @@ export default function DashboardLayout({ -
- - setGuildId(g ?? "")} - /> +
+ + + +
+ + +
Overview
+
+ setGuildId(g ?? "")} + /> +
+
- {/* Sub-nav space — filled per-page */} -
-
- -
-
- } - > - {children} -
-
-
+
+
+ +
+
+ } + > + {children} +
+
+
+
+
-
diff --git a/services/frontend/src/app/(dashboard)/messages/view.tsx b/services/frontend/src/app/(dashboard)/messages/view.tsx index 1d4efa9..0b8b5e7 100644 --- a/services/frontend/src/app/(dashboard)/messages/view.tsx +++ b/services/frontend/src/app/(dashboard)/messages/view.tsx @@ -3,8 +3,6 @@ import { Flag, Image, Loader2, Search } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; -import { GlassCard } from "@/components/glass/card"; -import { GlassPanel } from "@/components/glass/panel"; import { SubNav } from "@/components/layout/sub-nav"; import { Lightbox } from "@/components/messages/lightbox"; import { extractFirstImage } from "@/components/messages/message-card"; @@ -13,6 +11,7 @@ import { MessageList } from "@/components/messages/message-list"; import { SearchOverlay } from "@/components/messages/search-overlay"; import { EmptyState, ErrorState, LoadingSkeleton } from "@/components/shared"; import { GuildSelector } from "@/components/shared/guild-selector"; +import { Card } from "@/components/ui/card"; import { Select, SelectContent, @@ -218,12 +217,14 @@ export default function MessagesView({ {detailId && (
{detailLoading ? ( - - + ) : detailMessage ? (
- + ))} {items.length === 0 && ( - {children} + + {children} + diff --git a/services/frontend/src/components/dashboard/activity-chart.tsx b/services/frontend/src/components/dashboard/activity-chart.tsx index 510b19d..5cccd9b 100644 --- a/services/frontend/src/components/dashboard/activity-chart.tsx +++ b/services/frontend/src/components/dashboard/activity-chart.tsx @@ -9,8 +9,9 @@ import { XAxis, YAxis, } from "recharts"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { useMounted } from "@/lib/hooks/use-mounted"; +import { cn } from "@/lib/utils"; interface ActivityChartProps { data?: { day: string; messages: number; flagged: number }[]; @@ -28,7 +29,7 @@ export function ActivityChart({ data = [] }: ActivityChartProps) { const mounted = useMounted(); return ( - +
Message Activity @@ -123,6 +124,6 @@ export function ActivityChart({ data = [] }: ActivityChartProps) {
)}
- + ); } diff --git a/services/frontend/src/components/dashboard/channels-section.tsx b/services/frontend/src/components/dashboard/channels-section.tsx index 23af645..67aaaaa 100644 --- a/services/frontend/src/components/dashboard/channels-section.tsx +++ b/services/frontend/src/components/dashboard/channels-section.tsx @@ -2,7 +2,6 @@ import { Hash, Search } from "lucide-react"; import { useCallback, useState } from "react"; -import { GlassCard } from "@/components/glass/card"; import { EmptyState, LoadingSkeleton } from "@/components/shared"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -11,6 +10,7 @@ import { Input } from "@/components/ui/input"; import { useChannelDetail, useChannels } from "@/hooks"; import { renderMessageContent } from "@/lib/format"; import type { DashboardChannel } from "@/lib/types"; +import { cn } from "@/lib/utils"; export function ChannelsSection({ guildId }: { guildId?: string }) { const [search, setSearch] = useState(""); @@ -31,7 +31,14 @@ export function ChannelsSection({ guildId }: { guildId?: string }) { if (error) { return ( - + Failed to load channels: {error.message} - + ); } @@ -76,7 +83,9 @@ export function ChannelsSection({ guildId }: { guildId?: string }) { )}
- + {detail ? (
@@ -144,7 +153,7 @@ export function ChannelsSection({ guildId }: { guildId?: string }) {

)} - +
); } diff --git a/services/frontend/src/components/dashboard/hourly-activity-chart.tsx b/services/frontend/src/components/dashboard/hourly-activity-chart.tsx index cf64013..cf49cf8 100644 --- a/services/frontend/src/components/dashboard/hourly-activity-chart.tsx +++ b/services/frontend/src/components/dashboard/hourly-activity-chart.tsx @@ -9,8 +9,9 @@ import { XAxis, YAxis, } from "recharts"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { useMounted } from "@/lib/hooks/use-mounted"; +import { cn } from "@/lib/utils"; interface HourlyActivityChartProps { data?: { hour: number; messages: number; flagged: number }[]; @@ -46,7 +47,7 @@ export function HourlyActivityChart({ data = [] }: HourlyActivityChartProps) { const maxMessages = Math.max(...full.map((d) => d.messages)); return ( - +
Hourly Activity @@ -103,6 +104,6 @@ export function HourlyActivityChart({ data = [] }: HourlyActivityChartProps) {
)}
- + ); } diff --git a/services/frontend/src/components/dashboard/moderation-donut.tsx b/services/frontend/src/components/dashboard/moderation-donut.tsx index eb829bd..98abe6d 100644 --- a/services/frontend/src/components/dashboard/moderation-donut.tsx +++ b/services/frontend/src/components/dashboard/moderation-donut.tsx @@ -1,8 +1,9 @@ "use client"; import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { useMounted } from "@/lib/hooks/use-mounted"; +import { cn } from "@/lib/utils"; interface ModerationDonutProps { data?: { name: string; value: number; color: string }[]; @@ -27,7 +28,7 @@ export function ModerationDonut({ data = [] }: ModerationDonutProps) { : 0; return ( - +
Moderation Breakdown @@ -96,6 +97,6 @@ export function ModerationDonut({ data = [] }: ModerationDonutProps) { )}
-
+
); } diff --git a/services/frontend/src/components/dashboard/reactions-section.tsx b/services/frontend/src/components/dashboard/reactions-section.tsx index 9f5b2be..b942d05 100644 --- a/services/frontend/src/components/dashboard/reactions-section.tsx +++ b/services/frontend/src/components/dashboard/reactions-section.tsx @@ -1,11 +1,12 @@ "use client"; import { Flame, Heart, SmilePlus } from "lucide-react"; -import { GlassCard } from "@/components/glass/card"; import { EmptyState, LoadingSkeleton } from "@/components/shared"; import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; import { useTopReactions, useTopReactors } from "@/hooks"; import { renderMessageContent } from "@/lib/format"; +import { cn } from "@/lib/utils"; function formatReactionTime(ts: number | null): string { if (!ts) return ""; @@ -31,19 +32,23 @@ export function ReactionsSection() { {reactionsLoading ? ( ) : !reactions || reactions.length === 0 ? ( - + - +
) : (
{reactions.map((r, i) => ( - {i + 1} @@ -76,7 +81,7 @@ export function ReactionsSection() { {r.reaction_count} - + ))}
)} @@ -90,19 +95,23 @@ export function ReactionsSection() { {reactorsLoading ? ( ) : !reactors || reactors.length === 0 ? ( - + - + ) : (
{reactors.map((r, i) => ( - {i + 1} @@ -120,7 +129,7 @@ export function ReactionsSection() { {r.net_count} - + ))}
)} diff --git a/services/frontend/src/components/dashboard/stat-card.tsx b/services/frontend/src/components/dashboard/stat-card.tsx index 37de095..22b3fcd 100644 --- a/services/frontend/src/components/dashboard/stat-card.tsx +++ b/services/frontend/src/components/dashboard/stat-card.tsx @@ -2,7 +2,7 @@ import type { LucideIcon } from "lucide-react"; import { Area, AreaChart, ResponsiveContainer } from "recharts"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { useMounted } from "@/lib/hooks/use-mounted"; import { cn } from "@/lib/utils"; @@ -39,7 +39,13 @@ export function StatCard({ const numValue = typeof value === "number" ? value : Number(value); return ( - +
@@ -90,6 +96,6 @@ export function StatCard({
)} - + ); } diff --git a/services/frontend/src/components/dashboard/top-channels-chart.tsx b/services/frontend/src/components/dashboard/top-channels-chart.tsx index 707a333..e065313 100644 --- a/services/frontend/src/components/dashboard/top-channels-chart.tsx +++ b/services/frontend/src/components/dashboard/top-channels-chart.tsx @@ -8,8 +8,9 @@ import { XAxis, YAxis, } from "recharts"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { useMounted } from "@/lib/hooks/use-mounted"; +import { cn } from "@/lib/utils"; interface TopChannelsChartProps { data?: { name: string; count: number }[]; @@ -19,7 +20,7 @@ export function TopChannelsChart({ data = [] }: TopChannelsChartProps) { const mounted = useMounted(); return ( - +
Top Channels @@ -68,6 +69,6 @@ export function TopChannelsChart({ data = [] }: TopChannelsChartProps) {
)}
- + ); } diff --git a/services/frontend/src/components/dashboard/users-section.tsx b/services/frontend/src/components/dashboard/users-section.tsx index 8af450b..018929a 100644 --- a/services/frontend/src/components/dashboard/users-section.tsx +++ b/services/frontend/src/components/dashboard/users-section.tsx @@ -2,7 +2,6 @@ import { Search, Users, UserX } from "lucide-react"; import { useCallback, useState } from "react"; -import { GlassCard } from "@/components/glass/card"; import { EmptyState, LoadingSkeleton } from "@/components/shared"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; @@ -12,6 +11,7 @@ import { Input } from "@/components/ui/input"; import { useUserDetail, useUsers } from "@/hooks"; import { renderMessageContent } from "@/lib/format"; import type { DashboardUser } from "@/lib/types"; +import { cn } from "@/lib/utils"; const TRUST_TIERS = [ { @@ -67,7 +67,14 @@ export function UsersSection() { if (error) { return ( - + Failed to load users: {error.message} - + ); } @@ -112,7 +119,9 @@ export function UsersSection() { )}
- + {detail ? (
@@ -196,7 +205,7 @@ export function UsersSection() {

)} - +
); } diff --git a/services/frontend/src/components/glass/card.tsx b/services/frontend/src/components/glass/card.tsx deleted file mode 100644 index 3fcc9b1..0000000 --- a/services/frontend/src/components/glass/card.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import type { ComponentPropsWithoutRef } from "react"; -import { cn } from "@/lib/utils"; - -type GlassVariant = "base" | "elevated" | "interactive" | "danger"; - -interface GlassCardProps extends ComponentPropsWithoutRef<"div"> { - variant?: GlassVariant; -} - -const variantStyles: Record = { - base: "glass rounded-[var(--radius-card)]", - elevated: "glass-elevated rounded-[var(--radius-card)]", - interactive: - "glass rounded-[var(--radius-card)] transition-all duration-150 hover:scale-[1.01] hover:border-[var(--color-border-glow)] cursor-pointer", - danger: "glass rounded-[var(--radius-card)] border-red-500/30", -}; - -export function GlassCard({ - variant = "base", - className, - children, - ...props -}: GlassCardProps) { - return ( -
- {children} -
- ); -} diff --git a/services/frontend/src/components/glass/index.ts b/services/frontend/src/components/glass/index.ts deleted file mode 100644 index 756eb98..0000000 --- a/services/frontend/src/components/glass/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GlassCard } from "./card"; -export { GlassPanel } from "./panel"; diff --git a/services/frontend/src/components/glass/panel.tsx b/services/frontend/src/components/glass/panel.tsx deleted file mode 100644 index e0801f4..0000000 --- a/services/frontend/src/components/glass/panel.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import type { ComponentPropsWithoutRef } from "react"; -import { cn } from "@/lib/utils"; - -interface GlassPanelProps extends ComponentPropsWithoutRef<"div"> { - dense?: boolean; -} - -export function GlassPanel({ - dense = false, - className, - children, - ...props -}: GlassPanelProps) { - return ( -
- {children} -
- ); -} diff --git a/services/frontend/src/components/layout/app-sidebar.tsx b/services/frontend/src/components/layout/app-sidebar.tsx new file mode 100644 index 0000000..f9691c0 --- /dev/null +++ b/services/frontend/src/components/layout/app-sidebar.tsx @@ -0,0 +1,84 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { ThemeToggle } from "@/components/layout/theme-toggle"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarRail, +} from "@/components/ui/sidebar"; +import { isActivePath, navItems } from "@/lib/navigation"; + +/** + * Primary application navigation — pure shadcn Sidebar primitives. + * Renders as a fixed desktop rail (collapsible to icons) and a Sheet + * on mobile via the Sidebar component itself. + */ +export function AppSidebar() { + const pathname = usePathname(); + + return ( + + + + + }> +
+ D +
+
+ Discord Automod + + Moderation dashboard + +
+
+
+
+
+ + + + Navigation + + {navItems.map(({ href, label, icon: Icon, matchPrefix }) => { + const active = isActivePath(pathname, matchPrefix); + return ( + + } + isActive={active} + tooltip={label} + className="group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center" + > + + {label} + + + ); + })} + + + + + + + + + + + + + +
+ ); +} diff --git a/services/frontend/src/components/layout/hidden-sidebar.tsx b/services/frontend/src/components/layout/hidden-sidebar.tsx deleted file mode 100644 index 5e39723..0000000 --- a/services/frontend/src/components/layout/hidden-sidebar.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { GuildSelector } from "@/components/shared/guild-selector"; - -interface HiddenSidebarProps { - guildId: string; - onGuildChange: (guildId: string) => void; -} - -export function HiddenSidebar({ guildId, onGuildChange }: HiddenSidebarProps) { - const [visible, setVisible] = useState(false); - let hideTimer: ReturnType | null = null; - - const handleMouseEnter = () => { - if (hideTimer) clearTimeout(hideTimer); - setVisible(true); - }; - - const handleMouseLeave = () => { - hideTimer = setTimeout(() => setVisible(false), 300); - }; - - return ( - <> - {/* Hotspot trigger */} - {/* biome-ignore lint/a11y/noStaticElementInteractions: transparent mouse detection zone, not interactive content */} -
- - {/* Sidebar */} -
-
- - Guilds - -
-
- -
-
- - ); -} diff --git a/services/frontend/src/components/layout/mobile-nav.tsx b/services/frontend/src/components/layout/mobile-nav.tsx deleted file mode 100644 index 9a7890d..0000000 --- a/services/frontend/src/components/layout/mobile-nav.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -import { isActivePath, mobileNavItems } from "@/lib/navigation"; -import { cn } from "@/lib/utils"; - -export function MobileNav() { - const pathname = usePathname(); - - return ( - - ); -} diff --git a/services/frontend/src/components/layout/theme-toggle.tsx b/services/frontend/src/components/layout/theme-toggle.tsx new file mode 100644 index 0000000..85be412 --- /dev/null +++ b/services/frontend/src/components/layout/theme-toggle.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ThemeToggle() { + const { setTheme } = useTheme(); + + return ( + + + } + > + + + Toggle theme + + + setTheme("light")}> + + Light + + setTheme("dark")}> + + Dark + + + + ); +} diff --git a/services/frontend/src/components/layout/top-nav.tsx b/services/frontend/src/components/layout/top-nav.tsx deleted file mode 100644 index c12b927..0000000 --- a/services/frontend/src/components/layout/top-nav.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; - -import { Moon, Sun } from "lucide-react"; -import { usePathname, useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { isActivePath, navItems } from "@/lib/navigation"; - -export function TopNav() { - const pathname = usePathname(); - const router = useRouter(); - const [theme, setTheme] = useState<"light" | "dark">("dark"); - - // Apply persisted theme on mount (client-side only — avoids SSR hydration - // mismatch from touching during server render). - useEffect(() => { - const stored = localStorage.getItem("theme") as "light" | "dark" | null; - const initial = - stored === "light" ? "light" : stored === "dark" ? "dark" : "light"; - setTheme(initial); - document.documentElement.classList.remove("light", "dark"); - document.documentElement.classList.add(initial); - }, []); - - const toggleTheme = () => { - const next = theme === "dark" ? "light" : "dark"; - setTheme(next); - localStorage.setItem("theme", next); - document.documentElement.classList.remove("light", "dark"); - document.documentElement.classList.add(next); - }; - - return ( -
- {/* Brand */} -
-
- D - -
- - Discord Automod - -
- - {/* Nav links */} - - - {/* Right side */} -
- -
-
- ); -} diff --git a/services/frontend/src/components/messages/ai-analysis-panel.tsx b/services/frontend/src/components/messages/ai-analysis-panel.tsx index 6756c04..9448afe 100644 --- a/services/frontend/src/components/messages/ai-analysis-panel.tsx +++ b/services/frontend/src/components/messages/ai-analysis-panel.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { GlassPanel } from "@/components/glass/panel"; +import { Card } from "@/components/ui/card"; import { cn } from "@/lib/utils"; interface AiAnalysisPanelProps { @@ -37,11 +37,11 @@ export function AiAnalysisPanel({ if (!status || status === "pending") { return ( - + AI analysis pending - + ); } @@ -55,7 +55,7 @@ export function AiAnalysisPanel({ : categories || []; return ( - +
AI Analysis @@ -155,6 +155,6 @@ export function AiAnalysisPanel({ {action}
)} -
+ ); } diff --git a/services/frontend/src/components/messages/message-detail-view.tsx b/services/frontend/src/components/messages/message-detail-view.tsx index e1450c2..dbbe4e9 100644 --- a/services/frontend/src/components/messages/message-detail-view.tsx +++ b/services/frontend/src/components/messages/message-detail-view.tsx @@ -1,9 +1,10 @@ "use client"; import { ArrowLeft, MessageSquare, MessagesSquare, Pencil } from "lucide-react"; -import { GlassCard } from "@/components/glass/card"; +import { Card } from "@/components/ui/card"; import { getMessageChannelLabel, renderMessageContent } from "@/lib/format"; import type { AttachmentRecord, MessageRecord } from "@/lib/types"; +import { cn } from "@/lib/utils"; import { AiAnalysisPanel } from "./ai-analysis-panel"; import { AttachmentsGrid } from "./attachments-grid"; @@ -21,7 +22,9 @@ export function MessageDetailView({ onImageClick, }: MessageDetailViewProps) { return ( - + {onBack && ( - + ) ); } diff --git a/services/frontend/src/components/ui/attachment.tsx b/services/frontend/src/components/ui/attachment.tsx deleted file mode 100644 index 20609f0..0000000 --- a/services/frontend/src/components/ui/attachment.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import * as React from "react" -import { mergeProps } from "@base-ui/react/merge-props" -import { useRender } from "@base-ui/react/use-render" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" - -const attachmentVariants = cva( - "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed", - { - variants: { - size: { - default: - "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2", - sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5", - xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1", - }, - orientation: { - horizontal: "min-w-40 items-center", - vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30", - }, - }, - } -) - -function Attachment({ - className, - state = "done", - size = "default", - orientation = "horizontal", - ...props -}: React.ComponentProps<"div"> & - VariantProps & { - state?: "idle" | "uploading" | "processing" | "error" | "done" - }) { - return ( -
- ) -} - -const attachmentMediaVariants = cva( - "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5", - { - variants: { - variant: { - icon: "", - image: - "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover", - }, - }, - defaultVariants: { - variant: "icon", - }, - } -) - -function AttachmentMedia({ - className, - variant = "icon", - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
- ) -} - -function AttachmentContent({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ) -} - -function AttachmentTitle({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ) -} - -function AttachmentDescription({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ) -} - -function AttachmentActions({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ) -} - -function AttachmentAction({ - className, - variant, - size = "icon-xs", - ...props -}: React.ComponentProps) { - return ( -