@rendra-ui/web 2.1.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.
- package/LICENSE +21 -0
- package/README.md +433 -0
- package/bin/rendra.mjs +114 -0
- package/dist/base.css +246 -0
- package/dist/button-DIcpJwK9.js +143 -0
- package/dist/button-DIcpJwK9.js.map +1 -0
- package/dist/chart.js +520 -0
- package/dist/chart.js.map +1 -0
- package/dist/cli/auditar.js +125 -0
- package/dist/cli/auditar.js.map +1 -0
- package/dist/cli/codigos.js +15 -0
- package/dist/cli/codigos.js.map +1 -0
- package/dist/cli/help.js +17 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/trocar.js +242 -0
- package/dist/cli/trocar.js.map +1 -0
- package/dist/cli/typescript-loader.js +27 -0
- package/dist/cli/typescript-loader.js.map +1 -0
- package/dist/cn-DVPheE99.js +22 -0
- package/dist/cn-DVPheE99.js.map +1 -0
- package/dist/components-Cm2ezlPT.js +800 -0
- package/dist/components-Cm2ezlPT.js.map +1 -0
- package/dist/components.css +3365 -0
- package/dist/document-viewer.js +204 -0
- package/dist/document-viewer.js.map +1 -0
- package/dist/empty-state-DtyZAfYb.js +168 -0
- package/dist/empty-state-DtyZAfYb.js.map +1 -0
- package/dist/index.js +10530 -0
- package/dist/index.js.map +1 -0
- package/dist/rendra-provider-cCnquOZy.js +88 -0
- package/dist/rendra-provider-cCnquOZy.js.map +1 -0
- package/dist/rich-text-editor.js +439 -0
- package/dist/rich-text-editor.js.map +1 -0
- package/dist/router-bridge.js +51 -0
- package/dist/router-bridge.js.map +1 -0
- package/dist/tokens.css +732 -0
- package/dist/tooltip-DyeuGPCc.js +105 -0
- package/dist/tooltip-DyeuGPCc.js.map +1 -0
- package/dist/types/brand/apply-theme.d.ts +9 -0
- package/dist/types/brand/brand-context.d.ts +21 -0
- package/dist/types/brand/brand-provider.d.ts +49 -0
- package/dist/types/brand/palette.d.ts +63 -0
- package/dist/types/brand/public.d.ts +6 -0
- package/dist/types/brand/theme.d.ts +80 -0
- package/dist/types/brand/types.d.ts +54 -0
- package/dist/types/brand/use-brand.d.ts +1 -0
- package/dist/types/catalog/components.d.ts +81 -0
- package/dist/types/chart.d.ts +1 -0
- package/dist/types/components/app-shell/app-shell.d.ts +36 -0
- package/dist/types/components/app-shell/command-search.d.ts +1 -0
- package/dist/types/components/app-shell/header.d.ts +1 -0
- package/dist/types/components/app-shell/layout.d.ts +53 -0
- package/dist/types/components/app-shell/mega-menu.d.ts +1 -0
- package/dist/types/components/app-shell/navigation-utils.d.ts +18 -0
- package/dist/types/components/app-shell/notifications.d.ts +6 -0
- package/dist/types/components/app-shell/shell-context.d.ts +48 -0
- package/dist/types/components/app-shell/sidebar.d.ts +1 -0
- package/dist/types/components/app-shell/types.d.ts +53 -0
- package/dist/types/components/layout/index.d.ts +4 -0
- package/dist/types/components/layout/page-header.d.ts +38 -0
- package/dist/types/components/layout/primitives.d.ts +71 -0
- package/dist/types/components/layout/tokens.d.ts +43 -0
- package/dist/types/components/rendra-provider.d.ts +48 -0
- package/dist/types/components/rendra-router-bridge.d.ts +18 -0
- package/dist/types/components/ui/accordion.d.ts +16 -0
- package/dist/types/components/ui/action-bar.d.ts +35 -0
- package/dist/types/components/ui/alert.d.ts +26 -0
- package/dist/types/components/ui/avatar.d.ts +23 -0
- package/dist/types/components/ui/badge.d.ts +15 -0
- package/dist/types/components/ui/brand-feedback-icon.d.ts +26 -0
- package/dist/types/components/ui/brand-logo.d.ts +8 -0
- package/dist/types/components/ui/breadcrumb.d.ts +19 -0
- package/dist/types/components/ui/button-group.d.ts +27 -0
- package/dist/types/components/ui/button.d.ts +30 -0
- package/dist/types/components/ui/calendar.d.ts +34 -0
- package/dist/types/components/ui/card.d.ts +32 -0
- package/dist/types/components/ui/chart.d.ts +57 -0
- package/dist/types/components/ui/chat.d.ts +137 -0
- package/dist/types/components/ui/checkbox.d.ts +37 -0
- package/dist/types/components/ui/checklist.d.ts +16 -0
- package/dist/types/components/ui/color-picker.d.ts +13 -0
- package/dist/types/components/ui/data-toolbar.d.ts +37 -0
- package/dist/types/components/ui/date-picker.d.ts +41 -0
- package/dist/types/components/ui/document-viewer.d.ts +19 -0
- package/dist/types/components/ui/drawer.d.ts +30 -0
- package/dist/types/components/ui/dropdown-menu.d.ts +20 -0
- package/dist/types/components/ui/empty-state.d.ts +15 -0
- package/dist/types/components/ui/error-page.d.ts +13 -0
- package/dist/types/components/ui/field.d.ts +42 -0
- package/dist/types/components/ui/form.d.ts +42 -0
- package/dist/types/components/ui/image-cropper.d.ts +55 -0
- package/dist/types/components/ui/image-viewer.d.ts +14 -0
- package/dist/types/components/ui/info-hint.d.ts +9 -0
- package/dist/types/components/ui/input.d.ts +91 -0
- package/dist/types/components/ui/kanban.d.ts +98 -0
- package/dist/types/components/ui/list.d.ts +31 -0
- package/dist/types/components/ui/modal.d.ts +27 -0
- package/dist/types/components/ui/otp-input.d.ts +17 -0
- package/dist/types/components/ui/overlay-shell.d.ts +14 -0
- package/dist/types/components/ui/pagination.d.ts +18 -0
- package/dist/types/components/ui/picker-panel.d.ts +24 -0
- package/dist/types/components/ui/popover.d.ts +12 -0
- package/dist/types/components/ui/progress.d.ts +16 -0
- package/dist/types/components/ui/qr-code.d.ts +13 -0
- package/dist/types/components/ui/radio-group.d.ts +27 -0
- package/dist/types/components/ui/rating.d.ts +19 -0
- package/dist/types/components/ui/rendra-credit.d.ts +12 -0
- package/dist/types/components/ui/repeatable-field.d.ts +22 -0
- package/dist/types/components/ui/rich-text-editor.d.ts +21 -0
- package/dist/types/components/ui/select.d.ts +65 -0
- package/dist/types/components/ui/separator.d.ts +7 -0
- package/dist/types/components/ui/skeleton.d.ts +3 -0
- package/dist/types/components/ui/slider.d.ts +18 -0
- package/dist/types/components/ui/sortable-handle.d.ts +16 -0
- package/dist/types/components/ui/spinner.d.ts +7 -0
- package/dist/types/components/ui/stat-card.d.ts +20 -0
- package/dist/types/components/ui/switch.d.ts +15 -0
- package/dist/types/components/ui/table.d.ts +134 -0
- package/dist/types/components/ui/tabs.d.ts +25 -0
- package/dist/types/components/ui/textarea.d.ts +10 -0
- package/dist/types/components/ui/timeline.d.ts +18 -0
- package/dist/types/components/ui/toast.d.ts +18 -0
- package/dist/types/components/ui/tooltip.d.ts +17 -0
- package/dist/types/components/ui/upload.d.ts +50 -0
- package/dist/types/components/ui/widget-grid.d.ts +22 -0
- package/dist/types/components/ui/wizard.d.ts +38 -0
- package/dist/types/document-viewer.d.ts +1 -0
- package/dist/types/hooks/use-breakpoint.d.ts +21 -0
- package/dist/types/hooks/use-debounced-value.d.ts +5 -0
- package/dist/types/hooks/use-fill-height.d.ts +6 -0
- package/dist/types/hooks/use-lookup.d.ts +30 -0
- package/dist/types/hooks/use-reduced-motion.d.ts +1 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/lib/cn.d.ts +2 -0
- package/dist/types/lib/contrast.d.ts +2 -0
- package/dist/types/lib/control.d.ts +20 -0
- package/dist/types/lib/lookup.d.ts +22 -0
- package/dist/types/lib/masks.d.ts +46 -0
- package/dist/types/lib/qr-encode.d.ts +48 -0
- package/dist/types/lib/shape.d.ts +8 -0
- package/dist/types/lib/sortable.d.ts +72 -0
- package/dist/types/lib/validators.d.ts +14 -0
- package/dist/types/rendra-router-bridge.d.ts +1 -0
- package/dist/types/rich-text-editor.d.ts +1 -0
- package/dist/types/widget-grid.d.ts +1 -0
- package/dist/use-breakpoint-BmXOZtF9.js +49 -0
- package/dist/use-breakpoint-BmXOZtF9.js.map +1 -0
- package/dist/widget-grid.js +118 -0
- package/dist/widget-grid.js.map +1 -0
- package/package.json +182 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
|
|
2
|
+
import { t as cn } from "./cn-DVPheE99.js";
|
|
3
|
+
import { t as Button } from "./button-DIcpJwK9.js";
|
|
4
|
+
import { t as EmptyState } from "./empty-state-DtyZAfYb.js";
|
|
5
|
+
import { ChevronLeft, ChevronRight, ExternalLink, ZoomIn, ZoomOut } from "lucide-react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
|
+
//#region src/components/ui/document-viewer.tsx
|
|
9
|
+
var MIN_SCALE = .25;
|
|
10
|
+
var MAX_SCALE = 3;
|
|
11
|
+
var ZOOM_STEP = 1.25;
|
|
12
|
+
/** Um visualizador de documento por tela; nunca crie um segundo para PDF. */
|
|
13
|
+
function DocumentViewer({ url, title, className, emptyTitle = "Nenhum documento selecionado", emptyDescription = "Escolha um arquivo PDF para visualizar aqui.", loadingLabel = "Carregando documento...", errorTitle = "Não foi possível abrir o documento", errorDescription = "O arquivo pode estar indisponível, corrompido ou bloqueado por CORS.", openInNewTabLabel = "Abrir em nova aba", zoomInLabel = "Aumentar zoom", zoomOutLabel = "Diminuir zoom", previousPageLabel = "Página anterior", nextPageLabel = "Próxima página" }) {
|
|
14
|
+
const [status, setStatus] = useState(url ? "loading" : "empty");
|
|
15
|
+
const [page, setPage] = useState(1);
|
|
16
|
+
const [numPages, setNumPages] = useState(0);
|
|
17
|
+
const [scale, setScale] = useState(1);
|
|
18
|
+
const [fitToWidth, setFitToWidth] = useState(true);
|
|
19
|
+
const pdfRef = useRef(null);
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
const canvasRef = useRef(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
let loadingTask = null;
|
|
25
|
+
pdfRef.current = null;
|
|
26
|
+
setPage(1);
|
|
27
|
+
setNumPages(0);
|
|
28
|
+
setFitToWidth(true);
|
|
29
|
+
setScale(1);
|
|
30
|
+
if (!url) {
|
|
31
|
+
setStatus("empty");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const targetUrl = url;
|
|
35
|
+
setStatus("loading");
|
|
36
|
+
async function load() {
|
|
37
|
+
try {
|
|
38
|
+
const [pdfjsLib, workerModule] = await Promise.all([import("pdfjs-dist"), import("pdfjs-dist/build/pdf.worker.min.mjs?url")]);
|
|
39
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = workerModule.default;
|
|
40
|
+
if (cancelled) return;
|
|
41
|
+
loadingTask = pdfjsLib.getDocument({ url: targetUrl });
|
|
42
|
+
const doc = await loadingTask.promise;
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
pdfRef.current = doc;
|
|
45
|
+
setNumPages(doc.numPages);
|
|
46
|
+
setStatus("ready");
|
|
47
|
+
} catch {
|
|
48
|
+
if (cancelled) return;
|
|
49
|
+
setStatus("error");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
load();
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
loadingTask?.destroy();
|
|
56
|
+
};
|
|
57
|
+
}, [url]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (status !== "ready" || !pdfRef.current) return;
|
|
60
|
+
let cancelled = false;
|
|
61
|
+
async function draw() {
|
|
62
|
+
const doc = pdfRef.current;
|
|
63
|
+
if (!doc) return;
|
|
64
|
+
const pdfPage = await doc.getPage(page);
|
|
65
|
+
if (cancelled) return;
|
|
66
|
+
const natural = pdfPage.getViewport({ scale: 1 });
|
|
67
|
+
const containerWidth = containerRef.current?.clientWidth ?? 0;
|
|
68
|
+
const effectiveScale = fitToWidth && containerWidth > 0 ? containerWidth / natural.width : scale;
|
|
69
|
+
const viewport = pdfPage.getViewport({ scale: effectiveScale });
|
|
70
|
+
const canvas = canvasRef.current;
|
|
71
|
+
if (!canvas) return;
|
|
72
|
+
canvas.width = viewport.width;
|
|
73
|
+
canvas.height = viewport.height;
|
|
74
|
+
const ctx = canvas.getContext("2d");
|
|
75
|
+
if (!ctx) return;
|
|
76
|
+
await pdfPage.render({
|
|
77
|
+
canvas,
|
|
78
|
+
canvasContext: ctx,
|
|
79
|
+
viewport
|
|
80
|
+
}).promise;
|
|
81
|
+
}
|
|
82
|
+
draw();
|
|
83
|
+
return () => {
|
|
84
|
+
cancelled = true;
|
|
85
|
+
};
|
|
86
|
+
}, [
|
|
87
|
+
status,
|
|
88
|
+
page,
|
|
89
|
+
scale,
|
|
90
|
+
fitToWidth
|
|
91
|
+
]);
|
|
92
|
+
const zoomIn = () => {
|
|
93
|
+
setFitToWidth(false);
|
|
94
|
+
setScale((s) => Math.min(MAX_SCALE, s * ZOOM_STEP));
|
|
95
|
+
};
|
|
96
|
+
const zoomOut = () => {
|
|
97
|
+
setFitToWidth(false);
|
|
98
|
+
setScale((s) => Math.max(MIN_SCALE, s / ZOOM_STEP));
|
|
99
|
+
};
|
|
100
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
101
|
+
"data-rendra": "DOC-001",
|
|
102
|
+
role: "group",
|
|
103
|
+
"aria-label": title,
|
|
104
|
+
className: cn("flex h-viewer min-h-0 flex-col overflow-hidden rounded-surface border bg-card", className),
|
|
105
|
+
children: [
|
|
106
|
+
status === "empty" && /* @__PURE__ */ jsx(EmptyState, {
|
|
107
|
+
title: emptyTitle,
|
|
108
|
+
description: emptyDescription,
|
|
109
|
+
className: "m-auto"
|
|
110
|
+
}),
|
|
111
|
+
status === "loading" && /* @__PURE__ */ jsx("div", {
|
|
112
|
+
className: "m-auto flex flex-col items-center gap-2 px-4 py-8 text-sm text-muted-foreground",
|
|
113
|
+
role: "status",
|
|
114
|
+
children: loadingLabel
|
|
115
|
+
}),
|
|
116
|
+
status === "error" && /* @__PURE__ */ jsx(EmptyState, {
|
|
117
|
+
type: "error",
|
|
118
|
+
title: errorTitle,
|
|
119
|
+
description: errorDescription,
|
|
120
|
+
className: "m-auto",
|
|
121
|
+
actions: url && /* @__PURE__ */ jsx(Button, {
|
|
122
|
+
variant: "outline",
|
|
123
|
+
asChild: true,
|
|
124
|
+
children: /* @__PURE__ */ jsxs("a", {
|
|
125
|
+
href: url,
|
|
126
|
+
target: "_blank",
|
|
127
|
+
rel: "noreferrer",
|
|
128
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true }), openInNewTabLabel]
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
}),
|
|
132
|
+
status === "ready" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
133
|
+
className: "flex shrink-0 items-center justify-between gap-3 border-b bg-card px-3 py-2",
|
|
134
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
135
|
+
className: "flex items-center gap-1",
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx(Button, {
|
|
138
|
+
variant: "ghost",
|
|
139
|
+
size: "sm",
|
|
140
|
+
iconOnly: true,
|
|
141
|
+
"aria-label": zoomOutLabel,
|
|
142
|
+
icon: /* @__PURE__ */ jsx(ZoomOut, {}),
|
|
143
|
+
onClick: zoomOut
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ jsx("span", {
|
|
146
|
+
className: "w-12 text-center text-xs text-muted-foreground tabular-nums",
|
|
147
|
+
children: fitToWidth ? "Ajustar" : `${Math.round(scale * 100)}%`
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ jsx(Button, {
|
|
150
|
+
variant: "ghost",
|
|
151
|
+
size: "sm",
|
|
152
|
+
iconOnly: true,
|
|
153
|
+
"aria-label": zoomInLabel,
|
|
154
|
+
icon: /* @__PURE__ */ jsx(ZoomIn, {}),
|
|
155
|
+
onClick: zoomIn
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
}), numPages > 1 && /* @__PURE__ */ jsxs("div", {
|
|
159
|
+
className: "flex items-center gap-2",
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx(Button, {
|
|
162
|
+
variant: "ghost",
|
|
163
|
+
size: "sm",
|
|
164
|
+
iconOnly: true,
|
|
165
|
+
"aria-label": previousPageLabel,
|
|
166
|
+
icon: /* @__PURE__ */ jsx(ChevronLeft, {}),
|
|
167
|
+
disabled: page <= 1,
|
|
168
|
+
onClick: () => setPage((p) => Math.max(1, p - 1))
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ jsxs("span", {
|
|
171
|
+
className: "text-xs text-muted-foreground tabular-nums",
|
|
172
|
+
"aria-live": "polite",
|
|
173
|
+
children: [
|
|
174
|
+
page,
|
|
175
|
+
" de ",
|
|
176
|
+
numPages
|
|
177
|
+
]
|
|
178
|
+
}),
|
|
179
|
+
/* @__PURE__ */ jsx(Button, {
|
|
180
|
+
variant: "ghost",
|
|
181
|
+
size: "sm",
|
|
182
|
+
iconOnly: true,
|
|
183
|
+
"aria-label": nextPageLabel,
|
|
184
|
+
icon: /* @__PURE__ */ jsx(ChevronRight, {}),
|
|
185
|
+
disabled: page >= numPages,
|
|
186
|
+
onClick: () => setPage((p) => Math.min(numPages, p + 1))
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
})]
|
|
190
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
191
|
+
ref: containerRef,
|
|
192
|
+
className: "flex min-h-0 flex-1 items-center justify-center overflow-auto bg-muted p-4",
|
|
193
|
+
children: /* @__PURE__ */ jsx("canvas", {
|
|
194
|
+
ref: canvasRef,
|
|
195
|
+
className: "max-w-full rounded-item bg-card shadow-sm"
|
|
196
|
+
})
|
|
197
|
+
})] })
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { DocumentViewer };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=document-viewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-viewer.js","names":[],"sources":["../src/components/ui/document-viewer.tsx"],"sourcesContent":["import { ChevronLeft, ChevronRight, ExternalLink, ZoomIn, ZoomOut } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport { Button } from '@/components/ui/button'\nimport { EmptyState } from '@/components/ui/empty-state'\nimport { cn } from '@/lib/cn'\n\n/*\n * Visualizador de documentos PDF. Carrega o pdfjs-dist sob demanda (import dinâmico), com o\n * worker também sob demanda, para o pacote principal nunca pagar o custo de quem não usa o\n * visualizador. url null é o estado vazio legítimo (nenhum documento escolhido ainda). Falha\n * (CORS, arquivo inválido) é declarada, nunca uma tela em branco: sempre com o link \"Abrir em\n * nova aba\".\n */\n\n// pdfjs-dist é grande e só entra no bundle de quem realmente usa o DocumentViewer: os tipos\n// abaixo são só de compilação (import de tipo), o import de valor é sempre dinâmico (import()).\ntype PdfDocumentProxy = import('pdfjs-dist').PDFDocumentProxy\n// destroy() vive na tarefa de carregamento (o que getDocument devolve antes do .promise),\n// não no documento já resolvido: é ela que precisa ser guardada para liberar depois.\ntype PdfLoadingTask = import('pdfjs-dist').PDFDocumentLoadingTask\n\ntype Status = 'empty' | 'loading' | 'ready' | 'error'\n\nexport interface DocumentViewerProps {\n /** Endereço do PDF. null é o estado vazio legítimo (nenhum documento escolhido ainda). */\n url: string | null\n /** Nome acessível do documento (aria-label do visualizador). */\n title: string\n className?: string\n emptyTitle?: string\n emptyDescription?: string\n loadingLabel?: string\n errorTitle?: string\n errorDescription?: string\n openInNewTabLabel?: string\n zoomInLabel?: string\n zoomOutLabel?: string\n previousPageLabel?: string\n nextPageLabel?: string\n}\n\nconst MIN_SCALE = 0.25\nconst MAX_SCALE = 3\nconst ZOOM_STEP = 1.25\n\n/** Um visualizador de documento por tela; nunca crie um segundo para PDF. */\nexport function DocumentViewer({\n url,\n title,\n className,\n emptyTitle = 'Nenhum documento selecionado',\n emptyDescription = 'Escolha um arquivo PDF para visualizar aqui.',\n loadingLabel = 'Carregando documento...',\n errorTitle = 'Não foi possível abrir o documento',\n errorDescription = 'O arquivo pode estar indisponível, corrompido ou bloqueado por CORS.',\n openInNewTabLabel = 'Abrir em nova aba',\n zoomInLabel = 'Aumentar zoom',\n zoomOutLabel = 'Diminuir zoom',\n previousPageLabel = 'Página anterior',\n nextPageLabel = 'Próxima página',\n}: DocumentViewerProps) {\n const [status, setStatus] = useState<Status>(url ? 'loading' : 'empty')\n const [page, setPage] = useState(1)\n const [numPages, setNumPages] = useState(0)\n const [scale, setScale] = useState(1)\n const [fitToWidth, setFitToWidth] = useState(true)\n const pdfRef = useRef<PdfDocumentProxy | null>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const canvasRef = useRef<HTMLCanvasElement>(null)\n\n // Carrega o documento (ou reinicia o estado vazio) sempre que a url muda. Libera a tarefa\n // de carregamento (destroy) ao trocar de url e ao desmontar, para não vazar memória nem o\n // worker que o pdfjs sobe para cada documento.\n useEffect(() => {\n let cancelled = false\n let loadingTask: PdfLoadingTask | null = null\n pdfRef.current = null\n setPage(1)\n setNumPages(0)\n setFitToWidth(true)\n setScale(1)\n\n if (!url) {\n setStatus('empty')\n return\n }\n const targetUrl: string = url\n\n setStatus('loading')\n async function load() {\n try {\n const [pdfjsLib, workerModule] = await Promise.all([\n import('pdfjs-dist'),\n import('pdfjs-dist/build/pdf.worker.min.mjs?url'),\n ])\n pdfjsLib.GlobalWorkerOptions.workerSrc = workerModule.default\n if (cancelled) return\n loadingTask = pdfjsLib.getDocument({ url: targetUrl })\n const doc = await loadingTask.promise\n if (cancelled) return\n pdfRef.current = doc\n setNumPages(doc.numPages)\n setStatus('ready')\n } catch {\n if (cancelled) return\n setStatus('error')\n }\n }\n void load()\n return () => {\n cancelled = true\n void loadingTask?.destroy()\n }\n }, [url])\n\n // Desenha a página atual sempre que ela, o zoom ou o modo \"ajustar à largura\" mudam.\n useEffect(() => {\n if (status !== 'ready' || !pdfRef.current) return\n let cancelled = false\n async function draw() {\n const doc = pdfRef.current\n if (!doc) return\n const pdfPage = await doc.getPage(page)\n if (cancelled) return\n const natural = pdfPage.getViewport({ scale: 1 })\n const containerWidth = containerRef.current?.clientWidth ?? 0\n const effectiveScale =\n fitToWidth && containerWidth > 0 ? containerWidth / natural.width : scale\n const viewport = pdfPage.getViewport({ scale: effectiveScale })\n const canvas = canvasRef.current\n if (!canvas) return\n canvas.width = viewport.width\n canvas.height = viewport.height\n const ctx = canvas.getContext('2d')\n // Sem contexto 2D disponível (ambiente sem canvas real): mantém os controles\n // funcionando, só não desenha o pixel a pixel.\n if (!ctx) return\n await pdfPage.render({ canvas, canvasContext: ctx, viewport }).promise\n }\n void draw()\n return () => {\n cancelled = true\n }\n }, [status, page, scale, fitToWidth])\n\n const zoomIn = () => {\n setFitToWidth(false)\n setScale((s) => Math.min(MAX_SCALE, s * ZOOM_STEP))\n }\n const zoomOut = () => {\n setFitToWidth(false)\n setScale((s) => Math.max(MIN_SCALE, s / ZOOM_STEP))\n }\n\n return (\n <div\n data-rendra=\"DOC-001\"\n role=\"group\"\n aria-label={title}\n className={cn(\n 'flex h-viewer min-h-0 flex-col overflow-hidden rounded-surface border bg-card',\n className,\n )}\n >\n {status === 'empty' && (\n <EmptyState title={emptyTitle} description={emptyDescription} className=\"m-auto\" />\n )}\n\n {status === 'loading' && (\n <div\n className=\"m-auto flex flex-col items-center gap-2 px-4 py-8 text-sm text-muted-foreground\"\n role=\"status\"\n >\n {loadingLabel}\n </div>\n )}\n\n {status === 'error' && (\n <EmptyState\n type=\"error\"\n title={errorTitle}\n description={errorDescription}\n className=\"m-auto\"\n actions={\n url && (\n <Button variant=\"outline\" asChild>\n <a href={url} target=\"_blank\" rel=\"noreferrer\">\n <ExternalLink aria-hidden />\n {openInNewTabLabel}\n </a>\n </Button>\n )\n }\n />\n )}\n\n {status === 'ready' && (\n <>\n <div className=\"flex shrink-0 items-center justify-between gap-3 border-b bg-card px-3 py-2\">\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n aria-label={zoomOutLabel}\n icon={<ZoomOut />}\n onClick={zoomOut}\n />\n <span className=\"w-12 text-center text-xs text-muted-foreground tabular-nums\">\n {fitToWidth ? 'Ajustar' : `${Math.round(scale * 100)}%`}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n aria-label={zoomInLabel}\n icon={<ZoomIn />}\n onClick={zoomIn}\n />\n </div>\n {numPages > 1 && (\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n aria-label={previousPageLabel}\n icon={<ChevronLeft />}\n disabled={page <= 1}\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n />\n <span className=\"text-xs text-muted-foreground tabular-nums\" aria-live=\"polite\">\n {page} de {numPages}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n aria-label={nextPageLabel}\n icon={<ChevronRight />}\n disabled={page >= numPages}\n onClick={() => setPage((p) => Math.min(numPages, p + 1))}\n />\n </div>\n )}\n </div>\n <div\n ref={containerRef}\n className=\"flex min-h-0 flex-1 items-center justify-center overflow-auto bg-muted p-4\"\n >\n <canvas ref={canvasRef} className=\"max-w-full rounded-item bg-card shadow-sm\" />\n </div>\n </>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
|
|
2
|
+
import { t as cn } from "./cn-DVPheE99.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
//#region src/brand/brand-context.ts
|
|
7
|
+
var BrandContext = createContext(null);
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/brand/use-brand.ts
|
|
10
|
+
function useBrand() {
|
|
11
|
+
const ctx = useContext(BrandContext);
|
|
12
|
+
if (!ctx) throw new Error("useBrand precisa estar dentro de <BrandProvider>.");
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/components/ui/brand-feedback-icon.tsx
|
|
17
|
+
var iconVariants = cva("relative inline-flex shrink-0", {
|
|
18
|
+
variants: {
|
|
19
|
+
type: {
|
|
20
|
+
success: "text-success",
|
|
21
|
+
error: "text-destructive",
|
|
22
|
+
warning: "text-warning",
|
|
23
|
+
info: "text-info"
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
sm: "size-icon-sm",
|
|
27
|
+
md: "size-icon-md",
|
|
28
|
+
lg: "size-icon-lg",
|
|
29
|
+
xl: "size-12",
|
|
30
|
+
"2xl": "size-16"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
type: "info",
|
|
35
|
+
size: "md"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
var badgeTone = {
|
|
39
|
+
success: "bg-success text-success-foreground",
|
|
40
|
+
error: "bg-destructive text-destructive-foreground",
|
|
41
|
+
warning: "bg-warning text-warning-foreground",
|
|
42
|
+
info: "bg-info text-info-foreground"
|
|
43
|
+
};
|
|
44
|
+
var feedbackLabels = {
|
|
45
|
+
success: "Sucesso",
|
|
46
|
+
error: "Erro",
|
|
47
|
+
warning: "Atenção",
|
|
48
|
+
info: "Informação"
|
|
49
|
+
};
|
|
50
|
+
/** Glifos universais de status desenhados em traço, herdando currentColor. */
|
|
51
|
+
function StatusGlyph({ type, animated, ...props }) {
|
|
52
|
+
const draw = animated ? {
|
|
53
|
+
pathLength: 1,
|
|
54
|
+
strokeDasharray: 1,
|
|
55
|
+
className: "animate-draw"
|
|
56
|
+
} : {};
|
|
57
|
+
const common = {
|
|
58
|
+
viewBox: "0 0 12 12",
|
|
59
|
+
fill: "none",
|
|
60
|
+
stroke: "currentColor",
|
|
61
|
+
strokeWidth: 2,
|
|
62
|
+
strokeLinecap: "round",
|
|
63
|
+
strokeLinejoin: "round",
|
|
64
|
+
"aria-hidden": true,
|
|
65
|
+
...props
|
|
66
|
+
};
|
|
67
|
+
switch (type) {
|
|
68
|
+
case "success": return /* @__PURE__ */ jsx("svg", {
|
|
69
|
+
...common,
|
|
70
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
71
|
+
d: "M3 6.2 5.1 8.2 9 4",
|
|
72
|
+
...draw
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
case "error": return /* @__PURE__ */ jsx("svg", {
|
|
76
|
+
...common,
|
|
77
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
78
|
+
d: "M4 4l4 4M8 4 4 8",
|
|
79
|
+
...draw
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
case "warning": return /* @__PURE__ */ jsx("svg", {
|
|
83
|
+
...common,
|
|
84
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
85
|
+
d: "M6 3.2v3.3M6 8.8v0",
|
|
86
|
+
...draw
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
case "info": return /* @__PURE__ */ jsx("svg", {
|
|
90
|
+
...common,
|
|
91
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
92
|
+
d: "M6 3.2v0M6 5.5v3.3",
|
|
93
|
+
...draw
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ícone de feedback da marca: o símbolo da marca em currentColor, tingido pela cor
|
|
100
|
+
* semântica, com um selo de status para que o significado não dependa só da cor.
|
|
101
|
+
* Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).
|
|
102
|
+
*/
|
|
103
|
+
function BrandFeedbackIcon({ type, size, label, animated = false, className }) {
|
|
104
|
+
const { brand } = useBrand();
|
|
105
|
+
const Custom = brand.feedbackIcons?.[type];
|
|
106
|
+
const Symbol = brand.symbol;
|
|
107
|
+
const a11y = label ? {
|
|
108
|
+
role: "img",
|
|
109
|
+
"aria-label": label
|
|
110
|
+
} : { "aria-hidden": true };
|
|
111
|
+
return /* @__PURE__ */ jsx("span", {
|
|
112
|
+
"data-slot": "feedback-icon",
|
|
113
|
+
"data-rendra": "BFI-001",
|
|
114
|
+
className: cn(iconVariants({
|
|
115
|
+
type,
|
|
116
|
+
size
|
|
117
|
+
}), animated && type === "error" && "animate-shake", className),
|
|
118
|
+
...a11y,
|
|
119
|
+
children: Custom ? /* @__PURE__ */ jsx(Custom, { className: cn("size-full", animated && "animate-pop") }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Symbol, { className: cn("size-full mask-status-cutout", animated && "animate-pop") }), /* @__PURE__ */ jsx("span", {
|
|
120
|
+
className: cn("absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full", badgeTone[type], animated && "animate-pop anim-delay-1"),
|
|
121
|
+
children: /* @__PURE__ */ jsx(StatusGlyph, {
|
|
122
|
+
type,
|
|
123
|
+
animated,
|
|
124
|
+
className: "size-full"
|
|
125
|
+
})
|
|
126
|
+
})] })
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/components/ui/empty-state.tsx
|
|
131
|
+
/** Estado vazio (e de erro) com o ícone de feedback da marca. */
|
|
132
|
+
function EmptyState({ title, description, type = "info", actions, size = "default", className }) {
|
|
133
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
134
|
+
"data-rendra": "VAZ-001",
|
|
135
|
+
className: cn("flex flex-col items-center justify-center gap-4 text-center", size === "compact" ? "px-4 py-8" : "px-4 py-12 md:py-16", className),
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsxs("span", {
|
|
138
|
+
className: "relative flex items-center justify-center",
|
|
139
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
140
|
+
"aria-hidden": true,
|
|
141
|
+
className: "absolute size-16 rounded-full bg-gradient-soft"
|
|
142
|
+
}), /* @__PURE__ */ jsx(BrandFeedbackIcon, {
|
|
143
|
+
type,
|
|
144
|
+
size: size === "compact" ? "xl" : "2xl",
|
|
145
|
+
className: "relative"
|
|
146
|
+
})]
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: "flex w-full flex-col gap-1",
|
|
150
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
151
|
+
className: "text-base font-semibold",
|
|
152
|
+
children: title
|
|
153
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
154
|
+
className: "text-sm text-muted-foreground",
|
|
155
|
+
children: description
|
|
156
|
+
})]
|
|
157
|
+
}),
|
|
158
|
+
actions && /* @__PURE__ */ jsx("div", {
|
|
159
|
+
className: "flex flex-wrap justify-center gap-3",
|
|
160
|
+
children: actions
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { BrandContext as a, useBrand as i, BrandFeedbackIcon as n, feedbackLabels as r, EmptyState as t };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=empty-state-DtyZAfYb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state-DtyZAfYb.js","names":[],"sources":["../src/brand/brand-context.ts","../src/brand/use-brand.ts","../src/components/ui/brand-feedback-icon.tsx","../src/components/ui/empty-state.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { Shape } from '@/lib/shape'\nimport type { BrandConfig, PaletteConfig } from './types'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nexport interface BrandContextValue {\n brand: BrandConfig\n brands: BrandConfig[]\n setBrandId: (id: string) => void\n mode: ColorMode\n resolvedMode: 'light' | 'dark'\n setMode: (mode: ColorMode) => void\n /** Formato do template ativo (fixo por template). */\n shape: Shape\n /** Paleta de cores em uso (por padrão, a do próprio modelo). */\n palette: PaletteConfig\n palettes: PaletteConfig[]\n /** Usa a paleta de outro template; null volta às cores do próprio modelo. */\n setPaletteId: (id: string | null) => void\n /** Logotipo adequado para a sidebar com a paleta atual. */\n sidebarLogoVariant: 'light' | 'dark'\n}\n\nexport const BrandContext = createContext<BrandContextValue | null>(null)\n","import { useContext } from 'react'\nimport { BrandContext } from './brand-context'\n\nexport function useBrand() {\n const ctx = useContext(BrandContext)\n if (!ctx) throw new Error('useBrand precisa estar dentro de <BrandProvider>.')\n return ctx\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport type { SVGProps } from 'react'\nimport { useBrand } from '@/brand/use-brand'\nimport type { FeedbackType } from '@/brand/types'\nimport { cn } from '@/lib/cn'\n\nconst iconVariants = cva('relative inline-flex shrink-0', {\n variants: {\n type: {\n success: 'text-success',\n error: 'text-destructive',\n warning: 'text-warning',\n info: 'text-info',\n },\n size: {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n xl: 'size-12',\n '2xl': 'size-16',\n },\n },\n defaultVariants: { type: 'info', size: 'md' },\n})\n\nconst badgeTone: Record<FeedbackType, string> = {\n success: 'bg-success text-success-foreground',\n error: 'bg-destructive text-destructive-foreground',\n warning: 'bg-warning text-warning-foreground',\n info: 'bg-info text-info-foreground',\n}\n\nexport const feedbackLabels: Record<FeedbackType, string> = {\n success: 'Sucesso',\n error: 'Erro',\n warning: 'Atenção',\n info: 'Informação',\n}\n\n/** Glifos universais de status desenhados em traço, herdando currentColor. */\nfunction StatusGlyph({\n type,\n animated,\n ...props\n}: { type: FeedbackType; animated?: boolean } & SVGProps<SVGSVGElement>) {\n // pathLength=1 normaliza o traço: o desenho anima de 1 a 0 em qualquer glifo.\n const draw = animated\n ? ({ pathLength: 1, strokeDasharray: 1, className: 'animate-draw' } as const)\n : {}\n const common = {\n viewBox: '0 0 12 12',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n 'aria-hidden': true,\n ...props,\n }\n switch (type) {\n case 'success':\n return (\n <svg {...common}>\n <path d=\"M3 6.2 5.1 8.2 9 4\" {...draw} />\n </svg>\n )\n case 'error':\n return (\n <svg {...common}>\n <path d=\"M4 4l4 4M8 4 4 8\" {...draw} />\n </svg>\n )\n case 'warning':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v3.3M6 8.8v0\" {...draw} />\n </svg>\n )\n case 'info':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v0M6 5.5v3.3\" {...draw} />\n </svg>\n )\n }\n}\n\nexport interface BrandFeedbackIconProps extends Omit<VariantProps<typeof iconVariants>, 'type'> {\n type: FeedbackType\n /** Texto para leitores de tela. Sem ele o ícone é decorativo (o texto ao lado informa). */\n label?: string\n /**\n * Anima a entrada: o símbolo entra com mola e o glifo se desenha (check se formando,\n * X se riscando). O erro treme ao final. Respeita reduzir movimento.\n * Troque a key do componente para repetir a animação.\n */\n animated?: boolean\n className?: string\n}\n\n/**\n * Ícone de feedback da marca: o símbolo da marca em currentColor, tingido pela cor\n * semântica, com um selo de status para que o significado não dependa só da cor.\n * Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).\n */\nexport function BrandFeedbackIcon({\n type,\n size,\n label,\n animated = false,\n className,\n}: BrandFeedbackIconProps) {\n const { brand } = useBrand()\n const Custom = brand.feedbackIcons?.[type]\n const Symbol = brand.symbol\n const a11y = label\n ? ({ role: 'img', 'aria-label': label } as const)\n : ({ 'aria-hidden': true } as const)\n\n return (\n <span\n data-slot=\"feedback-icon\"\n data-rendra=\"BFI-001\"\n className={cn(\n iconVariants({ type, size }),\n animated && type === 'error' && 'animate-shake',\n className,\n )}\n {...a11y}\n >\n {Custom ? (\n <Custom className={cn('size-full', animated && 'animate-pop')} />\n ) : (\n <>\n <Symbol className={cn('size-full mask-status-cutout', animated && 'animate-pop')} />\n <span\n className={cn(\n 'absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full',\n badgeTone[type],\n animated && 'animate-pop anim-delay-1',\n )}\n >\n <StatusGlyph type={type} animated={animated} className=\"size-full\" />\n </span>\n </>\n )}\n </span>\n )\n}\n","import type { ReactNode } from 'react'\nimport type { FeedbackType } from '@/brand/types'\nimport { BrandFeedbackIcon } from '@/components/ui/brand-feedback-icon'\nimport { cn } from '@/lib/cn'\n\nexport interface EmptyStateProps {\n title: string\n description?: ReactNode\n /** Tipo do ícone de feedback da marca. info para \"sem dados\", error para falha. */\n type?: FeedbackType\n /** Ações: no máximo uma principal e uma secundária. */\n actions?: ReactNode\n /** compact: dentro de tabela ou card pequeno. */\n size?: 'default' | 'compact'\n className?: string\n}\n\n/** Estado vazio (e de erro) com o ícone de feedback da marca. */\nexport function EmptyState({\n title,\n description,\n type = 'info',\n actions,\n size = 'default',\n className,\n}: EmptyStateProps) {\n return (\n <div\n data-rendra=\"VAZ-001\"\n className={cn(\n 'flex flex-col items-center justify-center gap-4 text-center',\n size === 'compact' ? 'px-4 py-8' : 'px-4 py-12 md:py-16',\n className,\n )}\n >\n <span className=\"relative flex items-center justify-center\">\n <span aria-hidden className=\"absolute size-16 rounded-full bg-gradient-soft\" />\n <BrandFeedbackIcon\n type={type}\n size={size === 'compact' ? 'xl' : '2xl'}\n className=\"relative\"\n />\n </span>\n <div className=\"flex w-full flex-col gap-1\">\n <p className=\"text-base font-semibold\">{title}</p>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap justify-center gap-3\">{actions}</div>}\n </div>\n )\n}\n"],"mappings":"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"}
|