@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,88 @@
|
|
|
1
|
+
/*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useMemo, useSyncExternalStore } from "react";
|
|
4
|
+
//#region src/components/rendra-provider.tsx
|
|
5
|
+
/** Sem roteador nenhum: o link é só um `<a href>`. */
|
|
6
|
+
function DefaultLink({ to, children, ...rest }) {
|
|
7
|
+
return /* @__PURE__ */ jsx("a", {
|
|
8
|
+
href: to,
|
|
9
|
+
...rest,
|
|
10
|
+
children
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function subscribeToLocation(onChange) {
|
|
14
|
+
window.addEventListener("popstate", onChange);
|
|
15
|
+
return () => window.removeEventListener("popstate", onChange);
|
|
16
|
+
}
|
|
17
|
+
/** Sem roteador nenhum: lê `window.location.pathname` direto. */
|
|
18
|
+
function useDefaultCurrentPath() {
|
|
19
|
+
return useSyncExternalStore(subscribeToLocation, () => window.location.pathname, () => "/");
|
|
20
|
+
}
|
|
21
|
+
/** Sem roteador nenhum: navega com `window.location`. */
|
|
22
|
+
function defaultNavigate(to, options) {
|
|
23
|
+
if (options?.replace) window.location.replace(to);
|
|
24
|
+
else window.location.assign(to);
|
|
25
|
+
}
|
|
26
|
+
/** Sem roteador nenhum: volta pelo histórico do navegador. */
|
|
27
|
+
function defaultGoBack() {
|
|
28
|
+
window.history.back();
|
|
29
|
+
}
|
|
30
|
+
/** Sem roteador nenhum: nenhuma trilha para montar. */
|
|
31
|
+
function defaultUseBreadcrumbs() {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
var RendraContext = createContext({
|
|
35
|
+
linkComponent: DefaultLink,
|
|
36
|
+
useCurrentPath: useDefaultCurrentPath,
|
|
37
|
+
navigate: defaultNavigate,
|
|
38
|
+
goBack: defaultGoBack,
|
|
39
|
+
useBreadcrumbs: defaultUseBreadcrumbs
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Única peça que sabe como navegar (`react-router`, outro roteador, ou nenhum).
|
|
43
|
+
* Sem `RendraProvider` na árvore, os hooks abaixo caem para um comportamento simples,
|
|
44
|
+
* baseado só em `window.location`/`history`, e os componentes continuam funcionando.
|
|
45
|
+
*/
|
|
46
|
+
function RendraProvider({ children, linkComponent, useCurrentPath: useCurrentPathProp, navigate, goBack, useBreadcrumbs }) {
|
|
47
|
+
const value = useMemo(() => ({
|
|
48
|
+
linkComponent,
|
|
49
|
+
useCurrentPath: useCurrentPathProp,
|
|
50
|
+
navigate,
|
|
51
|
+
goBack,
|
|
52
|
+
useBreadcrumbs: useBreadcrumbs ?? defaultUseBreadcrumbs
|
|
53
|
+
}), [
|
|
54
|
+
linkComponent,
|
|
55
|
+
useCurrentPathProp,
|
|
56
|
+
navigate,
|
|
57
|
+
goBack,
|
|
58
|
+
useBreadcrumbs
|
|
59
|
+
]);
|
|
60
|
+
return /* @__PURE__ */ jsx(RendraContext.Provider, {
|
|
61
|
+
value,
|
|
62
|
+
children
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Componente de link do provider ativo (ou `<a href>`, sem provider). */
|
|
66
|
+
function useRendraLink() {
|
|
67
|
+
return useContext(RendraContext).linkComponent;
|
|
68
|
+
}
|
|
69
|
+
/** Caminho atual (ex.: `/clientes/novo`), do provider ativo ou de `window.location`. */
|
|
70
|
+
function useCurrentPath() {
|
|
71
|
+
return useContext(RendraContext).useCurrentPath();
|
|
72
|
+
}
|
|
73
|
+
/** Navegar e voltar, do provider ativo ou de `window.location`/`history`. */
|
|
74
|
+
function useRendraNavigate() {
|
|
75
|
+
const ctx = useContext(RendraContext);
|
|
76
|
+
return {
|
|
77
|
+
navigate: ctx.navigate,
|
|
78
|
+
goBack: ctx.goBack
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Trilha da rota atual, do provider ativo ou vazia, sem roteador. */
|
|
82
|
+
function useRendraBreadcrumbs() {
|
|
83
|
+
return useContext(RendraContext).useBreadcrumbs();
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { useRendraNavigate as a, useRendraLink as i, useCurrentPath as n, useRendraBreadcrumbs as r, RendraProvider as t };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=rendra-provider-cCnquOZy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rendra-provider-cCnquOZy.js","names":[],"sources":["../src/components/rendra-provider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n useSyncExternalStore,\n type AnchorHTMLAttributes,\n type ComponentType,\n type ReactNode,\n} from 'react'\n\n/**\n * Link com a mesma interface do `<a>`/`Link`: recebe `to`, `className`, `children`,\n * `aria-*`, `onClick` e repassa o resto.\n */\nexport interface RendraLinkProps {\n to: string\n className?: string\n children?: ReactNode\n [key: string]: unknown\n}\n\nexport interface RendraBreadcrumbItem {\n label: string\n to?: string\n}\n\nexport interface RendraNavigateOptions {\n replace?: boolean\n}\n\nexport interface RendraProviderProps {\n children: ReactNode\n /** Componente de link do roteador do host (ex.: `Link` do react-router). */\n linkComponent: ComponentType<RendraLinkProps>\n /** Devolve o caminho atual (ex.: `useLocation().pathname`). */\n useCurrentPath: () => string\n /** Navega para um caminho, com `replace` opcional. */\n navigate: (to: string, options?: RendraNavigateOptions) => void\n /** Volta à tela anterior. */\n goBack: () => void\n /** Trilha da rota atual (ex.: via `useMatches` e `handle.crumb`). Opcional. */\n useBreadcrumbs?: () => RendraBreadcrumbItem[]\n}\n\ninterface RendraContextValue {\n linkComponent: ComponentType<RendraLinkProps>\n useCurrentPath: () => string\n navigate: (to: string, options?: RendraNavigateOptions) => void\n goBack: () => void\n useBreadcrumbs: () => RendraBreadcrumbItem[]\n}\n\n/** Sem roteador nenhum: o link é só um `<a href>`. */\nfunction DefaultLink({ to, children, ...rest }: RendraLinkProps) {\n return (\n <a href={to} {...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}>\n {children}\n </a>\n )\n}\n\nfunction subscribeToLocation(onChange: () => void) {\n window.addEventListener('popstate', onChange)\n return () => window.removeEventListener('popstate', onChange)\n}\n\n/** Sem roteador nenhum: lê `window.location.pathname` direto. */\nfunction useDefaultCurrentPath(): string {\n return useSyncExternalStore(\n subscribeToLocation,\n () => window.location.pathname,\n () => '/',\n )\n}\n\n/** Sem roteador nenhum: navega com `window.location`. */\nfunction defaultNavigate(to: string, options?: RendraNavigateOptions) {\n if (options?.replace) {\n window.location.replace(to)\n } else {\n window.location.assign(to)\n }\n}\n\n/** Sem roteador nenhum: volta pelo histórico do navegador. */\nfunction defaultGoBack() {\n window.history.back()\n}\n\n/** Sem roteador nenhum: nenhuma trilha para montar. */\nfunction defaultUseBreadcrumbs(): RendraBreadcrumbItem[] {\n return []\n}\n\nconst defaultValue: RendraContextValue = {\n linkComponent: DefaultLink,\n useCurrentPath: useDefaultCurrentPath,\n navigate: defaultNavigate,\n goBack: defaultGoBack,\n useBreadcrumbs: defaultUseBreadcrumbs,\n}\n\nconst RendraContext = createContext<RendraContextValue>(defaultValue)\n\n/**\n * Única peça que sabe como navegar (`react-router`, outro roteador, ou nenhum).\n * Sem `RendraProvider` na árvore, os hooks abaixo caem para um comportamento simples,\n * baseado só em `window.location`/`history`, e os componentes continuam funcionando.\n */\nexport function RendraProvider({\n children,\n linkComponent,\n useCurrentPath: useCurrentPathProp,\n navigate,\n goBack,\n useBreadcrumbs,\n}: RendraProviderProps) {\n const value = useMemo<RendraContextValue>(\n () => ({\n linkComponent,\n useCurrentPath: useCurrentPathProp,\n navigate,\n goBack,\n useBreadcrumbs: useBreadcrumbs ?? defaultUseBreadcrumbs,\n }),\n [linkComponent, useCurrentPathProp, navigate, goBack, useBreadcrumbs],\n )\n return <RendraContext.Provider value={value}>{children}</RendraContext.Provider>\n}\n\n/** Componente de link do provider ativo (ou `<a href>`, sem provider). */\nexport function useRendraLink(): ComponentType<RendraLinkProps> {\n return useContext(RendraContext).linkComponent\n}\n\n/** Caminho atual (ex.: `/clientes/novo`), do provider ativo ou de `window.location`. */\nexport function useCurrentPath(): string {\n const ctx = useContext(RendraContext)\n return ctx.useCurrentPath()\n}\n\n/** Navegar e voltar, do provider ativo ou de `window.location`/`history`. */\nexport function useRendraNavigate(): {\n navigate: (to: string, options?: RendraNavigateOptions) => void\n goBack: () => void\n} {\n const ctx = useContext(RendraContext)\n return { navigate: ctx.navigate, goBack: ctx.goBack }\n}\n\n/** Trilha da rota atual, do provider ativo ou vazia, sem roteador. */\nexport function useRendraBreadcrumbs(): RendraBreadcrumbItem[] {\n const ctx = useContext(RendraContext)\n return ctx.useBreadcrumbs()\n}\n"],"mappings":";;;;;AAqDA,SAAS,YAAY,EAAE,IAAI,UAAU,GAAG,QAAyB;CAC/D,OACE,oBAAC,KAAD;EAAG,MAAM;EAAI,GAAK;EACf;CACA,CAAA;AAEP;AAEA,SAAS,oBAAoB,UAAsB;CACjD,OAAO,iBAAiB,YAAY,QAAQ;CAC5C,aAAa,OAAO,oBAAoB,YAAY,QAAQ;AAC9D;;AAGA,SAAS,wBAAgC;CACvC,OAAO,qBACL,2BACM,OAAO,SAAS,gBAChB,GACR;AACF;;AAGA,SAAS,gBAAgB,IAAY,SAAiC;CACpE,IAAI,SAAS,SACX,OAAO,SAAS,QAAQ,EAAE;MAE1B,OAAO,SAAS,OAAO,EAAE;AAE7B;;AAGA,SAAS,gBAAgB;CACvB,OAAO,QAAQ,KAAK;AACtB;;AAGA,SAAS,wBAAgD;CACvD,OAAO,CAAC;AACV;AAUA,IAAM,gBAAgB,cAAkC;CAPtD,eAAe;CACf,gBAAgB;CAChB,UAAU;CACV,QAAQ;CACR,gBAAgB;AAGsC,CAAY;;;;;;AAOpE,SAAgB,eAAe,EAC7B,UACA,eACA,gBAAgB,oBAChB,UACA,QACA,kBACsB;CACtB,MAAM,QAAQ,eACL;EACL;EACA,gBAAgB;EAChB;EACA;EACA,gBAAgB,kBAAkB;CACpC,IACA;EAAC;EAAe;EAAoB;EAAU;EAAQ;CAAc,CACtE;CACA,OAAO,oBAAC,cAAc,UAAf;EAA+B;EAAQ;CAAiC,CAAA;AACjF;;AAGA,SAAgB,gBAAgD;CAC9D,OAAO,WAAW,aAAa,CAAC,CAAC;AACnC;;AAGA,SAAgB,iBAAyB;CAEvC,OADY,WAAW,aAChB,CAAA,CAAI,eAAe;AAC5B;;AAGA,SAAgB,oBAGd;CACA,MAAM,MAAM,WAAW,aAAa;CACpC,OAAO;EAAE,UAAU,IAAI;EAAU,QAAQ,IAAI;CAAO;AACtD;;AAGA,SAAgB,uBAA+C;CAE7D,OADY,WAAW,aAChB,CAAA,CAAI,eAAe;AAC5B"}
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
|
|
2
|
+
import { t as cn } from "./cn-DVPheE99.js";
|
|
3
|
+
import { a as DropdownMenuContent, d as DropdownMenuSeparator, h as DropdownMenuTrigger, r as DropdownMenu, s as DropdownMenuItem, t as Tooltip } from "./tooltip-DyeuGPCc.js";
|
|
4
|
+
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Bold, Code, Code2, Eraser, ImagePlus, Italic, Link2, List, ListOrdered, Minus, Quote, Redo2, Strikethrough, Table, Underline, Undo2 } from "lucide-react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { Image } from "@tiptap/extension-image";
|
|
8
|
+
import { Placeholder } from "@tiptap/extension-placeholder";
|
|
9
|
+
import { TableKit } from "@tiptap/extension-table";
|
|
10
|
+
import { TextAlign } from "@tiptap/extension-text-align";
|
|
11
|
+
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
|
|
12
|
+
import { StarterKit } from "@tiptap/starter-kit";
|
|
13
|
+
//#region src/components/ui/rich-text-editor.tsx
|
|
14
|
+
var heights = {
|
|
15
|
+
sm: "min-h-24",
|
|
16
|
+
md: "min-h-chart-sm",
|
|
17
|
+
lg: "min-h-chart-md"
|
|
18
|
+
};
|
|
19
|
+
var readAsDataUrl = (file) => new Promise((resolve, reject) => {
|
|
20
|
+
const reader = new FileReader();
|
|
21
|
+
reader.onload = () => resolve(String(reader.result));
|
|
22
|
+
reader.onerror = () => reject(reader.error);
|
|
23
|
+
reader.readAsDataURL(file);
|
|
24
|
+
});
|
|
25
|
+
function ToolButton({ label, active, disabled, onClick, children }) {
|
|
26
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
27
|
+
content: label,
|
|
28
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
29
|
+
type: "button",
|
|
30
|
+
"aria-label": label,
|
|
31
|
+
"aria-pressed": active,
|
|
32
|
+
disabled,
|
|
33
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
34
|
+
onClick,
|
|
35
|
+
className: cn("flex size-control-sm shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 [&_svg]:size-icon-sm", active && "bg-primary-soft text-primary-soft-foreground hover:bg-primary-soft"),
|
|
36
|
+
children
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
var Divider = () => /* @__PURE__ */ jsx("span", {
|
|
41
|
+
"aria-hidden": true,
|
|
42
|
+
className: "mx-1 h-6 w-px shrink-0 bg-border"
|
|
43
|
+
});
|
|
44
|
+
function Toolbar({ editor, source, onToggleSource, onPickImage }) {
|
|
45
|
+
const s = useEditorState({
|
|
46
|
+
editor,
|
|
47
|
+
selector: ({ editor: e }) => ({
|
|
48
|
+
bold: e.isActive("bold"),
|
|
49
|
+
italic: e.isActive("italic"),
|
|
50
|
+
underline: e.isActive("underline"),
|
|
51
|
+
strike: e.isActive("strike"),
|
|
52
|
+
code: e.isActive("code"),
|
|
53
|
+
link: e.isActive("link"),
|
|
54
|
+
bullet: e.isActive("bulletList"),
|
|
55
|
+
ordered: e.isActive("orderedList"),
|
|
56
|
+
quote: e.isActive("blockquote"),
|
|
57
|
+
table: e.isActive("table"),
|
|
58
|
+
h: [
|
|
59
|
+
1,
|
|
60
|
+
2,
|
|
61
|
+
3
|
|
62
|
+
].find((level) => e.isActive("heading", { level })) ?? 0,
|
|
63
|
+
align: [
|
|
64
|
+
"center",
|
|
65
|
+
"right",
|
|
66
|
+
"justify"
|
|
67
|
+
].find((a) => e.isActive({ textAlign: a })) ?? "left",
|
|
68
|
+
canUndo: e.can().undo(),
|
|
69
|
+
canRedo: e.can().redo()
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
const chain = () => editor.chain().focus();
|
|
73
|
+
const off = source;
|
|
74
|
+
const setLink = () => {
|
|
75
|
+
const prev = editor.getAttributes("link").href;
|
|
76
|
+
const url = window.prompt("Endereço do link", prev ?? "https://");
|
|
77
|
+
if (url === null) return;
|
|
78
|
+
if (!url) chain().extendMarkRange("link").unsetLink().run();
|
|
79
|
+
else chain().extendMarkRange("link").setLink({ href: url }).run();
|
|
80
|
+
};
|
|
81
|
+
const blockLabel = s.h ? `Título ${s.h}` : "Parágrafo";
|
|
82
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
83
|
+
role: "toolbar",
|
|
84
|
+
"aria-label": "Formatação",
|
|
85
|
+
className: "flex flex-wrap items-center gap-1 border-b p-1",
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
88
|
+
label: "Desfazer",
|
|
89
|
+
disabled: off || !s.canUndo,
|
|
90
|
+
onClick: () => chain().undo().run(),
|
|
91
|
+
children: /* @__PURE__ */ jsx(Undo2, {})
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
94
|
+
label: "Refazer",
|
|
95
|
+
disabled: off || !s.canRedo,
|
|
96
|
+
onClick: () => chain().redo().run(),
|
|
97
|
+
children: /* @__PURE__ */ jsx(Redo2, {})
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
100
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
101
|
+
disabled: off,
|
|
102
|
+
className: "flex h-control-sm cursor-pointer items-center gap-1 rounded-item px-2 text-sm font-medium text-foreground outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
|
|
103
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
104
|
+
children: blockLabel
|
|
105
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
106
|
+
align: "start",
|
|
107
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
108
|
+
onSelect: () => chain().setParagraph().run(),
|
|
109
|
+
children: "Parágrafo"
|
|
110
|
+
}), [
|
|
111
|
+
1,
|
|
112
|
+
2,
|
|
113
|
+
3
|
|
114
|
+
].map((level) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
115
|
+
onSelect: () => chain().toggleHeading({ level }).run(),
|
|
116
|
+
children: ["Título ", level]
|
|
117
|
+
}, level))]
|
|
118
|
+
})] }),
|
|
119
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
120
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
121
|
+
label: "Negrito (Ctrl+B)",
|
|
122
|
+
active: s.bold,
|
|
123
|
+
disabled: off,
|
|
124
|
+
onClick: () => chain().toggleBold().run(),
|
|
125
|
+
children: /* @__PURE__ */ jsx(Bold, {})
|
|
126
|
+
}),
|
|
127
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
128
|
+
label: "Itálico (Ctrl+I)",
|
|
129
|
+
active: s.italic,
|
|
130
|
+
disabled: off,
|
|
131
|
+
onClick: () => chain().toggleItalic().run(),
|
|
132
|
+
children: /* @__PURE__ */ jsx(Italic, {})
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
135
|
+
label: "Sublinhado (Ctrl+U)",
|
|
136
|
+
active: s.underline,
|
|
137
|
+
disabled: off,
|
|
138
|
+
onClick: () => chain().toggleUnderline().run(),
|
|
139
|
+
children: /* @__PURE__ */ jsx(Underline, {})
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
142
|
+
label: "Riscado",
|
|
143
|
+
active: s.strike,
|
|
144
|
+
disabled: off,
|
|
145
|
+
onClick: () => chain().toggleStrike().run(),
|
|
146
|
+
children: /* @__PURE__ */ jsx(Strikethrough, {})
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
149
|
+
label: "Código",
|
|
150
|
+
active: s.code,
|
|
151
|
+
disabled: off,
|
|
152
|
+
onClick: () => chain().toggleCode().run(),
|
|
153
|
+
children: /* @__PURE__ */ jsx(Code, {})
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
156
|
+
label: "Link",
|
|
157
|
+
active: s.link,
|
|
158
|
+
disabled: off,
|
|
159
|
+
onClick: setLink,
|
|
160
|
+
children: /* @__PURE__ */ jsx(Link2, {})
|
|
161
|
+
}),
|
|
162
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
163
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
164
|
+
label: "Lista com marcadores",
|
|
165
|
+
active: s.bullet,
|
|
166
|
+
disabled: off,
|
|
167
|
+
onClick: () => chain().toggleBulletList().run(),
|
|
168
|
+
children: /* @__PURE__ */ jsx(List, {})
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
171
|
+
label: "Lista numerada",
|
|
172
|
+
active: s.ordered,
|
|
173
|
+
disabled: off,
|
|
174
|
+
onClick: () => chain().toggleOrderedList().run(),
|
|
175
|
+
children: /* @__PURE__ */ jsx(ListOrdered, {})
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
178
|
+
label: "Citação",
|
|
179
|
+
active: s.quote,
|
|
180
|
+
disabled: off,
|
|
181
|
+
onClick: () => chain().toggleBlockquote().run(),
|
|
182
|
+
children: /* @__PURE__ */ jsx(Quote, {})
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
185
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
186
|
+
label: "Alinhar à esquerda",
|
|
187
|
+
active: s.align === "left",
|
|
188
|
+
disabled: off,
|
|
189
|
+
onClick: () => chain().setTextAlign("left").run(),
|
|
190
|
+
children: /* @__PURE__ */ jsx(AlignLeft, {})
|
|
191
|
+
}),
|
|
192
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
193
|
+
label: "Centralizar",
|
|
194
|
+
active: s.align === "center",
|
|
195
|
+
disabled: off,
|
|
196
|
+
onClick: () => chain().setTextAlign("center").run(),
|
|
197
|
+
children: /* @__PURE__ */ jsx(AlignCenter, {})
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
200
|
+
label: "Alinhar à direita",
|
|
201
|
+
active: s.align === "right",
|
|
202
|
+
disabled: off,
|
|
203
|
+
onClick: () => chain().setTextAlign("right").run(),
|
|
204
|
+
children: /* @__PURE__ */ jsx(AlignRight, {})
|
|
205
|
+
}),
|
|
206
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
207
|
+
label: "Justificar",
|
|
208
|
+
active: s.align === "justify",
|
|
209
|
+
disabled: off,
|
|
210
|
+
onClick: () => chain().setTextAlign("justify").run(),
|
|
211
|
+
children: /* @__PURE__ */ jsx(AlignJustify, {})
|
|
212
|
+
}),
|
|
213
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
214
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
215
|
+
label: "Inserir imagem",
|
|
216
|
+
disabled: off,
|
|
217
|
+
onClick: onPickImage,
|
|
218
|
+
children: /* @__PURE__ */ jsx(ImagePlus, {})
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
221
|
+
content: "Tabela",
|
|
222
|
+
children: /* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
223
|
+
"aria-label": "Tabela",
|
|
224
|
+
disabled: off,
|
|
225
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
226
|
+
className: cn("flex size-control-sm cursor-pointer items-center justify-center rounded-item text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40 [&_svg]:size-icon-sm", s.table && "bg-primary-soft text-primary-soft-foreground"),
|
|
227
|
+
children: /* @__PURE__ */ jsx(Table, {})
|
|
228
|
+
})
|
|
229
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
230
|
+
align: "start",
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
233
|
+
onSelect: () => chain().insertTable({
|
|
234
|
+
rows: 3,
|
|
235
|
+
cols: 3,
|
|
236
|
+
withHeaderRow: true
|
|
237
|
+
}).run(),
|
|
238
|
+
children: "Inserir tabela 3 x 3"
|
|
239
|
+
}),
|
|
240
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
241
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
242
|
+
disabled: !s.table,
|
|
243
|
+
onSelect: () => chain().addRowAfter().run(),
|
|
244
|
+
children: "Adicionar linha abaixo"
|
|
245
|
+
}),
|
|
246
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
247
|
+
disabled: !s.table,
|
|
248
|
+
onSelect: () => chain().addColumnAfter().run(),
|
|
249
|
+
children: "Adicionar coluna à direita"
|
|
250
|
+
}),
|
|
251
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
252
|
+
disabled: !s.table,
|
|
253
|
+
onSelect: () => chain().deleteRow().run(),
|
|
254
|
+
children: "Excluir linha"
|
|
255
|
+
}),
|
|
256
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
257
|
+
disabled: !s.table,
|
|
258
|
+
onSelect: () => chain().deleteColumn().run(),
|
|
259
|
+
children: "Excluir coluna"
|
|
260
|
+
}),
|
|
261
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
262
|
+
disabled: !s.table,
|
|
263
|
+
destructive: true,
|
|
264
|
+
onSelect: () => chain().deleteTable().run(),
|
|
265
|
+
children: "Excluir tabela"
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
})] }),
|
|
269
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
270
|
+
label: "Linha divisória",
|
|
271
|
+
disabled: off,
|
|
272
|
+
onClick: () => chain().setHorizontalRule().run(),
|
|
273
|
+
children: /* @__PURE__ */ jsx(Minus, {})
|
|
274
|
+
}),
|
|
275
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
276
|
+
label: "Limpar formatação",
|
|
277
|
+
disabled: off,
|
|
278
|
+
onClick: () => chain().unsetAllMarks().clearNodes().run(),
|
|
279
|
+
children: /* @__PURE__ */ jsx(Eraser, {})
|
|
280
|
+
}),
|
|
281
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
282
|
+
/* @__PURE__ */ jsx(ToolButton, {
|
|
283
|
+
label: source ? "Voltar ao editor visual" : "Editar HTML",
|
|
284
|
+
active: source,
|
|
285
|
+
onClick: onToggleSource,
|
|
286
|
+
children: /* @__PURE__ */ jsx(Code2, {})
|
|
287
|
+
})
|
|
288
|
+
]
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function RichTextEditor({ value, defaultValue, onChange, placeholder = "Escreva aqui...", onImageUpload, minHeight = "md", invalid, disabled, id, className, ...aria }) {
|
|
292
|
+
const [source, setSource] = useState(false);
|
|
293
|
+
const [html, setHtml] = useState(value ?? defaultValue ?? "");
|
|
294
|
+
const fileRef = useRef(null);
|
|
295
|
+
const cb = useRef({
|
|
296
|
+
onChange,
|
|
297
|
+
onImageUpload
|
|
298
|
+
});
|
|
299
|
+
cb.current = {
|
|
300
|
+
onChange,
|
|
301
|
+
onImageUpload
|
|
302
|
+
};
|
|
303
|
+
const insertFiles = async (editor, files, pos) => {
|
|
304
|
+
for (const file of files.filter((f) => f.type.startsWith("image/"))) {
|
|
305
|
+
const src = cb.current.onImageUpload ? await cb.current.onImageUpload(file) : await readAsDataUrl(file);
|
|
306
|
+
const chain = editor.chain().focus();
|
|
307
|
+
if (pos !== void 0) chain.insertContentAt(pos, {
|
|
308
|
+
type: "image",
|
|
309
|
+
attrs: {
|
|
310
|
+
src,
|
|
311
|
+
alt: file.name
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
else chain.setImage({
|
|
315
|
+
src,
|
|
316
|
+
alt: file.name
|
|
317
|
+
});
|
|
318
|
+
chain.run();
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const editor = useEditor({
|
|
322
|
+
immediatelyRender: false,
|
|
323
|
+
editable: !disabled,
|
|
324
|
+
content: value ?? defaultValue ?? "",
|
|
325
|
+
extensions: [
|
|
326
|
+
StarterKit.configure({ link: {
|
|
327
|
+
openOnClick: false,
|
|
328
|
+
autolink: true
|
|
329
|
+
} }),
|
|
330
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
331
|
+
TableKit.configure({ table: { resizable: false } }),
|
|
332
|
+
Image.configure({
|
|
333
|
+
allowBase64: true,
|
|
334
|
+
resize: {
|
|
335
|
+
enabled: true,
|
|
336
|
+
directions: [
|
|
337
|
+
"top-left",
|
|
338
|
+
"top-right",
|
|
339
|
+
"bottom-left",
|
|
340
|
+
"bottom-right"
|
|
341
|
+
],
|
|
342
|
+
minWidth: 48,
|
|
343
|
+
minHeight: 48,
|
|
344
|
+
alwaysPreserveAspectRatio: true
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
Placeholder.configure({ placeholder })
|
|
348
|
+
],
|
|
349
|
+
editorProps: {
|
|
350
|
+
attributes: {
|
|
351
|
+
class: cn("rich-text min-w-0 px-4 py-3 text-base outline-none md:text-sm", heights[minHeight]),
|
|
352
|
+
...id ? { id } : {},
|
|
353
|
+
role: "textbox",
|
|
354
|
+
"aria-multiline": "true",
|
|
355
|
+
"aria-label": aria["aria-label"] ?? placeholder,
|
|
356
|
+
...aria["aria-describedby"] ? { "aria-describedby": aria["aria-describedby"] } : {},
|
|
357
|
+
...invalid ? { "aria-invalid": "true" } : {}
|
|
358
|
+
},
|
|
359
|
+
handlePaste: (_view, event) => {
|
|
360
|
+
const files = [...event.clipboardData?.files ?? []];
|
|
361
|
+
if (!files.some((f) => f.type.startsWith("image/")) || !editorRef.current) return false;
|
|
362
|
+
insertFiles(editorRef.current, files);
|
|
363
|
+
return true;
|
|
364
|
+
},
|
|
365
|
+
handleDrop: (view, event, _slice, moved) => {
|
|
366
|
+
const files = [...event.dataTransfer?.files ?? []];
|
|
367
|
+
if (moved || !files.some((f) => f.type.startsWith("image/")) || !editorRef.current) return false;
|
|
368
|
+
const pos = view.posAtCoords({
|
|
369
|
+
left: event.clientX,
|
|
370
|
+
top: event.clientY
|
|
371
|
+
})?.pos;
|
|
372
|
+
insertFiles(editorRef.current, files, pos);
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
onUpdate: ({ editor: e }) => {
|
|
377
|
+
const next = e.getHTML();
|
|
378
|
+
setHtml(next);
|
|
379
|
+
cb.current.onChange?.(next);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const editorRef = useRef(null);
|
|
383
|
+
editorRef.current = editor;
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
if (!editor || value === void 0 || value === editor.getHTML()) return;
|
|
386
|
+
editor.commands.setContent(value, { emitUpdate: false });
|
|
387
|
+
setHtml(value);
|
|
388
|
+
}, [editor, value]);
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
editor?.setEditable(!disabled);
|
|
391
|
+
}, [editor, disabled]);
|
|
392
|
+
const toggleSource = () => {
|
|
393
|
+
if (!editor) return;
|
|
394
|
+
if (source) editor.commands.setContent(html, { emitUpdate: true });
|
|
395
|
+
else setHtml(editor.getHTML());
|
|
396
|
+
setSource((v) => !v);
|
|
397
|
+
};
|
|
398
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
399
|
+
"data-slot": "control",
|
|
400
|
+
"data-rendra": "RTE-001",
|
|
401
|
+
className: cn("flex min-w-0 flex-col overflow-hidden rounded-control border border-input bg-field text-foreground transition-[border-color,box-shadow] duration-150 focus-within:border-ring focus-within:bg-card focus-within:ring-2 focus-within:ring-ring/25", invalid && "border-destructive focus-within:border-destructive focus-within:ring-destructive/25", disabled && "opacity-60", className),
|
|
402
|
+
children: [
|
|
403
|
+
editor && /* @__PURE__ */ jsx(Toolbar, {
|
|
404
|
+
editor,
|
|
405
|
+
source,
|
|
406
|
+
onToggleSource: toggleSource,
|
|
407
|
+
onPickImage: () => fileRef.current?.click()
|
|
408
|
+
}),
|
|
409
|
+
source ? /* @__PURE__ */ jsx("textarea", {
|
|
410
|
+
"aria-label": "Código HTML do conteúdo",
|
|
411
|
+
value: html,
|
|
412
|
+
onChange: (e) => {
|
|
413
|
+
setHtml(e.target.value);
|
|
414
|
+
cb.current.onChange?.(e.target.value);
|
|
415
|
+
},
|
|
416
|
+
spellCheck: false,
|
|
417
|
+
className: cn("w-full resize-y bg-transparent px-4 py-3 font-mono text-sm outline-none", heights[minHeight])
|
|
418
|
+
}) : /* @__PURE__ */ jsx(EditorContent, {
|
|
419
|
+
editor,
|
|
420
|
+
className: "min-w-0 cursor-text"
|
|
421
|
+
}),
|
|
422
|
+
/* @__PURE__ */ jsx("input", {
|
|
423
|
+
ref: fileRef,
|
|
424
|
+
type: "file",
|
|
425
|
+
accept: "image/*",
|
|
426
|
+
multiple: true,
|
|
427
|
+
hidden: true,
|
|
428
|
+
onChange: (e) => {
|
|
429
|
+
if (editor && e.target.files) insertFiles(editor, [...e.target.files]);
|
|
430
|
+
e.target.value = "";
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
]
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
export { RichTextEditor };
|
|
438
|
+
|
|
439
|
+
//# sourceMappingURL=rich-text-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-text-editor.js","names":[],"sources":["../src/components/ui/rich-text-editor.tsx"],"sourcesContent":["import { Image } from '@tiptap/extension-image'\nimport { Placeholder } from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport { TextAlign } from '@tiptap/extension-text-align'\nimport { EditorContent, useEditor, useEditorState, type Editor } from '@tiptap/react'\nimport { StarterKit } from '@tiptap/starter-kit'\nimport {\n AlignCenter,\n AlignJustify,\n AlignLeft,\n AlignRight,\n Bold,\n Code,\n Code2,\n Eraser,\n ImagePlus,\n Italic,\n Link2,\n List,\n ListOrdered,\n Minus,\n Quote,\n Redo2,\n Strikethrough,\n Table as TableIcon,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport { useEffect, useRef, useState, type ReactNode } from 'react'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Tooltip } from '@/components/ui/tooltip'\nimport { cn } from '@/lib/cn'\n\n/*\n * Editor de texto rico único (Tiptap, código aberto). Formatação completa, listas,\n * alinhamento, links, tabela, imagem e modo HTML para colar ou editar o código.\n * Imagens: botão, colar (Ctrl+V de um print ou imagem copiada) ou arrastar e soltar; ao\n * tocar na imagem aparecem 4 alças nos cantos para redimensionar, mantendo a proporção.\n * Mobile: a barra quebra em linhas (sem rolagem lateral) e as alças ficam maiores.\n */\n\nexport interface RichTextEditorProps {\n /** Conteúdo em HTML. */\n value?: string\n defaultValue?: string\n onChange?: (html: string) => void\n placeholder?: string\n /**\n * Envia a imagem e devolve a URL pública. Sem ela, a imagem entra no conteúdo em base64\n * (bom para demonstração; em produção, envie ao servidor).\n */\n onImageUpload?: (file: File) => Promise<string>\n /** Altura mínima da área de texto. */\n minHeight?: 'sm' | 'md' | 'lg'\n invalid?: boolean\n disabled?: boolean\n id?: string\n 'aria-label'?: string\n 'aria-describedby'?: string\n className?: string\n}\n\nconst heights = { sm: 'min-h-24', md: 'min-h-chart-sm', lg: 'min-h-chart-md' } as const\n\nconst readAsDataUrl = (file: File) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(file)\n })\n\nfunction ToolButton({\n label,\n active,\n disabled,\n onClick,\n children,\n}: {\n label: string\n active?: boolean\n disabled?: boolean\n onClick: () => void\n children: ReactNode\n}) {\n return (\n <Tooltip content={label}>\n <button\n type=\"button\"\n aria-label={label}\n aria-pressed={active}\n disabled={disabled}\n // Mantém a seleção do texto ao clicar no botão.\n onMouseDown={(e) => e.preventDefault()}\n onClick={onClick}\n className={cn(\n 'flex size-control-sm shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 [&_svg]:size-icon-sm',\n active && 'bg-primary-soft text-primary-soft-foreground hover:bg-primary-soft',\n )}\n >\n {children}\n </button>\n </Tooltip>\n )\n}\n\nconst Divider = () => <span aria-hidden className=\"mx-1 h-6 w-px shrink-0 bg-border\" />\n\nfunction Toolbar({\n editor,\n source,\n onToggleSource,\n onPickImage,\n}: {\n editor: Editor\n source: boolean\n onToggleSource: () => void\n onPickImage: () => void\n}) {\n const s = useEditorState({\n editor,\n selector: ({ editor: e }) => ({\n bold: e.isActive('bold'),\n italic: e.isActive('italic'),\n underline: e.isActive('underline'),\n strike: e.isActive('strike'),\n code: e.isActive('code'),\n link: e.isActive('link'),\n bullet: e.isActive('bulletList'),\n ordered: e.isActive('orderedList'),\n quote: e.isActive('blockquote'),\n table: e.isActive('table'),\n h: [1, 2, 3].find((level) => e.isActive('heading', { level })) ?? 0,\n align:\n (['center', 'right', 'justify'] as const).find((a) => e.isActive({ textAlign: a })) ??\n 'left',\n canUndo: e.can().undo(),\n canRedo: e.can().redo(),\n }),\n })\n const chain = () => editor.chain().focus()\n const off = source\n\n const setLink = () => {\n const prev = editor.getAttributes('link').href as string | undefined\n const url = window.prompt('Endereço do link', prev ?? 'https://')\n if (url === null) return\n if (!url) chain().extendMarkRange('link').unsetLink().run()\n else chain().extendMarkRange('link').setLink({ href: url }).run()\n }\n\n const blockLabel = s.h ? `Título ${s.h}` : 'Parágrafo'\n\n return (\n <div\n role=\"toolbar\"\n aria-label=\"Formatação\"\n className=\"flex flex-wrap items-center gap-1 border-b p-1\"\n >\n <ToolButton\n label=\"Desfazer\"\n disabled={off || !s.canUndo}\n onClick={() => chain().undo().run()}\n >\n <Undo2 />\n </ToolButton>\n <ToolButton label=\"Refazer\" disabled={off || !s.canRedo} onClick={() => chain().redo().run()}>\n <Redo2 />\n </ToolButton>\n <Divider />\n <DropdownMenu>\n <DropdownMenuTrigger\n disabled={off}\n className=\"flex h-control-sm cursor-pointer items-center gap-1 rounded-item px-2 text-sm font-medium text-foreground outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\"\n onMouseDown={(e) => e.preventDefault()}\n >\n {blockLabel}\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem onSelect={() => chain().setParagraph().run()}>\n Parágrafo\n </DropdownMenuItem>\n {([1, 2, 3] as const).map((level) => (\n <DropdownMenuItem key={level} onSelect={() => chain().toggleHeading({ level }).run()}>\n Título {level}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n <Divider />\n <ToolButton\n label=\"Negrito (Ctrl+B)\"\n active={s.bold}\n disabled={off}\n onClick={() => chain().toggleBold().run()}\n >\n <Bold />\n </ToolButton>\n <ToolButton\n label=\"Itálico (Ctrl+I)\"\n active={s.italic}\n disabled={off}\n onClick={() => chain().toggleItalic().run()}\n >\n <Italic />\n </ToolButton>\n <ToolButton\n label=\"Sublinhado (Ctrl+U)\"\n active={s.underline}\n disabled={off}\n onClick={() => chain().toggleUnderline().run()}\n >\n <Underline />\n </ToolButton>\n <ToolButton\n label=\"Riscado\"\n active={s.strike}\n disabled={off}\n onClick={() => chain().toggleStrike().run()}\n >\n <Strikethrough />\n </ToolButton>\n <ToolButton\n label=\"Código\"\n active={s.code}\n disabled={off}\n onClick={() => chain().toggleCode().run()}\n >\n <Code />\n </ToolButton>\n <ToolButton label=\"Link\" active={s.link} disabled={off} onClick={setLink}>\n <Link2 />\n </ToolButton>\n <Divider />\n <ToolButton\n label=\"Lista com marcadores\"\n active={s.bullet}\n disabled={off}\n onClick={() => chain().toggleBulletList().run()}\n >\n <List />\n </ToolButton>\n <ToolButton\n label=\"Lista numerada\"\n active={s.ordered}\n disabled={off}\n onClick={() => chain().toggleOrderedList().run()}\n >\n <ListOrdered />\n </ToolButton>\n <ToolButton\n label=\"Citação\"\n active={s.quote}\n disabled={off}\n onClick={() => chain().toggleBlockquote().run()}\n >\n <Quote />\n </ToolButton>\n <Divider />\n <ToolButton\n label=\"Alinhar à esquerda\"\n active={s.align === 'left'}\n disabled={off}\n onClick={() => chain().setTextAlign('left').run()}\n >\n <AlignLeft />\n </ToolButton>\n <ToolButton\n label=\"Centralizar\"\n active={s.align === 'center'}\n disabled={off}\n onClick={() => chain().setTextAlign('center').run()}\n >\n <AlignCenter />\n </ToolButton>\n <ToolButton\n label=\"Alinhar à direita\"\n active={s.align === 'right'}\n disabled={off}\n onClick={() => chain().setTextAlign('right').run()}\n >\n <AlignRight />\n </ToolButton>\n <ToolButton\n label=\"Justificar\"\n active={s.align === 'justify'}\n disabled={off}\n onClick={() => chain().setTextAlign('justify').run()}\n >\n <AlignJustify />\n </ToolButton>\n <Divider />\n <ToolButton label=\"Inserir imagem\" disabled={off} onClick={onPickImage}>\n <ImagePlus />\n </ToolButton>\n <DropdownMenu>\n <Tooltip content=\"Tabela\">\n <DropdownMenuTrigger\n aria-label=\"Tabela\"\n disabled={off}\n onMouseDown={(e) => e.preventDefault()}\n className={cn(\n 'flex size-control-sm cursor-pointer items-center justify-center rounded-item text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40 [&_svg]:size-icon-sm',\n s.table && 'bg-primary-soft text-primary-soft-foreground',\n )}\n >\n <TableIcon />\n </DropdownMenuTrigger>\n </Tooltip>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n onSelect={() => chain().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}\n >\n Inserir tabela 3 x 3\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem disabled={!s.table} onSelect={() => chain().addRowAfter().run()}>\n Adicionar linha abaixo\n </DropdownMenuItem>\n <DropdownMenuItem disabled={!s.table} onSelect={() => chain().addColumnAfter().run()}>\n Adicionar coluna à direita\n </DropdownMenuItem>\n <DropdownMenuItem disabled={!s.table} onSelect={() => chain().deleteRow().run()}>\n Excluir linha\n </DropdownMenuItem>\n <DropdownMenuItem disabled={!s.table} onSelect={() => chain().deleteColumn().run()}>\n Excluir coluna\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!s.table}\n destructive\n onSelect={() => chain().deleteTable().run()}\n >\n Excluir tabela\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <ToolButton\n label=\"Linha divisória\"\n disabled={off}\n onClick={() => chain().setHorizontalRule().run()}\n >\n <Minus />\n </ToolButton>\n <ToolButton\n label=\"Limpar formatação\"\n disabled={off}\n onClick={() => chain().unsetAllMarks().clearNodes().run()}\n >\n <Eraser />\n </ToolButton>\n <Divider />\n <ToolButton\n label={source ? 'Voltar ao editor visual' : 'Editar HTML'}\n active={source}\n onClick={onToggleSource}\n >\n <Code2 />\n </ToolButton>\n </div>\n )\n}\n\nexport function RichTextEditor({\n value,\n defaultValue,\n onChange,\n placeholder = 'Escreva aqui...',\n onImageUpload,\n minHeight = 'md',\n invalid,\n disabled,\n id,\n className,\n ...aria\n}: RichTextEditorProps) {\n const [source, setSource] = useState(false)\n const [html, setHtml] = useState(value ?? defaultValue ?? '')\n const fileRef = useRef<HTMLInputElement>(null)\n const cb = useRef({ onChange, onImageUpload })\n cb.current = { onChange, onImageUpload }\n\n const insertFiles = async (editor: Editor, files: File[], pos?: number) => {\n for (const file of files.filter((f) => f.type.startsWith('image/'))) {\n const src = cb.current.onImageUpload\n ? await cb.current.onImageUpload(file)\n : await readAsDataUrl(file)\n const chain = editor.chain().focus()\n if (pos !== undefined)\n chain.insertContentAt(pos, { type: 'image', attrs: { src, alt: file.name } })\n else chain.setImage({ src, alt: file.name })\n chain.run()\n }\n }\n\n const editor = useEditor({\n immediatelyRender: false,\n editable: !disabled,\n content: value ?? defaultValue ?? '',\n extensions: [\n StarterKit.configure({ link: { openOnClick: false, autolink: true } }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n TableKit.configure({ table: { resizable: false } }),\n Image.configure({\n allowBase64: true,\n resize: {\n enabled: true,\n directions: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],\n minWidth: 48,\n minHeight: 48,\n alwaysPreserveAspectRatio: true,\n },\n }),\n Placeholder.configure({ placeholder }),\n ],\n editorProps: {\n attributes: {\n class: cn(\n 'rich-text min-w-0 px-4 py-3 text-base outline-none md:text-sm',\n heights[minHeight],\n ),\n ...(id ? { id } : {}),\n role: 'textbox',\n 'aria-multiline': 'true',\n // Nome acessível: o rótulo do Field não nomeia uma div editável; sem aria-label, usa o\n // placeholder.\n 'aria-label': aria['aria-label'] ?? placeholder,\n ...(aria['aria-describedby'] ? { 'aria-describedby': aria['aria-describedby'] } : {}),\n ...(invalid ? { 'aria-invalid': 'true' } : {}),\n },\n // Colar um print ou imagem copiada (Ctrl+V): vira imagem no conteúdo.\n handlePaste: (_view, event) => {\n const files = [...(event.clipboardData?.files ?? [])]\n if (!files.some((f) => f.type.startsWith('image/')) || !editorRef.current) return false\n void insertFiles(editorRef.current, files)\n return true\n },\n // Arrastar e soltar imagens na posição em que caem.\n handleDrop: (view, event, _slice, moved) => {\n const files = [...(event.dataTransfer?.files ?? [])]\n if (moved || !files.some((f) => f.type.startsWith('image/')) || !editorRef.current)\n return false\n const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos\n void insertFiles(editorRef.current, files, pos)\n return true\n },\n },\n onUpdate: ({ editor: e }) => {\n const next = e.getHTML()\n setHtml(next)\n cb.current.onChange?.(next)\n },\n })\n const editorRef = useRef<Editor | null>(null)\n editorRef.current = editor\n\n // Valor controlado vindo de fora.\n useEffect(() => {\n if (!editor || value === undefined || value === editor.getHTML()) return\n editor.commands.setContent(value, { emitUpdate: false })\n setHtml(value)\n }, [editor, value])\n\n useEffect(() => {\n editor?.setEditable(!disabled)\n }, [editor, disabled])\n\n const toggleSource = () => {\n if (!editor) return\n if (source) editor.commands.setContent(html, { emitUpdate: true })\n else setHtml(editor.getHTML())\n setSource((v) => !v)\n }\n\n return (\n <div\n data-slot=\"control\"\n data-rendra=\"RTE-001\"\n className={cn(\n 'flex min-w-0 flex-col overflow-hidden rounded-control border border-input bg-field text-foreground transition-[border-color,box-shadow] duration-150 focus-within:border-ring focus-within:bg-card focus-within:ring-2 focus-within:ring-ring/25',\n invalid &&\n 'border-destructive focus-within:border-destructive focus-within:ring-destructive/25',\n disabled && 'opacity-60',\n className,\n )}\n >\n {editor && (\n <Toolbar\n editor={editor}\n source={source}\n onToggleSource={toggleSource}\n onPickImage={() => fileRef.current?.click()}\n />\n )}\n {source ? (\n <textarea\n aria-label=\"Código HTML do conteúdo\"\n value={html}\n onChange={(e) => {\n setHtml(e.target.value)\n cb.current.onChange?.(e.target.value)\n }}\n spellCheck={false}\n className={cn(\n 'w-full resize-y bg-transparent px-4 py-3 font-mono text-sm outline-none',\n heights[minHeight],\n )}\n />\n ) : (\n <EditorContent editor={editor} className=\"min-w-0 cursor-text\" />\n )}\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n onChange={(e) => {\n if (editor && e.target.files) void insertFiles(editor, [...e.target.files])\n e.target.value = ''\n }}\n />\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
|
|
2
|
+
import { t as RendraProvider } from "./rendra-provider-cCnquOZy.js";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { useCallback } from "react";
|
|
5
|
+
import { Link, Outlet, useLocation, useMatches, useNavigate } from "react-router";
|
|
6
|
+
//#region src/components/rendra-router-bridge.tsx
|
|
7
|
+
/** `Link` do react-router com a interface do `RendraLinkProps` (repassa o resto). */
|
|
8
|
+
function RouterLink({ to, children, ...rest }) {
|
|
9
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
10
|
+
to,
|
|
11
|
+
...rest,
|
|
12
|
+
children
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** Caminho atual do react-router. */
|
|
16
|
+
function useCurrentPathFromRouter() {
|
|
17
|
+
return useLocation().pathname;
|
|
18
|
+
}
|
|
19
|
+
/** Trilha a partir de `useMatches`/`handle.crumb`, a mesma convenção do header. */
|
|
20
|
+
function useBreadcrumbsFromMatches() {
|
|
21
|
+
return useMatches().filter((m) => m.handle?.crumb).map((m, i, all) => {
|
|
22
|
+
const crumb = m.handle.crumb;
|
|
23
|
+
return {
|
|
24
|
+
label: typeof crumb === "function" ? crumb(m.params) : crumb,
|
|
25
|
+
to: i < all.length - 1 ? m.pathname : void 0
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Liga o `RendraProvider` ao `react-router` do boilerplate: link, caminho atual, navegar
|
|
31
|
+
* e a trilha. É o único lugar do app que sabe que o roteador é o `react-router`; os
|
|
32
|
+
* componentes de `src/components/ui` e o `AppShell` não importam `react-router` diretamente.
|
|
33
|
+
* Sem `children`, renderiza `<Outlet />`, para ser usado como elemento raiz das rotas.
|
|
34
|
+
*/
|
|
35
|
+
function RendraRouterBridge({ children } = {}) {
|
|
36
|
+
const navigate = useNavigate();
|
|
37
|
+
const go = useCallback((to, options) => navigate(to, options), [navigate]);
|
|
38
|
+
const goBack = useCallback(() => navigate(-1), [navigate]);
|
|
39
|
+
return /* @__PURE__ */ jsx(RendraProvider, {
|
|
40
|
+
linkComponent: RouterLink,
|
|
41
|
+
useCurrentPath: useCurrentPathFromRouter,
|
|
42
|
+
navigate: go,
|
|
43
|
+
goBack,
|
|
44
|
+
useBreadcrumbs: useBreadcrumbsFromMatches,
|
|
45
|
+
children: children ?? /* @__PURE__ */ jsx(Outlet, {})
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { RendraRouterBridge };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=router-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-bridge.js","names":[],"sources":["../src/components/rendra-router-bridge.tsx"],"sourcesContent":["import { useCallback, type ReactNode } from 'react'\nimport { Link, Outlet, useLocation, useMatches, useNavigate, type UIMatch } from 'react-router'\nimport {\n RendraProvider,\n type RendraBreadcrumbItem,\n type RendraLinkProps,\n} from '@/components/rendra-provider'\n\n/**\n * Metadados de rota lidos pela trilha: handle: { crumb: 'Clientes' }. Convenção do\n * react-router (`handle`), que não existe em outros roteadores; por isso fica aqui, na\n * ponte, e não em src/components/app-shell/header.tsx.\n */\nexport interface RouteHandle {\n crumb?: string | ((params: Record<string, string | undefined>) => string)\n}\n\n/** `Link` do react-router com a interface do `RendraLinkProps` (repassa o resto). */\nfunction RouterLink({ to, children, ...rest }: RendraLinkProps) {\n return (\n <Link to={to} {...rest}>\n {children}\n </Link>\n )\n}\n\n/** Caminho atual do react-router. */\nfunction useCurrentPathFromRouter(): string {\n return useLocation().pathname\n}\n\n/** Trilha a partir de `useMatches`/`handle.crumb`, a mesma convenção do header. */\nfunction useBreadcrumbsFromMatches(): RendraBreadcrumbItem[] {\n const matches = useMatches() as UIMatch<unknown, RouteHandle | undefined>[]\n return matches\n .filter((m) => m.handle?.crumb)\n .map((m, i, all) => {\n const crumb = m.handle!.crumb!\n return {\n label: typeof crumb === 'function' ? crumb(m.params) : crumb,\n to: i < all.length - 1 ? m.pathname : undefined,\n }\n })\n}\n\n/**\n * Liga o `RendraProvider` ao `react-router` do boilerplate: link, caminho atual, navegar\n * e a trilha. É o único lugar do app que sabe que o roteador é o `react-router`; os\n * componentes de `src/components/ui` e o `AppShell` não importam `react-router` diretamente.\n * Sem `children`, renderiza `<Outlet />`, para ser usado como elemento raiz das rotas.\n */\nexport function RendraRouterBridge({ children }: { children?: ReactNode } = {}) {\n const navigate = useNavigate()\n const go = useCallback(\n (to: string, options?: { replace?: boolean }) => navigate(to, options),\n [navigate],\n )\n const goBack = useCallback(() => navigate(-1), [navigate])\n return (\n <RendraProvider\n linkComponent={RouterLink}\n useCurrentPath={useCurrentPathFromRouter}\n navigate={go}\n goBack={goBack}\n useBreadcrumbs={useBreadcrumbsFromMatches}\n >\n {children ?? <Outlet />}\n </RendraProvider>\n )\n}\n"],"mappings":";;;;;;;AAkBA,SAAS,WAAW,EAAE,IAAI,UAAU,GAAG,QAAyB;CAC9D,OACE,oBAAC,MAAD;EAAU;EAAI,GAAI;EACf;CACG,CAAA;AAEV;;AAGA,SAAS,2BAAmC;CAC1C,OAAO,YAAY,CAAC,CAAC;AACvB;;AAGA,SAAS,4BAAoD;CAE3D,OADgB,WACT,CAAA,CACJ,QAAQ,MAAM,EAAE,QAAQ,KAAK,CAAC,CAC9B,KAAK,GAAG,GAAG,QAAQ;EAClB,MAAM,QAAQ,EAAE,OAAQ;EACxB,OAAO;GACL,OAAO,OAAO,UAAU,aAAa,MAAM,EAAE,MAAM,IAAI;GACvD,IAAI,IAAI,IAAI,SAAS,IAAI,EAAE,WAAW,KAAA;EACxC;CACF,CAAC;AACL;;;;;;;AAQA,SAAgB,mBAAmB,EAAE,aAAuC,CAAC,GAAG;CAC9E,MAAM,WAAW,YAAY;CAC7B,MAAM,KAAK,aACR,IAAY,YAAoC,SAAS,IAAI,OAAO,GACrE,CAAC,QAAQ,CACX;CACA,MAAM,SAAS,kBAAkB,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC;CACzD,OACE,oBAAC,gBAAD;EACE,eAAe;EACf,gBAAgB;EAChB,UAAU;EACF;EACR,gBAAgB;EAEf,UAAA,YAAY,oBAAC,QAAD,CAAS,CAAA;CACR,CAAA;AAEpB"}
|