"use client" import { useMemo, useState, type ReactNode } from "react" import { Alert, AlertDescription, AlertTitle, } from "@/components/reui/alert" import { Badge } from "@/components/reui/badge" import { Frame, FramePanel } from "@/components/reui/frame" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" import { CheckIcon, CopyIcon, DownloadIcon, LoaderCircleIcon, RefreshCwIcon, TriangleAlertIcon, } from "lucide-react" import { cn } from "@/lib/utils" import { toast } from "sonner" export type CodeExportFormat = { id: string label: string filename: string code: string /** Empty / unavailable — show warning instead of code */ emptyMessage?: string /** Content came from live device fetch */ isLive?: boolean } function downloadText(filename: string, content: string) { const blob = new Blob([content], { type: "text/plain;charset=utf-8" }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = filename a.click() URL.revokeObjectURL(url) } function highlightLine(line: string): string { const trimmed = line.trimStart() if (line.startsWith("#") || trimmed.startsWith(";")) return "text-muted-foreground" if (trimmed.startsWith("[")) return "text-info" if (trimmed.startsWith("/")) return "text-info" if (/^\s+[a-zA-Z]/.test(line) || /^[A-Za-z][\w-]*=/.test(line)) return "text-foreground" return "text-foreground/90" } function CodeBlock({ code, className }: { code: string; className?: string }) { const lines = code.length ? code.split("\n") : [""] return (
{lines.map((line, i) => (
{line || " "}
))}
)
}
export interface CodeExportSheetProps {
open: boolean
onClose: () => void
title: string
description?: ReactNode
formats: CodeExportFormat[]
/** Initial format id when sheet opens */
initialFormatId?: string
/** Optional live fetch from device */
onLiveFetch?: (formatId: string) => void
liveBusy?: boolean
/** Short CTA — keep laconic */
liveLabel?: string
className?: string
/** Extra content under format tabs (e.g. meta strip) */
returnFocus?: () => HTMLElement | null
beforeCode?: ReactNode
}
/**
* Shared code export Sheet — ReUI Frame + sticky footer.
* Preview DNA: https://reui.io/preview/base/sheet-8 · Frame https://reui.io/docs/components/base/frame
* Alert: https://reui.io/docs/components/base/alert
*/
function CodeExportSheet({
open,
onClose,
title,
description,
formats,
initialFormatId,
onLiveFetch,
liveBusy,
liveLabel = "С роутера",
className,
beforeCode,
returnFocus,
}: CodeExportSheetProps) {
const firstId = formats[0]?.id ?? "default"
const initialTab = initialFormatId && formats.some(format => format.id === initialFormatId) ? initialFormatId : firstId
const resetKey = JSON.stringify([open, initialTab])
const [previousKey, setPreviousKey] = useState(resetKey)
const [tab, setTab] = useState(initialTab)
const [copiedCode, setCopiedCode] = useStateЛокальный снимок. Подтяните с роутера, если нужен private-key с устройства.
) : null}