@sesionario/blocks 1.39.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/EmptyState.tsx","../src/components/office/officeFile.ts","../src/components/office/OfficeViewer.tsx","../src/components/ui/dialog.tsx","../src/components/media/MediaViewerDialog.tsx","../src/components/office/OfficeViewerDialog.tsx"],"names":["twMerge","clsx","jsxs","jsx","Loader2","useState","useEffect","DialogPrimitive","XIcon","X","Download"],"mappings":"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","file":"office.cjs","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { AlertTriangle, Inbox, Loader2 } from \"lucide-react\";\n\nimport { cn } from \"../lib/utils\";\nimport {\n\tEmpty,\n\tEmptyContent,\n\tEmptyDescription,\n\tEmptyHeader,\n\tEmptyMedia,\n\tEmptyTitle,\n} from \"./ui/empty\";\n\nexport function LoadingState({\n\tlabel = \"Cargando…\",\n\tclassName,\n}: {\n\tlabel?: string;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex items-center justify-center gap-2 py-12 text-sm text-muted-foreground\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n\t\t\t<span>{label}</span>\n\t\t</div>\n\t);\n}\n\nexport function ErrorState({\n\ttitle = \"Algo salió mal\",\n\tdescription,\n\taction,\n\tclassName,\n}: {\n\ttitle?: string;\n\tdescription?: string;\n\taction?: React.ReactNode;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div\n\t\t\trole=\"alert\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex flex-col items-center gap-3 rounded-xl border border-dashed border-destructive/40 bg-destructive/5 py-12 text-center\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<AlertTriangle className=\"size-6 text-destructive\" aria-hidden=\"true\" />\n\t\t\t<div>\n\t\t\t\t<p className=\"font-medium\">{title}</p>\n\t\t\t\t{description ? (\n\t\t\t\t\t<p className=\"mt-1 text-sm text-muted-foreground\">{description}</p>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t{action}\n\t\t</div>\n\t);\n}\n\n/** Accept any icon component with a className prop — avoids LucideIcon\n * version skew between blocks peers and consuming apps. */\nexport type EmptyStateIcon = React.ComponentType<{\n\tclassName?: string;\n\t\"aria-hidden\"?: boolean | \"true\" | \"false\";\n}>;\n\nexport type EmptyStateProps = {\n\ttitle: string;\n\tdescription?: string;\n\taction?: React.ReactNode;\n\ticon?: EmptyStateIcon;\n\tvariant?: \"default\" | \"compact\";\n\tclassName?: string;\n};\n\nexport function EmptyState({\n\ttitle,\n\tdescription,\n\taction,\n\ticon: Icon = Inbox,\n\tvariant = \"default\",\n\tclassName,\n}: EmptyStateProps) {\n\tconst isCompact = variant === \"compact\";\n\n\treturn (\n\t\t<Empty\n\t\t\tclassName={cn(\n\t\t\t\t\"gap-3\",\n\t\t\t\tisCompact\n\t\t\t\t\t? \"flex-none rounded-none px-4 py-6\"\n\t\t\t\t\t: \"flex-none rounded-xl border border-dashed px-6 py-12\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<EmptyHeader className=\"gap-3\">\n\t\t\t\t<EmptyMedia\n\t\t\t\t\tvariant=\"icon\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"mb-0 bg-transparent text-muted-foreground\",\n\t\t\t\t\t\tisCompact\n\t\t\t\t\t\t\t? \"size-auto [&_svg:not([class*='size-'])]:size-8 [&_svg]:opacity-50\"\n\t\t\t\t\t\t\t: \"size-auto [&_svg:not([class*='size-'])]:size-6\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<Icon aria-hidden=\"true\" />\n\t\t\t\t</EmptyMedia>\n\t\t\t\t<EmptyTitle\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"font-medium tracking-normal\",\n\t\t\t\t\t\tisCompact ? \"text-sm\" : \"text-base\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{title}\n\t\t\t\t</EmptyTitle>\n\t\t\t\t{description ? (\n\t\t\t\t\t<EmptyDescription className=\"mt-0\">{description}</EmptyDescription>\n\t\t\t\t) : null}\n\t\t\t</EmptyHeader>\n\t\t\t{action ? <EmptyContent className=\"gap-0\">{action}</EmptyContent> : null}\n\t\t</Empty>\n\t);\n}\n","/**\n * Microsoft Office Online (`view.officeapps.live.com`) only renders these\n * types. Keep this list in sync with the workers that mint cookie-less view\n * URLs (auth-svc evidence, ads-svc documents).\n */\nexport const OFFICE_MIME_TYPES = [\n\t\"application/msword\",\n\t\"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\t\"application/vnd.ms-excel\",\n\t\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t\"application/vnd.ms-powerpoint\",\n\t\"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n] as const;\n\nexport type OfficeMimeType = (typeof OFFICE_MIME_TYPES)[number];\n\nexport const OFFICE_EXTENSIONS = [\n\t\".doc\",\n\t\".docx\",\n\t\".xls\",\n\t\".xlsx\",\n\t\".ppt\",\n\t\".pptx\",\n] as const;\n\nconst MIME_SET = new Set<string>(OFFICE_MIME_TYPES);\nconst EXTENSION_SET = new Set<string>(OFFICE_EXTENSIONS);\n\nconst OFFICE_VIEWER_EMBED = \"https://view.officeapps.live.com/op/embed.aspx\";\n\nexport function isOfficeMime(mime: string | null | undefined): boolean {\n\tconst normalized = mime?.split(\";\")[0]?.trim().toLowerCase();\n\treturn Boolean(normalized && MIME_SET.has(normalized));\n}\n\nexport function isOfficeFile(input: {\n\tfileName?: string | null;\n\tmime?: string | null;\n}): boolean {\n\tif (isOfficeMime(input.mime)) return true;\n\tconst name = input.fileName?.trim().toLowerCase() ?? \"\";\n\tconst dot = name.lastIndexOf(\".\");\n\tif (dot < 0) return false;\n\treturn EXTENSION_SET.has(name.slice(dot));\n}\n\n/** Embed URL for the free Office Online viewer. `fileUrl` must be publicly fetchable. */\nexport function officeViewerSrc(fileUrl: string): string {\n\treturn `${OFFICE_VIEWER_EMBED}?src=${encodeURIComponent(fileUrl)}`;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\nimport { LoadingState } from \"../EmptyState\";\nimport { officeViewerSrc } from \"./officeFile\";\n\nexport type OfficeViewerLabels = {\n\tloading?: string;\n};\n\nexport type OfficeViewerProps = {\n\tfileUrl: string;\n\ttitle: string;\n\tlabels?: OfficeViewerLabels;\n\tclassName?: string;\n};\n\n/**\n * Embeds the free Microsoft Office Online viewer. The file URL must be\n * reachable by Microsoft's servers (no cookies, no JWT).\n */\nexport function OfficeViewer({\n\tfileUrl,\n\ttitle,\n\tlabels,\n\tclassName,\n}: OfficeViewerProps) {\n\tconst [loaded, setLoaded] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetLoaded(false);\n\t}, [fileUrl]);\n\n\treturn (\n\t\t<div className={cn(\"relative flex min-h-0 flex-1 flex-col\", className)}>\n\t\t\t{!loaded ? (\n\t\t\t\t<div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background\">\n\t\t\t\t\t<LoadingState label={labels?.loading ?? \"Cargando documento…\"} />\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t<iframe\n\t\t\t\tsrc={officeViewerSrc(fileUrl)}\n\t\t\t\ttitle={title}\n\t\t\t\tclassName=\"min-h-0 w-full flex-1 border-0\"\n\t\t\t\tonLoad={() => setLoaded(true)}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n\treturn <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n\treturn <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n\treturn <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n\treturn <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n\treturn (\n\t\t<DialogPrimitive.Overlay\n\t\t\tdata-slot=\"dialog-overlay\"\n\t\t\tclassName={cn(\n\t\t\t\t\"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogContent({\n\tclassName,\n\tchildren,\n\tshowCloseButton = true,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n\tshowCloseButton?: boolean;\n}) {\n\treturn (\n\t\t<DialogPortal data-slot=\"dialog-portal\">\n\t\t\t<DialogOverlay />\n\t\t\t<DialogPrimitive.Content\n\t\t\t\tdata-slot=\"dialog-content\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t\t{showCloseButton && (\n\t\t\t\t\t<DialogPrimitive.Close\n\t\t\t\t\t\tdata-slot=\"dialog-close\"\n\t\t\t\t\t\tclassName=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<XIcon />\n\t\t\t\t\t\t<span className=\"sr-only\">Close</span>\n\t\t\t\t\t</DialogPrimitive.Close>\n\t\t\t\t)}\n\t\t\t</DialogPrimitive.Content>\n\t\t</DialogPortal>\n\t);\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"dialog-header\"\n\t\t\tclassName={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"dialog-footer\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogTitle({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n\treturn (\n\t\t<DialogPrimitive.Title\n\t\t\tdata-slot=\"dialog-title\"\n\t\t\tclassName={cn(\"text-lg leading-none font-semibold\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogDescription({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n\treturn (\n\t\t<DialogPrimitive.Description\n\t\t\tdata-slot=\"dialog-description\"\n\t\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport {\n\tDialog,\n\tDialogClose,\n\tDialogContent,\n\tDialogDescription,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogOverlay,\n\tDialogPortal,\n\tDialogTitle,\n\tDialogTrigger,\n};\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"../../lib/utils\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogTitle,\n} from \"../ui/dialog\";\n\nexport type MediaViewerDialogLabels = {\n\tclose?: string;\n\ttitle?: string;\n\tdescription?: string;\n};\n\nexport type MediaViewerDialogProps = {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tchildren: ReactNode;\n\tlabels?: MediaViewerDialogLabels;\n\tclassName?: string;\n\t/** Extra classes for the dialog content shell. */\n\tcontentClassName?: string;\n\tshowCloseButton?: boolean;\n};\n\n/**\n * Edge-to-edge fullscreen dialog shell for media viewers (post detail, PDF,\n * etc.). Uses the shared Radix dialog for focus trap, Esc and `aria-modal`.\n * Pass `contentClassName` to opt into a contained desktop card when needed.\n */\nexport function MediaViewerDialog({\n\topen,\n\tonOpenChange,\n\tchildren,\n\tlabels,\n\tclassName,\n\tcontentClassName,\n\tshowCloseButton = true,\n}: MediaViewerDialogProps) {\n\tconst closeLabel = labels?.close ?? \"Cerrar\";\n\tconst title = labels?.title ?? \"Visor\";\n\tconst description = labels?.description ?? title;\n\n\treturn (\n\t\t<Dialog open={open} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent\n\t\t\t\tshowCloseButton={false}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t// flex-col is required: without it the single child is a row item\n\t\t\t\t\t// sized to max-content (toolbar width) and the viewport collapses.\n\t\t\t\t\t\"inset-0 flex flex-col h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 top-0 left-0 gap-0 overflow-hidden rounded-none border-0 p-0 shadow-none sm:max-w-none\",\n\t\t\t\t\tcontentClassName,\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<DialogTitle className=\"sr-only\">{title}</DialogTitle>\n\t\t\t\t<DialogDescription className=\"sr-only\">{description}</DialogDescription>\n\t\t\t\t{showCloseButton && (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={() => onOpenChange(false)}\n\t\t\t\t\t\taria-label={closeLabel}\n\t\t\t\t\t\tclassName=\"absolute top-3 right-3 z-20 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<X className=\"h-5 w-5\" />\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t{children}\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n","\"use client\";\n\nimport { Download, X } from \"lucide-react\";\n\nimport { MediaViewerDialog } from \"../media/MediaViewerDialog\";\nimport { OfficeViewer, type OfficeViewerLabels } from \"./OfficeViewer\";\n\nexport type OfficeViewerDialogProps = {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\ttitle: string;\n\tfileUrl: string;\n\t/** Defaults to `fileUrl`. */\n\tdownloadUrl?: string;\n\tlabels?: OfficeViewerLabels & {\n\t\tclose?: string;\n\t\tdownload?: string;\n\t\tdialogDescription?: string;\n\t};\n};\n\n/**\n * Fullscreen shell around {@link OfficeViewer}. Title, close, and download\n * stay in one toolbar row, matching {@link PdfViewerDialog}.\n */\nexport function OfficeViewerDialog({\n\topen,\n\tonOpenChange,\n\ttitle,\n\tfileUrl,\n\tdownloadUrl,\n\tlabels,\n}: OfficeViewerDialogProps) {\n\tconst closeLabel = labels?.close ?? \"Cerrar\";\n\tconst downloadLabel = labels?.download ?? \"Descargar\";\n\tconst href = downloadUrl ?? fileUrl;\n\n\treturn (\n\t\t<MediaViewerDialog\n\t\t\topen={open}\n\t\t\tonOpenChange={onOpenChange}\n\t\t\tshowCloseButton={false}\n\t\t\tlabels={{\n\t\t\t\tclose: closeLabel,\n\t\t\t\ttitle,\n\t\t\t\tdescription: labels?.dialogDescription ?? title,\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"flex min-h-0 w-full min-w-0 flex-1 flex-col\">\n\t\t\t\t<div className=\"flex shrink-0 items-center gap-2 border-b px-2 py-2\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={() => onOpenChange(false)}\n\t\t\t\t\t\taria-label={closeLabel}\n\t\t\t\t\t\tclassName=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<X className=\"h-4 w-4\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<span className=\"min-w-0 flex-1 truncate text-sm font-medium text-foreground\">\n\t\t\t\t\t\t{title}\n\t\t\t\t\t</span>\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={href}\n\t\t\t\t\t\tdownload\n\t\t\t\t\t\tclassName=\"inline-flex h-8 shrink-0 items-center gap-1 rounded-md border bg-background px-2 text-sm text-foreground hover:bg-accent\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n\t\t\t\t\t\t{downloadLabel}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<OfficeViewer\n\t\t\t\t\tfileUrl={fileUrl}\n\t\t\t\t\ttitle={title}\n\t\t\t\t\tlabels={labels}\n\t\t\t\t\tclassName=\"min-h-0 flex-1\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</MediaViewerDialog>\n\t);\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+
3
+ type OfficeViewerLabels = {
4
+ loading?: string;
5
+ };
6
+ type OfficeViewerProps = {
7
+ fileUrl: string;
8
+ title: string;
9
+ labels?: OfficeViewerLabels;
10
+ className?: string;
11
+ };
12
+ /**
13
+ * Embeds the free Microsoft Office Online viewer. The file URL must be
14
+ * reachable by Microsoft's servers (no cookies, no JWT).
15
+ */
16
+ declare function OfficeViewer({ fileUrl, title, labels, className, }: OfficeViewerProps): React.JSX.Element;
17
+
18
+ type OfficeViewerDialogProps = {
19
+ open: boolean;
20
+ onOpenChange: (open: boolean) => void;
21
+ title: string;
22
+ fileUrl: string;
23
+ /** Defaults to `fileUrl`. */
24
+ downloadUrl?: string;
25
+ labels?: OfficeViewerLabels & {
26
+ close?: string;
27
+ download?: string;
28
+ dialogDescription?: string;
29
+ };
30
+ };
31
+ /**
32
+ * Fullscreen shell around {@link OfficeViewer}. Title, close, and download
33
+ * stay in one toolbar row, matching {@link PdfViewerDialog}.
34
+ */
35
+ declare function OfficeViewerDialog({ open, onOpenChange, title, fileUrl, downloadUrl, labels, }: OfficeViewerDialogProps): React.JSX.Element;
36
+
37
+ /**
38
+ * Microsoft Office Online (`view.officeapps.live.com`) only renders these
39
+ * types. Keep this list in sync with the workers that mint cookie-less view
40
+ * URLs (auth-svc evidence, ads-svc documents).
41
+ */
42
+ declare const OFFICE_MIME_TYPES: readonly ["application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation"];
43
+ type OfficeMimeType = (typeof OFFICE_MIME_TYPES)[number];
44
+ declare const OFFICE_EXTENSIONS: readonly [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"];
45
+ declare function isOfficeMime(mime: string | null | undefined): boolean;
46
+ declare function isOfficeFile(input: {
47
+ fileName?: string | null;
48
+ mime?: string | null;
49
+ }): boolean;
50
+ /** Embed URL for the free Office Online viewer. `fileUrl` must be publicly fetchable. */
51
+ declare function officeViewerSrc(fileUrl: string): string;
52
+
53
+ export { OFFICE_EXTENSIONS, OFFICE_MIME_TYPES, type OfficeMimeType, OfficeViewer, OfficeViewerDialog, type OfficeViewerDialogProps, type OfficeViewerLabels, type OfficeViewerProps, isOfficeFile, isOfficeMime, officeViewerSrc };
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+
3
+ type OfficeViewerLabels = {
4
+ loading?: string;
5
+ };
6
+ type OfficeViewerProps = {
7
+ fileUrl: string;
8
+ title: string;
9
+ labels?: OfficeViewerLabels;
10
+ className?: string;
11
+ };
12
+ /**
13
+ * Embeds the free Microsoft Office Online viewer. The file URL must be
14
+ * reachable by Microsoft's servers (no cookies, no JWT).
15
+ */
16
+ declare function OfficeViewer({ fileUrl, title, labels, className, }: OfficeViewerProps): React.JSX.Element;
17
+
18
+ type OfficeViewerDialogProps = {
19
+ open: boolean;
20
+ onOpenChange: (open: boolean) => void;
21
+ title: string;
22
+ fileUrl: string;
23
+ /** Defaults to `fileUrl`. */
24
+ downloadUrl?: string;
25
+ labels?: OfficeViewerLabels & {
26
+ close?: string;
27
+ download?: string;
28
+ dialogDescription?: string;
29
+ };
30
+ };
31
+ /**
32
+ * Fullscreen shell around {@link OfficeViewer}. Title, close, and download
33
+ * stay in one toolbar row, matching {@link PdfViewerDialog}.
34
+ */
35
+ declare function OfficeViewerDialog({ open, onOpenChange, title, fileUrl, downloadUrl, labels, }: OfficeViewerDialogProps): React.JSX.Element;
36
+
37
+ /**
38
+ * Microsoft Office Online (`view.officeapps.live.com`) only renders these
39
+ * types. Keep this list in sync with the workers that mint cookie-less view
40
+ * URLs (auth-svc evidence, ads-svc documents).
41
+ */
42
+ declare const OFFICE_MIME_TYPES: readonly ["application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation"];
43
+ type OfficeMimeType = (typeof OFFICE_MIME_TYPES)[number];
44
+ declare const OFFICE_EXTENSIONS: readonly [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"];
45
+ declare function isOfficeMime(mime: string | null | undefined): boolean;
46
+ declare function isOfficeFile(input: {
47
+ fileName?: string | null;
48
+ mime?: string | null;
49
+ }): boolean;
50
+ /** Embed URL for the free Office Online viewer. `fileUrl` must be publicly fetchable. */
51
+ declare function officeViewerSrc(fileUrl: string): string;
52
+
53
+ export { OFFICE_EXTENSIONS, OFFICE_MIME_TYPES, type OfficeMimeType, OfficeViewer, OfficeViewerDialog, type OfficeViewerDialogProps, type OfficeViewerLabels, type OfficeViewerProps, isOfficeFile, isOfficeMime, officeViewerSrc };
package/dist/office.js ADDED
@@ -0,0 +1,282 @@
1
+ "use client";
2
+ import { useState, useEffect } from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { X, Download, Loader2, XIcon } from 'lucide-react';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
8
+
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ function LoadingState({
13
+ label = "Cargando\u2026",
14
+ className
15
+ }) {
16
+ return /* @__PURE__ */ jsxs(
17
+ "div",
18
+ {
19
+ role: "status",
20
+ "aria-live": "polite",
21
+ className: cn(
22
+ "flex items-center justify-center gap-2 py-12 text-sm text-muted-foreground",
23
+ className
24
+ ),
25
+ children: [
26
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }),
27
+ /* @__PURE__ */ jsx("span", { children: label })
28
+ ]
29
+ }
30
+ );
31
+ }
32
+
33
+ // src/components/office/officeFile.ts
34
+ var OFFICE_MIME_TYPES = [
35
+ "application/msword",
36
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
37
+ "application/vnd.ms-excel",
38
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
39
+ "application/vnd.ms-powerpoint",
40
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
41
+ ];
42
+ var OFFICE_EXTENSIONS = [
43
+ ".doc",
44
+ ".docx",
45
+ ".xls",
46
+ ".xlsx",
47
+ ".ppt",
48
+ ".pptx"
49
+ ];
50
+ var MIME_SET = new Set(OFFICE_MIME_TYPES);
51
+ var EXTENSION_SET = new Set(OFFICE_EXTENSIONS);
52
+ var OFFICE_VIEWER_EMBED = "https://view.officeapps.live.com/op/embed.aspx";
53
+ function isOfficeMime(mime) {
54
+ const normalized = mime?.split(";")[0]?.trim().toLowerCase();
55
+ return Boolean(normalized && MIME_SET.has(normalized));
56
+ }
57
+ function isOfficeFile(input) {
58
+ if (isOfficeMime(input.mime)) return true;
59
+ const name = input.fileName?.trim().toLowerCase() ?? "";
60
+ const dot = name.lastIndexOf(".");
61
+ if (dot < 0) return false;
62
+ return EXTENSION_SET.has(name.slice(dot));
63
+ }
64
+ function officeViewerSrc(fileUrl) {
65
+ return `${OFFICE_VIEWER_EMBED}?src=${encodeURIComponent(fileUrl)}`;
66
+ }
67
+ function OfficeViewer({
68
+ fileUrl,
69
+ title,
70
+ labels,
71
+ className
72
+ }) {
73
+ const [loaded, setLoaded] = useState(false);
74
+ useEffect(() => {
75
+ setLoaded(false);
76
+ }, [fileUrl]);
77
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative flex min-h-0 flex-1 flex-col", className), children: [
78
+ !loaded ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center bg-background", children: /* @__PURE__ */ jsx(LoadingState, { label: labels?.loading ?? "Cargando documento\u2026" }) }) : null,
79
+ /* @__PURE__ */ jsx(
80
+ "iframe",
81
+ {
82
+ src: officeViewerSrc(fileUrl),
83
+ title,
84
+ className: "min-h-0 w-full flex-1 border-0",
85
+ onLoad: () => setLoaded(true)
86
+ }
87
+ )
88
+ ] });
89
+ }
90
+ function Dialog({
91
+ ...props
92
+ }) {
93
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
94
+ }
95
+ function DialogPortal({
96
+ ...props
97
+ }) {
98
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
99
+ }
100
+ function DialogOverlay({
101
+ className,
102
+ ...props
103
+ }) {
104
+ return /* @__PURE__ */ jsx(
105
+ DialogPrimitive.Overlay,
106
+ {
107
+ "data-slot": "dialog-overlay",
108
+ className: cn(
109
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
110
+ className
111
+ ),
112
+ ...props
113
+ }
114
+ );
115
+ }
116
+ function DialogContent({
117
+ className,
118
+ children,
119
+ showCloseButton = true,
120
+ ...props
121
+ }) {
122
+ return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
123
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
124
+ /* @__PURE__ */ jsxs(
125
+ DialogPrimitive.Content,
126
+ {
127
+ "data-slot": "dialog-content",
128
+ className: cn(
129
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
130
+ className
131
+ ),
132
+ ...props,
133
+ children: [
134
+ children,
135
+ showCloseButton && /* @__PURE__ */ jsxs(
136
+ DialogPrimitive.Close,
137
+ {
138
+ "data-slot": "dialog-close",
139
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
140
+ children: [
141
+ /* @__PURE__ */ jsx(XIcon, {}),
142
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
143
+ ]
144
+ }
145
+ )
146
+ ]
147
+ }
148
+ )
149
+ ] });
150
+ }
151
+ function DialogTitle({
152
+ className,
153
+ ...props
154
+ }) {
155
+ return /* @__PURE__ */ jsx(
156
+ DialogPrimitive.Title,
157
+ {
158
+ "data-slot": "dialog-title",
159
+ className: cn("text-lg leading-none font-semibold", className),
160
+ ...props
161
+ }
162
+ );
163
+ }
164
+ function DialogDescription({
165
+ className,
166
+ ...props
167
+ }) {
168
+ return /* @__PURE__ */ jsx(
169
+ DialogPrimitive.Description,
170
+ {
171
+ "data-slot": "dialog-description",
172
+ className: cn("text-muted-foreground text-sm", className),
173
+ ...props
174
+ }
175
+ );
176
+ }
177
+ function MediaViewerDialog({
178
+ open,
179
+ onOpenChange,
180
+ children,
181
+ labels,
182
+ className,
183
+ contentClassName,
184
+ showCloseButton = true
185
+ }) {
186
+ const closeLabel = labels?.close ?? "Cerrar";
187
+ const title = labels?.title ?? "Visor";
188
+ const description = labels?.description ?? title;
189
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
190
+ DialogContent,
191
+ {
192
+ showCloseButton: false,
193
+ className: cn(
194
+ // flex-col is required: without it the single child is a row item
195
+ // sized to max-content (toolbar width) and the viewport collapses.
196
+ "inset-0 flex flex-col h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 top-0 left-0 gap-0 overflow-hidden rounded-none border-0 p-0 shadow-none sm:max-w-none",
197
+ contentClassName,
198
+ className
199
+ ),
200
+ children: [
201
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: title }),
202
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: description }),
203
+ showCloseButton && /* @__PURE__ */ jsx(
204
+ "button",
205
+ {
206
+ type: "button",
207
+ onClick: () => onOpenChange(false),
208
+ "aria-label": closeLabel,
209
+ className: "absolute top-3 right-3 z-20 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70",
210
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
211
+ }
212
+ ),
213
+ children
214
+ ]
215
+ }
216
+ ) });
217
+ }
218
+ function OfficeViewerDialog({
219
+ open,
220
+ onOpenChange,
221
+ title,
222
+ fileUrl,
223
+ downloadUrl,
224
+ labels
225
+ }) {
226
+ const closeLabel = labels?.close ?? "Cerrar";
227
+ const downloadLabel = labels?.download ?? "Descargar";
228
+ const href = downloadUrl ?? fileUrl;
229
+ return /* @__PURE__ */ jsx(
230
+ MediaViewerDialog,
231
+ {
232
+ open,
233
+ onOpenChange,
234
+ showCloseButton: false,
235
+ labels: {
236
+ close: closeLabel,
237
+ title,
238
+ description: labels?.dialogDescription ?? title
239
+ },
240
+ children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 w-full min-w-0 flex-1 flex-col", children: [
241
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b px-2 py-2", children: [
242
+ /* @__PURE__ */ jsx(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ onClick: () => onOpenChange(false),
247
+ "aria-label": closeLabel,
248
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent",
249
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: title }),
253
+ /* @__PURE__ */ jsxs(
254
+ "a",
255
+ {
256
+ href,
257
+ download: true,
258
+ className: "inline-flex h-8 shrink-0 items-center gap-1 rounded-md border bg-background px-2 text-sm text-foreground hover:bg-accent",
259
+ children: [
260
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4", "aria-hidden": "true" }),
261
+ downloadLabel
262
+ ]
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsx(
267
+ OfficeViewer,
268
+ {
269
+ fileUrl,
270
+ title,
271
+ labels,
272
+ className: "min-h-0 flex-1"
273
+ }
274
+ )
275
+ ] })
276
+ }
277
+ );
278
+ }
279
+
280
+ export { OFFICE_EXTENSIONS, OFFICE_MIME_TYPES, OfficeViewer, OfficeViewerDialog, isOfficeFile, isOfficeMime, officeViewerSrc };
281
+ //# sourceMappingURL=office.js.map
282
+ //# sourceMappingURL=office.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/EmptyState.tsx","../src/components/office/officeFile.ts","../src/components/office/OfficeViewer.tsx","../src/components/ui/dialog.tsx","../src/components/media/MediaViewerDialog.tsx","../src/components/office/OfficeViewerDialog.tsx"],"names":["jsxs","jsx","X"],"mappings":"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","file":"office.js","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { AlertTriangle, Inbox, Loader2 } from \"lucide-react\";\n\nimport { cn } from \"../lib/utils\";\nimport {\n\tEmpty,\n\tEmptyContent,\n\tEmptyDescription,\n\tEmptyHeader,\n\tEmptyMedia,\n\tEmptyTitle,\n} from \"./ui/empty\";\n\nexport function LoadingState({\n\tlabel = \"Cargando…\",\n\tclassName,\n}: {\n\tlabel?: string;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex items-center justify-center gap-2 py-12 text-sm text-muted-foreground\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n\t\t\t<span>{label}</span>\n\t\t</div>\n\t);\n}\n\nexport function ErrorState({\n\ttitle = \"Algo salió mal\",\n\tdescription,\n\taction,\n\tclassName,\n}: {\n\ttitle?: string;\n\tdescription?: string;\n\taction?: React.ReactNode;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div\n\t\t\trole=\"alert\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex flex-col items-center gap-3 rounded-xl border border-dashed border-destructive/40 bg-destructive/5 py-12 text-center\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<AlertTriangle className=\"size-6 text-destructive\" aria-hidden=\"true\" />\n\t\t\t<div>\n\t\t\t\t<p className=\"font-medium\">{title}</p>\n\t\t\t\t{description ? (\n\t\t\t\t\t<p className=\"mt-1 text-sm text-muted-foreground\">{description}</p>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t{action}\n\t\t</div>\n\t);\n}\n\n/** Accept any icon component with a className prop — avoids LucideIcon\n * version skew between blocks peers and consuming apps. */\nexport type EmptyStateIcon = React.ComponentType<{\n\tclassName?: string;\n\t\"aria-hidden\"?: boolean | \"true\" | \"false\";\n}>;\n\nexport type EmptyStateProps = {\n\ttitle: string;\n\tdescription?: string;\n\taction?: React.ReactNode;\n\ticon?: EmptyStateIcon;\n\tvariant?: \"default\" | \"compact\";\n\tclassName?: string;\n};\n\nexport function EmptyState({\n\ttitle,\n\tdescription,\n\taction,\n\ticon: Icon = Inbox,\n\tvariant = \"default\",\n\tclassName,\n}: EmptyStateProps) {\n\tconst isCompact = variant === \"compact\";\n\n\treturn (\n\t\t<Empty\n\t\t\tclassName={cn(\n\t\t\t\t\"gap-3\",\n\t\t\t\tisCompact\n\t\t\t\t\t? \"flex-none rounded-none px-4 py-6\"\n\t\t\t\t\t: \"flex-none rounded-xl border border-dashed px-6 py-12\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<EmptyHeader className=\"gap-3\">\n\t\t\t\t<EmptyMedia\n\t\t\t\t\tvariant=\"icon\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"mb-0 bg-transparent text-muted-foreground\",\n\t\t\t\t\t\tisCompact\n\t\t\t\t\t\t\t? \"size-auto [&_svg:not([class*='size-'])]:size-8 [&_svg]:opacity-50\"\n\t\t\t\t\t\t\t: \"size-auto [&_svg:not([class*='size-'])]:size-6\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<Icon aria-hidden=\"true\" />\n\t\t\t\t</EmptyMedia>\n\t\t\t\t<EmptyTitle\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"font-medium tracking-normal\",\n\t\t\t\t\t\tisCompact ? \"text-sm\" : \"text-base\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{title}\n\t\t\t\t</EmptyTitle>\n\t\t\t\t{description ? (\n\t\t\t\t\t<EmptyDescription className=\"mt-0\">{description}</EmptyDescription>\n\t\t\t\t) : null}\n\t\t\t</EmptyHeader>\n\t\t\t{action ? <EmptyContent className=\"gap-0\">{action}</EmptyContent> : null}\n\t\t</Empty>\n\t);\n}\n","/**\n * Microsoft Office Online (`view.officeapps.live.com`) only renders these\n * types. Keep this list in sync with the workers that mint cookie-less view\n * URLs (auth-svc evidence, ads-svc documents).\n */\nexport const OFFICE_MIME_TYPES = [\n\t\"application/msword\",\n\t\"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\t\"application/vnd.ms-excel\",\n\t\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t\"application/vnd.ms-powerpoint\",\n\t\"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n] as const;\n\nexport type OfficeMimeType = (typeof OFFICE_MIME_TYPES)[number];\n\nexport const OFFICE_EXTENSIONS = [\n\t\".doc\",\n\t\".docx\",\n\t\".xls\",\n\t\".xlsx\",\n\t\".ppt\",\n\t\".pptx\",\n] as const;\n\nconst MIME_SET = new Set<string>(OFFICE_MIME_TYPES);\nconst EXTENSION_SET = new Set<string>(OFFICE_EXTENSIONS);\n\nconst OFFICE_VIEWER_EMBED = \"https://view.officeapps.live.com/op/embed.aspx\";\n\nexport function isOfficeMime(mime: string | null | undefined): boolean {\n\tconst normalized = mime?.split(\";\")[0]?.trim().toLowerCase();\n\treturn Boolean(normalized && MIME_SET.has(normalized));\n}\n\nexport function isOfficeFile(input: {\n\tfileName?: string | null;\n\tmime?: string | null;\n}): boolean {\n\tif (isOfficeMime(input.mime)) return true;\n\tconst name = input.fileName?.trim().toLowerCase() ?? \"\";\n\tconst dot = name.lastIndexOf(\".\");\n\tif (dot < 0) return false;\n\treturn EXTENSION_SET.has(name.slice(dot));\n}\n\n/** Embed URL for the free Office Online viewer. `fileUrl` must be publicly fetchable. */\nexport function officeViewerSrc(fileUrl: string): string {\n\treturn `${OFFICE_VIEWER_EMBED}?src=${encodeURIComponent(fileUrl)}`;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\nimport { LoadingState } from \"../EmptyState\";\nimport { officeViewerSrc } from \"./officeFile\";\n\nexport type OfficeViewerLabels = {\n\tloading?: string;\n};\n\nexport type OfficeViewerProps = {\n\tfileUrl: string;\n\ttitle: string;\n\tlabels?: OfficeViewerLabels;\n\tclassName?: string;\n};\n\n/**\n * Embeds the free Microsoft Office Online viewer. The file URL must be\n * reachable by Microsoft's servers (no cookies, no JWT).\n */\nexport function OfficeViewer({\n\tfileUrl,\n\ttitle,\n\tlabels,\n\tclassName,\n}: OfficeViewerProps) {\n\tconst [loaded, setLoaded] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetLoaded(false);\n\t}, [fileUrl]);\n\n\treturn (\n\t\t<div className={cn(\"relative flex min-h-0 flex-1 flex-col\", className)}>\n\t\t\t{!loaded ? (\n\t\t\t\t<div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background\">\n\t\t\t\t\t<LoadingState label={labels?.loading ?? \"Cargando documento…\"} />\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t<iframe\n\t\t\t\tsrc={officeViewerSrc(fileUrl)}\n\t\t\t\ttitle={title}\n\t\t\t\tclassName=\"min-h-0 w-full flex-1 border-0\"\n\t\t\t\tonLoad={() => setLoaded(true)}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n\treturn <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n\treturn <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n\treturn <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n\treturn <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n\treturn (\n\t\t<DialogPrimitive.Overlay\n\t\t\tdata-slot=\"dialog-overlay\"\n\t\t\tclassName={cn(\n\t\t\t\t\"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogContent({\n\tclassName,\n\tchildren,\n\tshowCloseButton = true,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n\tshowCloseButton?: boolean;\n}) {\n\treturn (\n\t\t<DialogPortal data-slot=\"dialog-portal\">\n\t\t\t<DialogOverlay />\n\t\t\t<DialogPrimitive.Content\n\t\t\t\tdata-slot=\"dialog-content\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t\t{showCloseButton && (\n\t\t\t\t\t<DialogPrimitive.Close\n\t\t\t\t\t\tdata-slot=\"dialog-close\"\n\t\t\t\t\t\tclassName=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<XIcon />\n\t\t\t\t\t\t<span className=\"sr-only\">Close</span>\n\t\t\t\t\t</DialogPrimitive.Close>\n\t\t\t\t)}\n\t\t\t</DialogPrimitive.Content>\n\t\t</DialogPortal>\n\t);\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"dialog-header\"\n\t\t\tclassName={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"dialog-footer\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogTitle({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n\treturn (\n\t\t<DialogPrimitive.Title\n\t\t\tdata-slot=\"dialog-title\"\n\t\t\tclassName={cn(\"text-lg leading-none font-semibold\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DialogDescription({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n\treturn (\n\t\t<DialogPrimitive.Description\n\t\t\tdata-slot=\"dialog-description\"\n\t\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport {\n\tDialog,\n\tDialogClose,\n\tDialogContent,\n\tDialogDescription,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogOverlay,\n\tDialogPortal,\n\tDialogTitle,\n\tDialogTrigger,\n};\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"../../lib/utils\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogTitle,\n} from \"../ui/dialog\";\n\nexport type MediaViewerDialogLabels = {\n\tclose?: string;\n\ttitle?: string;\n\tdescription?: string;\n};\n\nexport type MediaViewerDialogProps = {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tchildren: ReactNode;\n\tlabels?: MediaViewerDialogLabels;\n\tclassName?: string;\n\t/** Extra classes for the dialog content shell. */\n\tcontentClassName?: string;\n\tshowCloseButton?: boolean;\n};\n\n/**\n * Edge-to-edge fullscreen dialog shell for media viewers (post detail, PDF,\n * etc.). Uses the shared Radix dialog for focus trap, Esc and `aria-modal`.\n * Pass `contentClassName` to opt into a contained desktop card when needed.\n */\nexport function MediaViewerDialog({\n\topen,\n\tonOpenChange,\n\tchildren,\n\tlabels,\n\tclassName,\n\tcontentClassName,\n\tshowCloseButton = true,\n}: MediaViewerDialogProps) {\n\tconst closeLabel = labels?.close ?? \"Cerrar\";\n\tconst title = labels?.title ?? \"Visor\";\n\tconst description = labels?.description ?? title;\n\n\treturn (\n\t\t<Dialog open={open} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent\n\t\t\t\tshowCloseButton={false}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t// flex-col is required: without it the single child is a row item\n\t\t\t\t\t// sized to max-content (toolbar width) and the viewport collapses.\n\t\t\t\t\t\"inset-0 flex flex-col h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 top-0 left-0 gap-0 overflow-hidden rounded-none border-0 p-0 shadow-none sm:max-w-none\",\n\t\t\t\t\tcontentClassName,\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<DialogTitle className=\"sr-only\">{title}</DialogTitle>\n\t\t\t\t<DialogDescription className=\"sr-only\">{description}</DialogDescription>\n\t\t\t\t{showCloseButton && (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={() => onOpenChange(false)}\n\t\t\t\t\t\taria-label={closeLabel}\n\t\t\t\t\t\tclassName=\"absolute top-3 right-3 z-20 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<X className=\"h-5 w-5\" />\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t{children}\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n","\"use client\";\n\nimport { Download, X } from \"lucide-react\";\n\nimport { MediaViewerDialog } from \"../media/MediaViewerDialog\";\nimport { OfficeViewer, type OfficeViewerLabels } from \"./OfficeViewer\";\n\nexport type OfficeViewerDialogProps = {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\ttitle: string;\n\tfileUrl: string;\n\t/** Defaults to `fileUrl`. */\n\tdownloadUrl?: string;\n\tlabels?: OfficeViewerLabels & {\n\t\tclose?: string;\n\t\tdownload?: string;\n\t\tdialogDescription?: string;\n\t};\n};\n\n/**\n * Fullscreen shell around {@link OfficeViewer}. Title, close, and download\n * stay in one toolbar row, matching {@link PdfViewerDialog}.\n */\nexport function OfficeViewerDialog({\n\topen,\n\tonOpenChange,\n\ttitle,\n\tfileUrl,\n\tdownloadUrl,\n\tlabels,\n}: OfficeViewerDialogProps) {\n\tconst closeLabel = labels?.close ?? \"Cerrar\";\n\tconst downloadLabel = labels?.download ?? \"Descargar\";\n\tconst href = downloadUrl ?? fileUrl;\n\n\treturn (\n\t\t<MediaViewerDialog\n\t\t\topen={open}\n\t\t\tonOpenChange={onOpenChange}\n\t\t\tshowCloseButton={false}\n\t\t\tlabels={{\n\t\t\t\tclose: closeLabel,\n\t\t\t\ttitle,\n\t\t\t\tdescription: labels?.dialogDescription ?? title,\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"flex min-h-0 w-full min-w-0 flex-1 flex-col\">\n\t\t\t\t<div className=\"flex shrink-0 items-center gap-2 border-b px-2 py-2\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={() => onOpenChange(false)}\n\t\t\t\t\t\taria-label={closeLabel}\n\t\t\t\t\t\tclassName=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<X className=\"h-4 w-4\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<span className=\"min-w-0 flex-1 truncate text-sm font-medium text-foreground\">\n\t\t\t\t\t\t{title}\n\t\t\t\t\t</span>\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={href}\n\t\t\t\t\t\tdownload\n\t\t\t\t\t\tclassName=\"inline-flex h-8 shrink-0 items-center gap-1 rounded-md border bg-background px-2 text-sm text-foreground hover:bg-accent\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n\t\t\t\t\t\t{downloadLabel}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<OfficeViewer\n\t\t\t\t\tfileUrl={fileUrl}\n\t\t\t\t\ttitle={title}\n\t\t\t\t\tlabels={labels}\n\t\t\t\t\tclassName=\"min-h-0 flex-1\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</MediaViewerDialog>\n\t);\n}\n"]}
@@ -0,0 +1,127 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var nextThemes = require('next-themes');
5
+ var react = require('react');
6
+
7
+ // src/lib/brand-chrome.ts
8
+ var BRAND_CHROME_LIGHT = "#ffffff";
9
+ var BRAND_CHROME_DARK = "#171717";
10
+ var SNAP = "data-sesionario-chrome-snap";
11
+ var MEDIA = "data-sesionario-chrome-media";
12
+ var HREF = "data-sesionario-chrome-href";
13
+ var CONTENT = "data-sesionario-chrome-content";
14
+ var CREATED = "data-sesionario-chrome-created";
15
+ function chromeColor(theme) {
16
+ switch (theme) {
17
+ case "light":
18
+ return BRAND_CHROME_LIGHT;
19
+ case "dark":
20
+ return BRAND_CHROME_DARK;
21
+ default: {
22
+ const unreachable = theme;
23
+ return unreachable;
24
+ }
25
+ }
26
+ }
27
+ function iconUrl(theme, icons) {
28
+ switch (theme) {
29
+ case "light":
30
+ return icons.light;
31
+ case "dark":
32
+ return icons.dark;
33
+ default: {
34
+ const unreachable = theme;
35
+ return unreachable;
36
+ }
37
+ }
38
+ }
39
+ function snapshot(el, attr, store) {
40
+ if (el.hasAttribute(SNAP)) return;
41
+ if (el.hasAttribute(attr)) {
42
+ el.setAttribute(store, el.getAttribute(attr) ?? "");
43
+ }
44
+ }
45
+ function restoreAttr(el, attr, store) {
46
+ if (el.hasAttribute(store)) {
47
+ el.setAttribute(attr, el.getAttribute(store) ?? "");
48
+ el.removeAttribute(store);
49
+ return;
50
+ }
51
+ el.removeAttribute(attr);
52
+ }
53
+ function restoreSnap(el, pairs) {
54
+ if (!el.hasAttribute(SNAP)) return;
55
+ for (const [attr, store] of pairs) {
56
+ restoreAttr(el, attr, store);
57
+ }
58
+ el.removeAttribute(SNAP);
59
+ }
60
+ function applyThemeChrome(doc, theme, icons) {
61
+ const created = doc.querySelectorAll(`meta[${CREATED}]`);
62
+ if (theme === void 0) {
63
+ created.forEach((node) => node.remove());
64
+ doc.querySelectorAll('meta[name="theme-color"]').forEach(
65
+ (node) => restoreSnap(node, [
66
+ ["content", CONTENT],
67
+ ["media", MEDIA]
68
+ ])
69
+ );
70
+ doc.querySelectorAll('link[rel="icon"]').forEach(
71
+ (node) => restoreSnap(node, [
72
+ ["href", HREF],
73
+ ["media", MEDIA]
74
+ ])
75
+ );
76
+ return;
77
+ }
78
+ const color = chromeColor(theme);
79
+ const href = iconUrl(theme, icons);
80
+ const metas = doc.querySelectorAll('meta[name="theme-color"]');
81
+ if (metas.length === 0) {
82
+ const meta = doc.createElement("meta");
83
+ meta.setAttribute("name", "theme-color");
84
+ meta.setAttribute("content", color);
85
+ meta.setAttribute(CREATED, "1");
86
+ doc.head.append(meta);
87
+ } else {
88
+ metas.forEach((meta) => {
89
+ snapshot(meta, "content", CONTENT);
90
+ snapshot(meta, "media", MEDIA);
91
+ meta.setAttribute(SNAP, "1");
92
+ meta.setAttribute("content", color);
93
+ meta.removeAttribute("media");
94
+ });
95
+ }
96
+ doc.querySelectorAll('link[rel="icon"]').forEach((link, index) => {
97
+ snapshot(link, "href", HREF);
98
+ snapshot(link, "media", MEDIA);
99
+ link.setAttribute(SNAP, "1");
100
+ if (index === 0) {
101
+ link.setAttribute("href", href);
102
+ link.removeAttribute("media");
103
+ return;
104
+ }
105
+ link.setAttribute("media", "not all");
106
+ });
107
+ }
108
+
109
+ // src/components/ThemeChrome.tsx
110
+ function explicitTheme(theme) {
111
+ if (theme === "light" || theme === "dark") return theme;
112
+ return void 0;
113
+ }
114
+ function ThemeChrome({ lightIcon, darkIcon }) {
115
+ const { theme } = nextThemes.useTheme();
116
+ react.useEffect(() => {
117
+ applyThemeChrome(document, explicitTheme(theme), {
118
+ light: lightIcon,
119
+ dark: darkIcon
120
+ });
121
+ }, [theme, lightIcon, darkIcon]);
122
+ return null;
123
+ }
124
+
125
+ exports.ThemeChrome = ThemeChrome;
126
+ //# sourceMappingURL=theme-chrome.cjs.map
127
+ //# sourceMappingURL=theme-chrome.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/brand-chrome.ts","../src/components/ThemeChrome.tsx"],"names":["useTheme","useEffect"],"mappings":"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","file":"theme-chrome.cjs","sourcesContent":["/** Page background behind the browser chrome. Light and dark match `--background`. */\nexport const BRAND_CHROME_LIGHT = \"#ffffff\";\nexport const BRAND_CHROME_DARK = \"#171717\";\n\nexport const brandViewport = {\n\twidth: \"device-width\",\n\tinitialScale: 1,\n\tviewportFit: \"cover\" as const,\n\tcolorScheme: \"light dark\" as const,\n\tthemeColor: [\n\t\t{ media: \"(prefers-color-scheme: light)\", color: BRAND_CHROME_LIGHT },\n\t\t{ media: \"(prefers-color-scheme: dark)\", color: BRAND_CHROME_DARK },\n\t],\n};\n\nexport type BrandIconInput = {\n\tlight: string;\n\tdark: string;\n\tapple: string;\n\tfallback?: string;\n\tsvg?: string;\n};\n\ntype BrandIcon = { url: string; media?: string; type?: string };\n\n/** OS-sensitive favicon set. Apps pass their own public paths. */\nexport function brandIcons(input: BrandIconInput): {\n\ticon: BrandIcon[];\n\tapple: string;\n} {\n\tconst icon: BrandIcon[] = [\n\t\t{ url: input.light, media: \"(prefers-color-scheme: light)\" },\n\t\t{ url: input.dark, media: \"(prefers-color-scheme: dark)\" },\n\t];\n\tif (input.svg) {\n\t\ticon.push({ url: input.svg, type: \"image/svg+xml\" });\n\t}\n\tif (input.fallback) {\n\t\ticon.push({ url: input.fallback });\n\t}\n\treturn { icon, apple: input.apple };\n}\n\n/** `default` lets iOS paint the status bar from `theme-color`. */\nexport function brandAppleWebApp(title: string): {\n\tcapable: true;\n\tstatusBarStyle: \"default\";\n\ttitle: string;\n} {\n\treturn {\n\t\tcapable: true,\n\t\tstatusBarStyle: \"default\",\n\t\ttitle,\n\t};\n}\n\nexport type ExplicitChromeTheme = \"light\" | \"dark\";\n\nconst SNAP = \"data-sesionario-chrome-snap\";\nconst MEDIA = \"data-sesionario-chrome-media\";\nconst HREF = \"data-sesionario-chrome-href\";\nconst CONTENT = \"data-sesionario-chrome-content\";\nconst CREATED = \"data-sesionario-chrome-created\";\n\nfunction chromeColor(theme: ExplicitChromeTheme): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn BRAND_CHROME_LIGHT;\n\t\tcase \"dark\":\n\t\t\treturn BRAND_CHROME_DARK;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction iconUrl(\n\ttheme: ExplicitChromeTheme,\n\ticons: { light: string; dark: string },\n): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn icons.light;\n\t\tcase \"dark\":\n\t\t\treturn icons.dark;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction snapshot(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(SNAP)) return;\n\tif (el.hasAttribute(attr)) {\n\t\tel.setAttribute(store, el.getAttribute(attr) ?? \"\");\n\t}\n}\n\nfunction restoreAttr(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(store)) {\n\t\tel.setAttribute(attr, el.getAttribute(store) ?? \"\");\n\t\tel.removeAttribute(store);\n\t\treturn;\n\t}\n\tel.removeAttribute(attr);\n}\n\nfunction restoreSnap(el: Element, pairs: Array<[string, string]>) {\n\tif (!el.hasAttribute(SNAP)) return;\n\tfor (const [attr, store] of pairs) {\n\t\trestoreAttr(el, attr, store);\n\t}\n\tel.removeAttribute(SNAP);\n}\n\n/**\n * When the in-app theme is explicit, pin `theme-color` and the tab icon to\n * that choice. `system` and an unresolved theme put the OS media queries back.\n */\nexport function applyThemeChrome(\n\tdoc: Document,\n\ttheme: ExplicitChromeTheme | undefined,\n\ticons: { light: string; dark: string },\n): void {\n\tconst created = doc.querySelectorAll(`meta[${CREATED}]`);\n\tif (theme === undefined) {\n\t\tcreated.forEach((node) => node.remove());\n\t\tdoc.querySelectorAll('meta[name=\"theme-color\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"content\", CONTENT],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"href\", HREF],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\treturn;\n\t}\n\n\tconst color = chromeColor(theme);\n\tconst href = iconUrl(theme, icons);\n\tconst metas = doc.querySelectorAll('meta[name=\"theme-color\"]');\n\tif (metas.length === 0) {\n\t\tconst meta = doc.createElement(\"meta\");\n\t\tmeta.setAttribute(\"name\", \"theme-color\");\n\t\tmeta.setAttribute(\"content\", color);\n\t\tmeta.setAttribute(CREATED, \"1\");\n\t\tdoc.head.append(meta);\n\t} else {\n\t\tmetas.forEach((meta) => {\n\t\t\tsnapshot(meta, \"content\", CONTENT);\n\t\t\tsnapshot(meta, \"media\", MEDIA);\n\t\t\tmeta.setAttribute(SNAP, \"1\");\n\t\t\tmeta.setAttribute(\"content\", color);\n\t\t\tmeta.removeAttribute(\"media\");\n\t\t});\n\t}\n\n\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((link, index) => {\n\t\tsnapshot(link, \"href\", HREF);\n\t\tsnapshot(link, \"media\", MEDIA);\n\t\tlink.setAttribute(SNAP, \"1\");\n\t\tif (index === 0) {\n\t\t\tlink.setAttribute(\"href\", href);\n\t\t\tlink.removeAttribute(\"media\");\n\t\t\treturn;\n\t\t}\n\t\tlink.setAttribute(\"media\", \"not all\");\n\t});\n}\n","\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { useEffect } from \"react\";\nimport {\n\tapplyThemeChrome,\n\ttype ExplicitChromeTheme,\n} from \"../lib/brand-chrome\";\n\nexport type ThemeChromeProps = {\n\tlightIcon: string;\n\tdarkIcon: string;\n};\n\nfunction explicitTheme(\n\ttheme: string | undefined,\n): ExplicitChromeTheme | undefined {\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/** Pins browser chrome to an explicit light/dark choice. Renders nothing. */\nexport function ThemeChrome({ lightIcon, darkIcon }: ThemeChromeProps) {\n\tconst { theme } = useTheme();\n\n\tuseEffect(() => {\n\t\tapplyThemeChrome(document, explicitTheme(theme), {\n\t\t\tlight: lightIcon,\n\t\t\tdark: darkIcon,\n\t\t});\n\t}, [theme, lightIcon, darkIcon]);\n\n\treturn null;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ type ThemeChromeProps = {
2
+ lightIcon: string;
3
+ darkIcon: string;
4
+ };
5
+ /** Pins browser chrome to an explicit light/dark choice. Renders nothing. */
6
+ declare function ThemeChrome({ lightIcon, darkIcon }: ThemeChromeProps): null;
7
+
8
+ export { ThemeChrome, type ThemeChromeProps };