@proef/common-ui 2.2.0 → 2.2.2
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/dist/esm/index.js +26 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +1 -1
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
- package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +111 -54
- package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
- package/dist/esm/v1/components/tables/Table/ColumnFilter.js +14 -14
- package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -1
- package/dist/esm/v1/components/tables/Table/Table.js +9 -8
- package/dist/esm/v1/components/tables/Table/Table.js.map +1 -1
- package/dist/esm/v1/foundations/icons/icons.js +49 -0
- package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
- package/dist/esm/v1/primitives/display/RichText/RichText.js +12 -0
- package/dist/esm/v1/primitives/display/RichText/RichText.js.map +1 -0
- package/dist/esm/v1/primitives/display/RichText/utils.js +56 -0
- package/dist/esm/v1/primitives/display/RichText/utils.js.map +1 -0
- package/dist/esm/v1/primitives/forms/RichTextEditor/RichTextEditor.js +253 -0
- package/dist/esm/v1/primitives/forms/RichTextEditor/RichTextEditor.js.map +1 -0
- package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +45 -11
- package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
- package/dist/style.components.css +1 -1
- package/dist/style.css +1 -1
- package/dist/style.layered.css +1 -1
- package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +31 -5
- package/dist/types/v1/components/tables/Table/Table.d.ts +9 -1
- package/dist/types/v1/foundations/icons/icons.d.ts +20 -0
- package/dist/types/v1/index.d.ts +8 -3
- package/dist/types/v1/primitives/display/RichText/RichText.d.ts +17 -0
- package/dist/types/v1/primitives/display/RichText/utils.d.ts +22 -0
- package/dist/types/v1/primitives/forms/RichTextEditor/RichTextEditor.d.ts +64 -0
- package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +39 -8
- package/package.json +6 -1
|
@@ -99,6 +99,45 @@ function PanelOpenIcon(props) {
|
|
|
99
99
|
function ListIcon(props) {
|
|
100
100
|
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M9 6.5h11M9 12h11M9 17.5h11M4.5 6.5h.01M4.5 12h.01M4.5 17.5h.01" }) });
|
|
101
101
|
}
|
|
102
|
+
function BoldIcon(props) {
|
|
103
|
+
return /* @__PURE__ */ jsx(Svg, { strokeWidth: 2.2, ...props, children: /* @__PURE__ */ jsx("path", { d: "M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z" }) });
|
|
104
|
+
}
|
|
105
|
+
function ItalicIcon(props) {
|
|
106
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M10 5h8M6 19h8M14.5 5l-5 14" }) });
|
|
107
|
+
}
|
|
108
|
+
function UnderlineIcon(props) {
|
|
109
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M7 4.5v6a5 5 0 0 0 10 0v-6M5.5 20h13" }) });
|
|
110
|
+
}
|
|
111
|
+
function StrikethroughIcon(props) {
|
|
112
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M4.5 12h15M16.5 6.5A4.5 3.5 0 0 0 12 5c-2.5 0-4.5 1.3-4.5 3.2 0 1.4 1 2.4 2.7 2.9M8 17.3A5 3.6 0 0 0 12 19c2.6 0 4.6-1.4 4.6-3.3 0-.8-.3-1.5-.9-2" }) });
|
|
113
|
+
}
|
|
114
|
+
function ListBulletIcon(props) {
|
|
115
|
+
return /* @__PURE__ */ jsxs(Svg, { ...props, children: [
|
|
116
|
+
/* @__PURE__ */ jsx("path", { d: "M9.5 6.5h10M9.5 12h10M9.5 17.5h10" }),
|
|
117
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "6.5", r: "1", fill: "currentColor" }),
|
|
118
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1", fill: "currentColor" }),
|
|
119
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "17.5", r: "1", fill: "currentColor" })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
function ListNumberedIcon(props) {
|
|
123
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M10 6.5h10M10 12h10M10 17.5h10M4 5.5l1.3-.8v4M3.8 15.2c.3-.6.9-.9 1.5-.9.8 0 1.4.5 1.4 1.2 0 1-1.6 1.6-2.9 2.8h3" }) });
|
|
124
|
+
}
|
|
125
|
+
function QuoteIcon(props) {
|
|
126
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M5 18V12.5C5 9 6.5 7 9.5 6M14 18v-5.5C14 9 15.5 7 18.5 6M5 12.5h4.5V18H5M14 12.5h4.5V18H14" }) });
|
|
127
|
+
}
|
|
128
|
+
function ImageIcon(props) {
|
|
129
|
+
return /* @__PURE__ */ jsxs(Svg, { ...props, children: [
|
|
130
|
+
/* @__PURE__ */ jsx("rect", { x: "3.5", y: "4.5", width: "17", height: "15", rx: "2" }),
|
|
131
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "10", r: "1.6" }),
|
|
132
|
+
/* @__PURE__ */ jsx("path", { d: "M20.5 15.5l-4.5-4.5-8.5 8.5" })
|
|
133
|
+
] });
|
|
134
|
+
}
|
|
135
|
+
function MaximizeIcon(props) {
|
|
136
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M14 4.5h5.5V10M10 19.5H4.5V14M19.5 4.5L13.5 10.5M4.5 19.5l6-6" }) });
|
|
137
|
+
}
|
|
138
|
+
function MinimizeIcon(props) {
|
|
139
|
+
return /* @__PURE__ */ jsx(Svg, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M19.5 10H14V4.5M4.5 14H10v5.5M14 10l6-6M4 20l6-6" }) });
|
|
140
|
+
}
|
|
102
141
|
function SunIcon(props) {
|
|
103
142
|
return /* @__PURE__ */ jsxs(Svg, { ...props, children: [
|
|
104
143
|
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
|
|
@@ -147,6 +186,7 @@ function SlidersIcon(props) {
|
|
|
147
186
|
] });
|
|
148
187
|
}
|
|
149
188
|
export {
|
|
189
|
+
BoldIcon,
|
|
150
190
|
CheckIcon,
|
|
151
191
|
ChevronDownIcon,
|
|
152
192
|
ChevronLeftIcon,
|
|
@@ -158,21 +198,30 @@ export {
|
|
|
158
198
|
ExternalLinkIcon,
|
|
159
199
|
FilterIcon,
|
|
160
200
|
GlobeIcon,
|
|
201
|
+
ImageIcon,
|
|
161
202
|
InfoIcon,
|
|
203
|
+
ItalicIcon,
|
|
162
204
|
LayersIcon,
|
|
205
|
+
ListBulletIcon,
|
|
163
206
|
ListIcon,
|
|
207
|
+
ListNumberedIcon,
|
|
164
208
|
LockIcon,
|
|
165
209
|
LogOutIcon,
|
|
210
|
+
MaximizeIcon,
|
|
166
211
|
MenuIcon,
|
|
212
|
+
MinimizeIcon,
|
|
167
213
|
MonitorIcon,
|
|
168
214
|
MoonIcon,
|
|
169
215
|
PanelOpenIcon,
|
|
170
216
|
PrefilledIcon,
|
|
171
217
|
QrCodeIcon,
|
|
218
|
+
QuoteIcon,
|
|
172
219
|
SearchIcon,
|
|
173
220
|
SlidersIcon,
|
|
174
221
|
SparklesIcon,
|
|
222
|
+
StrikethroughIcon,
|
|
175
223
|
SunIcon,
|
|
224
|
+
UnderlineIcon,
|
|
176
225
|
WarningIcon
|
|
177
226
|
};
|
|
178
227
|
//# sourceMappingURL=icons.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.js","sources":["../../../../../src/v1/foundations/icons/icons.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/**\r\n * Conjunto de ícones do V1: traço de 1,7 a 1,9 em grelha de 24, cor do texto\r\n * (`currentColor`), sem preenchimento. Decorativos por omissão (`aria-hidden`):\r\n * quem os usa dá o nome acessível ao botão ou etiqueta à volta.\r\n */\r\nexport interface IconProps {\r\n /** Lado em px. */\r\n size?: number\r\n className?: string\r\n /** Espessura do traço. Por omissão 1,8; mais grosso em ícones muito pequenos (ex: o visto de uma caixa). */\r\n strokeWidth?: number\r\n}\r\n\r\nfunction Svg({ size = 16, className, strokeWidth = 1.8, children, fill = 'none' }: IconProps & { children: React.ReactNode; fill?: string }) {\r\n return (\r\n <svg\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill={fill}\r\n stroke={fill === 'none' ? 'currentColor' : 'none'}\r\n strokeWidth={strokeWidth}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden\r\n >\r\n {children}\r\n </svg>\r\n )\r\n}\r\n\r\n/** Lido de um QR code. */\r\nexport function QrCodeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3.5\" y=\"3.5\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <rect x=\"14\" y=\"3.5\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <rect x=\"3.5\" y=\"14\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <path d=\"M14 14h3.3v3.3M20.5 14v.01M14 20.5h.01M17.3 20.5h3.2v-3.2\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Identificado automaticamente (IA). */\r\nexport function SparklesIcon(props: IconProps) {\r\n return (\r\n <Svg {...props} fill=\"currentColor\">\r\n <path d=\"M12 2.5l1.7 4.6 4.6 1.7-4.6 1.7L12 15.1l-1.7-4.6L5.7 8.8l4.6-1.7z\" />\r\n <path d=\"M18.5 13.5l.85 2.35 2.35.85-2.35.85-.85 2.35-.85-2.35-2.35-.85 2.35-.85z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Pré-preenchido a partir de outro registo (ex: linha de um extrato). */\r\nexport function PrefilledIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"4\" y=\"3.5\" width=\"16\" height=\"17\" rx=\"2\" />\r\n <path d=\"M7.5 8.5h9M7.5 12h9M7.5 15.5h5.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Bloqueado: o valor não se pode alterar. */\r\nexport function LockIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"5\" y=\"10.5\" width=\"14\" height=\"10\" rx=\"2\" />\r\n <path d=\"M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3\" />\r\n <circle cx=\"12\" cy=\"15.5\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Mais informação. */\r\nexport function InfoIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" />\r\n <path d=\"M12 11v5.5\" />\r\n <circle cx=\"12\" cy=\"7.8\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function SearchIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\" />\r\n <path d=\"M15.5 15.5L20 20\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function CheckIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4.5 12.5l5 5 10-11\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronDownIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6 9.5l6 6 6-6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronUpIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6 14.5l6-6 6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronLeftIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M14.5 6l-6 6 6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronRightIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9.5 6l6 6-6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Ordenável: ainda sem ordem escolhida. */\r\nexport function ChevronsUpDownIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M7.5 9.5L12 5l4.5 4.5M7.5 14.5L12 19l4.5-4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Filtro (funil). */\r\nexport function FilterIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 5h16l-6.2 7.6v5.6l-3.6 1.8v-7.4z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Fechar, remover (×). */\r\nexport function CloseIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6.5 6.5l11 11M17.5 6.5l-11 11\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Abrir o menu (gaveta em ecrãs pequenos). */\r\nexport function MenuIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 7h16M4 12h16M4 17h16\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Agrupar (camadas). */\r\nexport function LayersIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M12 3.5l8.5 4.5-8.5 4.5L3.5 8z\" />\r\n <path d=\"M3.5 12l8.5 4.5 8.5-4.5M3.5 16l8.5 4.5 8.5-4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Expandir o menu lateral recolhido. */\r\nexport function PanelOpenIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3.5\" y=\"4\" width=\"17\" height=\"16\" rx=\"2\" />\r\n <path d=\"M9 4v16M13 10l2 2-2 2\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Lista de valores. */\r\nexport function ListIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9 6.5h11M9 12h11M9 17.5h11M4.5 6.5h.01M4.5 12h.01M4.5 17.5h.01\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema claro. */\r\nexport function SunIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\r\n <path d=\"M12 2.5v2M12 19.5v2M4.6 4.6L6 6M18 18l1.4 1.4M2.5 12h2M19.5 12h2M4.6 19.4L6 18M18 6l1.4-1.4\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema escuro. */\r\nexport function MoonIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema do sistema. */\r\nexport function MonitorIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"12.5\" rx=\"2\" />\r\n <path d=\"M8.5 20.5h7M12 16.5v4\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Idioma. */\r\nexport function GlobeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Terminar sessão. */\r\nexport function LogOutIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9.5 20.5H6a2 2 0 0 1-2-2v-13a2 2 0 0 1 2-2h3.5M15.5 16.5L20 12l-4.5-4.5M20 12H9.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Abre fora da aplicação (separador novo). */\r\nexport function ExternalLinkIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M14 4h6v6M20 4l-9 9M18 13.5V18a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Atenção (avisos, confirmações perigosas). */\r\nexport function WarningIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M10.3 4.3L2.8 17.5A2 2 0 0 0 4.5 20.5h15a2 2 0 0 0 1.7-3L13.7 4.3a2 2 0 0 0-3.4 0z\" />\r\n <path d=\"M12 9.5v4.5\" />\r\n <circle cx=\"12\" cy=\"17\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Erro. */\r\nexport function ErrorIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" />\r\n <path d=\"M9 9l6 6M15 9l-6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Intervalo (mínimo e máximo). */\r\nexport function SlidersIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 7h10M18 7h2M4 17h2M10 17h10\" />\r\n <circle cx=\"16\" cy=\"7\" r=\"2\" />\r\n <circle cx=\"8\" cy=\"17\" r=\"2\" />\r\n </Svg>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"icons.js","sources":["../../../../../src/v1/foundations/icons/icons.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n/**\r\n * Conjunto de ícones do V1: traço de 1,7 a 1,9 em grelha de 24, cor do texto\r\n * (`currentColor`), sem preenchimento. Decorativos por omissão (`aria-hidden`):\r\n * quem os usa dá o nome acessível ao botão ou etiqueta à volta.\r\n */\r\nexport interface IconProps {\r\n /** Lado em px. */\r\n size?: number\r\n className?: string\r\n /** Espessura do traço. Por omissão 1,8; mais grosso em ícones muito pequenos (ex: o visto de uma caixa). */\r\n strokeWidth?: number\r\n}\r\n\r\nfunction Svg({ size = 16, className, strokeWidth = 1.8, children, fill = 'none' }: IconProps & { children: React.ReactNode; fill?: string }) {\r\n return (\r\n <svg\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill={fill}\r\n stroke={fill === 'none' ? 'currentColor' : 'none'}\r\n strokeWidth={strokeWidth}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n className={className}\r\n aria-hidden\r\n >\r\n {children}\r\n </svg>\r\n )\r\n}\r\n\r\n/** Lido de um QR code. */\r\nexport function QrCodeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3.5\" y=\"3.5\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <rect x=\"14\" y=\"3.5\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <rect x=\"3.5\" y=\"14\" width=\"6.5\" height=\"6.5\" rx=\"1.3\" />\r\n <path d=\"M14 14h3.3v3.3M20.5 14v.01M14 20.5h.01M17.3 20.5h3.2v-3.2\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Identificado automaticamente (IA). */\r\nexport function SparklesIcon(props: IconProps) {\r\n return (\r\n <Svg {...props} fill=\"currentColor\">\r\n <path d=\"M12 2.5l1.7 4.6 4.6 1.7-4.6 1.7L12 15.1l-1.7-4.6L5.7 8.8l4.6-1.7z\" />\r\n <path d=\"M18.5 13.5l.85 2.35 2.35.85-2.35.85-.85 2.35-.85-2.35-2.35-.85 2.35-.85z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Pré-preenchido a partir de outro registo (ex: linha de um extrato). */\r\nexport function PrefilledIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"4\" y=\"3.5\" width=\"16\" height=\"17\" rx=\"2\" />\r\n <path d=\"M7.5 8.5h9M7.5 12h9M7.5 15.5h5.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Bloqueado: o valor não se pode alterar. */\r\nexport function LockIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"5\" y=\"10.5\" width=\"14\" height=\"10\" rx=\"2\" />\r\n <path d=\"M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3\" />\r\n <circle cx=\"12\" cy=\"15.5\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Mais informação. */\r\nexport function InfoIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" />\r\n <path d=\"M12 11v5.5\" />\r\n <circle cx=\"12\" cy=\"7.8\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function SearchIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\" />\r\n <path d=\"M15.5 15.5L20 20\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function CheckIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4.5 12.5l5 5 10-11\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronDownIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6 9.5l6 6 6-6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronUpIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6 14.5l6-6 6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronLeftIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M14.5 6l-6 6 6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\nexport function ChevronRightIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9.5 6l6 6-6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Ordenável: ainda sem ordem escolhida. */\r\nexport function ChevronsUpDownIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M7.5 9.5L12 5l4.5 4.5M7.5 14.5L12 19l4.5-4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Filtro (funil). */\r\nexport function FilterIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 5h16l-6.2 7.6v5.6l-3.6 1.8v-7.4z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Fechar, remover (×). */\r\nexport function CloseIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M6.5 6.5l11 11M17.5 6.5l-11 11\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Abrir o menu (gaveta em ecrãs pequenos). */\r\nexport function MenuIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 7h16M4 12h16M4 17h16\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Agrupar (camadas). */\r\nexport function LayersIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M12 3.5l8.5 4.5-8.5 4.5L3.5 8z\" />\r\n <path d=\"M3.5 12l8.5 4.5 8.5-4.5M3.5 16l8.5 4.5 8.5-4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Expandir o menu lateral recolhido. */\r\nexport function PanelOpenIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3.5\" y=\"4\" width=\"17\" height=\"16\" rx=\"2\" />\r\n <path d=\"M9 4v16M13 10l2 2-2 2\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Lista de valores. */\r\nexport function ListIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9 6.5h11M9 12h11M9 17.5h11M4.5 6.5h.01M4.5 12h.01M4.5 17.5h.01\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Texto: negrito. */\r\nexport function BoldIcon(props: IconProps) {\r\n return (\r\n <Svg strokeWidth={2.2} {...props}>\r\n <path d=\"M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Texto: itálico. */\r\nexport function ItalicIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M10 5h8M6 19h8M14.5 5l-5 14\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Texto: sublinhado. */\r\nexport function UnderlineIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M7 4.5v6a5 5 0 0 0 10 0v-6M5.5 20h13\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Texto: riscado. */\r\nexport function StrikethroughIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4.5 12h15M16.5 6.5A4.5 3.5 0 0 0 12 5c-2.5 0-4.5 1.3-4.5 3.2 0 1.4 1 2.4 2.7 2.9M8 17.3A5 3.6 0 0 0 12 19c2.6 0 4.6-1.4 4.6-3.3 0-.8-.3-1.5-.9-2\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Lista com marcas (no texto). */\r\nexport function ListBulletIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9.5 6.5h10M9.5 12h10M9.5 17.5h10\" />\r\n <circle cx=\"5\" cy=\"6.5\" r=\"1\" fill=\"currentColor\" />\r\n <circle cx=\"5\" cy=\"12\" r=\"1\" fill=\"currentColor\" />\r\n <circle cx=\"5\" cy=\"17.5\" r=\"1\" fill=\"currentColor\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Lista numerada (no texto). */\r\nexport function ListNumberedIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M10 6.5h10M10 12h10M10 17.5h10M4 5.5l1.3-.8v4M3.8 15.2c.3-.6.9-.9 1.5-.9.8 0 1.4.5 1.4 1.2 0 1-1.6 1.6-2.9 2.8h3\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Citação. */\r\nexport function QuoteIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M5 18V12.5C5 9 6.5 7 9.5 6M14 18v-5.5C14 9 15.5 7 18.5 6M5 12.5h4.5V18H5M14 12.5h4.5V18H14\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Imagem. */\r\nexport function ImageIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3.5\" y=\"4.5\" width=\"17\" height=\"15\" rx=\"2\" />\r\n <circle cx=\"9\" cy=\"10\" r=\"1.6\" />\r\n <path d=\"M20.5 15.5l-4.5-4.5-8.5 8.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Abrir em ecrã quase inteiro. */\r\nexport function MaximizeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M14 4.5h5.5V10M10 19.5H4.5V14M19.5 4.5L13.5 10.5M4.5 19.5l6-6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Voltar ao tamanho normal. */\r\nexport function MinimizeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M19.5 10H14V4.5M4.5 14H10v5.5M14 10l6-6M4 20l6-6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema claro. */\r\nexport function SunIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\r\n <path d=\"M12 2.5v2M12 19.5v2M4.6 4.6L6 6M18 18l1.4 1.4M2.5 12h2M19.5 12h2M4.6 19.4L6 18M18 6l1.4-1.4\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema escuro. */\r\nexport function MoonIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Tema do sistema. */\r\nexport function MonitorIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"12.5\" rx=\"2\" />\r\n <path d=\"M8.5 20.5h7M12 16.5v4\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Idioma. */\r\nexport function GlobeIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Terminar sessão. */\r\nexport function LogOutIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M9.5 20.5H6a2 2 0 0 1-2-2v-13a2 2 0 0 1 2-2h3.5M15.5 16.5L20 12l-4.5-4.5M20 12H9.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Abre fora da aplicação (separador novo). */\r\nexport function ExternalLinkIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M14 4h6v6M20 4l-9 9M18 13.5V18a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4.5\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Atenção (avisos, confirmações perigosas). */\r\nexport function WarningIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M10.3 4.3L2.8 17.5A2 2 0 0 0 4.5 20.5h15a2 2 0 0 0 1.7-3L13.7 4.3a2 2 0 0 0-3.4 0z\" />\r\n <path d=\"M12 9.5v4.5\" />\r\n <circle cx=\"12\" cy=\"17\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Erro. */\r\nexport function ErrorIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <circle cx=\"12\" cy=\"12\" r=\"9.5\" />\r\n <path d=\"M9 9l6 6M15 9l-6 6\" />\r\n </Svg>\r\n )\r\n}\r\n\r\n/** Intervalo (mínimo e máximo). */\r\nexport function SlidersIcon(props: IconProps) {\r\n return (\r\n <Svg {...props}>\r\n <path d=\"M4 7h10M18 7h2M4 17h2M10 17h10\" />\r\n <circle cx=\"16\" cy=\"7\" r=\"2\" />\r\n <circle cx=\"8\" cy=\"17\" r=\"2\" />\r\n </Svg>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { sanitizeRichText, richTextContentClasses } from "./utils.js";
|
|
4
|
+
function RichText({ value, className = "", ...props }) {
|
|
5
|
+
const html = useMemo(() => sanitizeRichText(value), [value]);
|
|
6
|
+
return /* @__PURE__ */ jsx("div", { className: `${richTextContentClasses} ${className}`, dangerouslySetInnerHTML: { __html: html }, ...props });
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
RichText,
|
|
10
|
+
RichText as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=RichText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichText.js","sources":["../../../../../../src/v1/primitives/display/RichText/RichText.tsx"],"sourcesContent":["import React, { useMemo } from 'react'\r\nimport { richTextContentClasses, sanitizeRichText } from './utils'\r\n\r\nexport interface RichTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'dangerouslySetInnerHTML'> {\r\n /** O texto: HTML do RichTextEditor ou texto simples (antigo, sem formatação). */\r\n value: string | null | undefined\r\n}\r\n\r\n/**\r\n * Mostra um texto com formatação (negrito, listas, citações, ligações, imagens) escrito no\r\n * [RichTextEditor](?path=/docs/v1-primitives-forms-richtexteditor--docs).\r\n *\r\n * O HTML é sempre limpo antes de entrar na página: só ficam as marcas de formatação (sem\r\n * scripts, estilos nem eventos) e as ligações abrem noutro separador. Texto sem marcas\r\n * (guardado antes do editor) aparece com as mudanças de linha.\r\n *\r\n * Não tem cor própria: herda a de quem o põe (ex: branco numa bolha navy de uma conversa).\r\n */\r\nexport function RichText({ value, className = '', ...props }: RichTextProps) {\r\n const html = useMemo(() => sanitizeRichText(value), [value])\r\n return <div className={`${richTextContentClasses} ${className}`} dangerouslySetInnerHTML={{ __html: html }} {...props} />\r\n}\r\n\r\nexport default RichText\r\n"],"names":[],"mappings":";;;AAkBO,SAAS,SAAS,EAAE,OAAO,YAAY,IAAI,GAAG,SAAwB;AAC3E,QAAM,OAAO,QAAQ,MAAM,iBAAiB,KAAK,GAAG,CAAC,KAAK,CAAC;AAC3D,SAAO,oBAAC,OAAA,EAAI,WAAW,GAAG,sBAAsB,IAAI,SAAS,IAAI,yBAAyB,EAAE,QAAQ,KAAA,GAAS,GAAG,OAAO;AACzH;"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import DOMPurify from "dompurify";
|
|
2
|
+
const richTextContentClasses = "break-words [&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-5 [&_li>p]:my-0 [&_blockquote]:my-1.5 [&_blockquote]:border-l-2 [&_blockquote]:border-current/30 [&_blockquote]:pl-3 [&_blockquote]:opacity-80 [&_a]:underline [&_a]:underline-offset-2 [&_u]:underline [&_s]:line-through [&_code]:rounded [&_code]:bg-current/10 [&_code]:px-1 [&_code]:text-[0.92em] [&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg";
|
|
3
|
+
const ALLOWED_TAGS = ["p", "br", "strong", "b", "em", "i", "u", "s", "strike", "del", "ul", "ol", "li", "blockquote", "a", "img", "h1", "h2", "h3", "code", "pre", "hr"];
|
|
4
|
+
const ALLOWED_ATTR = ["href", "src", "alt", "title", "target", "rel", "start"];
|
|
5
|
+
let purifier = null;
|
|
6
|
+
function getPurifier() {
|
|
7
|
+
if (purifier) return purifier;
|
|
8
|
+
purifier = DOMPurify(window);
|
|
9
|
+
purifier.addHook("afterSanitizeAttributes", (node) => {
|
|
10
|
+
if (node.tagName === "A" && node.getAttribute("href")) {
|
|
11
|
+
node.setAttribute("target", "_blank");
|
|
12
|
+
node.setAttribute("rel", "noopener noreferrer");
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return purifier;
|
|
16
|
+
}
|
|
17
|
+
function isRichText(value) {
|
|
18
|
+
return !!value && /<\/?[a-z][\s\S]*?>/i.test(value);
|
|
19
|
+
}
|
|
20
|
+
function escapeHtml(text) {
|
|
21
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
22
|
+
}
|
|
23
|
+
function textToRichText(text) {
|
|
24
|
+
if (!text) return "";
|
|
25
|
+
return text.split(/\r?\n/).map((line) => `<p>${escapeHtml(line)}</p>`).join("");
|
|
26
|
+
}
|
|
27
|
+
function sanitizeRichText(value) {
|
|
28
|
+
if (!value) return "";
|
|
29
|
+
const html = isRichText(value) ? value : textToRichText(value);
|
|
30
|
+
return getPurifier().sanitize(html, { ALLOWED_TAGS, ALLOWED_ATTR });
|
|
31
|
+
}
|
|
32
|
+
function richTextToPlain(value) {
|
|
33
|
+
if (!value) return "";
|
|
34
|
+
if (!isRichText(value)) return value;
|
|
35
|
+
const doc = new DOMParser().parseFromString(sanitizeRichText(value), "text/html");
|
|
36
|
+
doc.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
|
|
37
|
+
doc.querySelectorAll("p, li, blockquote, h1, h2, h3, pre").forEach((el) => {
|
|
38
|
+
if (el.tagName !== "P" && el.querySelector(":scope > p")) return;
|
|
39
|
+
el.append("\n");
|
|
40
|
+
});
|
|
41
|
+
return (doc.body.textContent ?? "").replace(/\n{3,}/g, "\n\n").trim();
|
|
42
|
+
}
|
|
43
|
+
function richTextIsEmpty(value) {
|
|
44
|
+
if (!value) return true;
|
|
45
|
+
if (!isRichText(value)) return !value.trim();
|
|
46
|
+
return !richTextToPlain(value) && !/<img\b/i.test(value);
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
isRichText,
|
|
50
|
+
richTextContentClasses,
|
|
51
|
+
richTextIsEmpty,
|
|
52
|
+
richTextToPlain,
|
|
53
|
+
sanitizeRichText,
|
|
54
|
+
textToRichText
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../../../../src/v1/primitives/display/RichText/utils.ts"],"sourcesContent":["import DOMPurify, { type DOMPurify as Purifier } from 'dompurify'\r\n\r\n/**\r\n * Texto com formatação (HTML do [RichTextEditor](?path=/docs/v1-primitives-forms-richtexteditor--docs)):\r\n * as classes do conteúdo e as funções para o guardar, ler e mostrar em segurança.\r\n */\r\n\r\n/**\r\n * Aspeto do conteúdo (listas, citações, ligações, imagens). Sem cor de texto: herda a de quem o põe\r\n * (ex: branco numa bolha navy de conversa).\r\n */\r\nexport const richTextContentClasses =\r\n 'break-words [&_p]:my-0 [&_p+p]:mt-1.5 ' +\r\n '[&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-5 [&_li>p]:my-0 ' +\r\n '[&_blockquote]:my-1.5 [&_blockquote]:border-l-2 [&_blockquote]:border-current/30 [&_blockquote]:pl-3 [&_blockquote]:opacity-80 ' +\r\n '[&_a]:underline [&_a]:underline-offset-2 [&_u]:underline [&_s]:line-through ' +\r\n '[&_code]:rounded [&_code]:bg-current/10 [&_code]:px-1 [&_code]:text-[0.92em] ' +\r\n '[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg'\r\n\r\n// Só o que o editor produz. Tudo o resto (script, style, iframe, on*, javascript:…) sai.\r\nconst ALLOWED_TAGS = ['p', 'br', 'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'del', 'ul', 'ol', 'li', 'blockquote', 'a', 'img', 'h1', 'h2', 'h3', 'code', 'pre', 'hr']\r\nconst ALLOWED_ATTR = ['href', 'src', 'alt', 'title', 'target', 'rel', 'start']\r\n\r\nlet purifier: Purifier | null = null\r\n\r\n/** Instância própria do DOMPurify: o hook das ligações não mexe no DOMPurify global da app. */\r\nfunction getPurifier(): Purifier {\r\n if (purifier) return purifier\r\n purifier = DOMPurify(window)\r\n purifier.addHook('afterSanitizeAttributes', (node) => {\r\n // Ligações abrem noutro separador e sem acesso a esta janela.\r\n if (node.tagName === 'A' && node.getAttribute('href')) {\r\n node.setAttribute('target', '_blank')\r\n node.setAttribute('rel', 'noopener noreferrer')\r\n }\r\n })\r\n return purifier\r\n}\r\n\r\n/** Parece HTML (tem pelo menos uma marca)? Textos antigos, guardados sem formatação, não. */\r\nexport function isRichText(value: string | null | undefined): boolean {\r\n return !!value && /<\\/?[a-z][\\s\\S]*?>/i.test(value)\r\n}\r\n\r\nfunction escapeHtml(text: string): string {\r\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\r\n}\r\n\r\n/** Texto simples → HTML: um parágrafo por linha (as linhas em branco ficam). */\r\nexport function textToRichText(text: string): string {\r\n if (!text) return ''\r\n return text\r\n .split(/\\r?\\n/)\r\n .map((line) => `<p>${escapeHtml(line)}</p>`)\r\n .join('')\r\n}\r\n\r\n/**\r\n * HTML seguro para pôr na página: só as marcas de formatação, sem scripts nem atributos de eventos.\r\n * Texto sem marcas é convertido (uma linha, um parágrafo). Usar sempre antes de `dangerouslySetInnerHTML`.\r\n */\r\nexport function sanitizeRichText(value: string | null | undefined): string {\r\n if (!value) return ''\r\n const html = isRichText(value) ? value : textToRichText(value)\r\n return getPurifier().sanitize(html, { ALLOWED_TAGS, ALLOWED_ATTR })\r\n}\r\n\r\n/** Só o texto (ex: pré-visualização numa lista, notificação, pesquisa). Parágrafos e itens viram linhas. */\r\nexport function richTextToPlain(value: string | null | undefined): string {\r\n if (!value) return ''\r\n if (!isRichText(value)) return value\r\n const doc = new DOMParser().parseFromString(sanitizeRichText(value), 'text/html')\r\n doc.querySelectorAll('br').forEach((br) => br.replaceWith('\\n'))\r\n // Um item ou citação com parágrafos lá dentro já muda de linha pelo parágrafo.\r\n doc.querySelectorAll('p, li, blockquote, h1, h2, h3, pre').forEach((el) => {\r\n if (el.tagName !== 'P' && el.querySelector(':scope > p')) return\r\n el.append('\\n')\r\n })\r\n return (doc.body.textContent ?? '').replace(/\\n{3,}/g, '\\n\\n').trim()\r\n}\r\n\r\n/** Sem texto nem imagens (ex: `<p></p>` de um editor vazio)? Para validar \"obrigatório\". */\r\nexport function richTextIsEmpty(value: string | null | undefined): boolean {\r\n if (!value) return true\r\n if (!isRichText(value)) return !value.trim()\r\n return !richTextToPlain(value) && !/<img\\b/i.test(value)\r\n}\r\n"],"names":[],"mappings":";AAWO,MAAM,yBACX;AAQF,MAAM,eAAe,CAAC,KAAK,MAAM,UAAU,KAAK,MAAM,KAAK,KAAK,KAAK,UAAU,OAAO,MAAM,MAAM,MAAM,cAAc,KAAK,OAAO,MAAM,MAAM,MAAM,QAAQ,OAAO,IAAI;AACvK,MAAM,eAAe,CAAC,QAAQ,OAAO,OAAO,SAAS,UAAU,OAAO,OAAO;AAE7E,IAAI,WAA4B;AAGhC,SAAS,cAAwB;AAC/B,MAAI,SAAU,QAAO;AACrB,aAAW,UAAU,MAAM;AAC3B,WAAS,QAAQ,2BAA2B,CAAC,SAAS;AAEpD,QAAI,KAAK,YAAY,OAAO,KAAK,aAAa,MAAM,GAAG;AACrD,WAAK,aAAa,UAAU,QAAQ;AACpC,WAAK,aAAa,OAAO,qBAAqB;AAAA,IAChD;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGO,SAAS,WAAW,OAA2C;AACpE,SAAO,CAAC,CAAC,SAAS,sBAAsB,KAAK,KAAK;AACpD;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KAAK,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ;AACvG;AAGO,SAAS,eAAe,MAAsB;AACnD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KACJ,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,MAAM,WAAW,IAAI,CAAC,MAAM,EAC1C,KAAK,EAAE;AACZ;AAMO,SAAS,iBAAiB,OAA0C;AACzE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,WAAW,KAAK,IAAI,QAAQ,eAAe,KAAK;AAC7D,SAAO,cAAc,SAAS,MAAM,EAAE,cAAc,cAAc;AACpE;AAGO,SAAS,gBAAgB,OAA0C;AACxE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,WAAW,KAAK,EAAG,QAAO;AAC/B,QAAM,MAAM,IAAI,UAAA,EAAY,gBAAgB,iBAAiB,KAAK,GAAG,WAAW;AAChF,MAAI,iBAAiB,IAAI,EAAE,QAAQ,CAAC,OAAO,GAAG,YAAY,IAAI,CAAC;AAE/D,MAAI,iBAAiB,oCAAoC,EAAE,QAAQ,CAAC,OAAO;AACzE,QAAI,GAAG,YAAY,OAAO,GAAG,cAAc,YAAY,EAAG;AAC1D,OAAG,OAAO,IAAI;AAAA,EAChB,CAAC;AACD,UAAQ,IAAI,KAAK,eAAe,IAAI,QAAQ,WAAW,MAAM,EAAE,KAAA;AACjE;AAGO,SAAS,gBAAgB,OAA2C;AACzE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,WAAW,KAAK,EAAG,QAAO,CAAC,MAAM,KAAA;AACtC,SAAO,CAAC,gBAAgB,KAAK,KAAK,CAAC,UAAU,KAAK,KAAK;AACzD;"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useCallback, useEffect } from "react";
|
|
3
|
+
import { useEditor, Extension, useEditorState, EditorContent } from "@tiptap/react";
|
|
4
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
5
|
+
import Image from "@tiptap/extension-image";
|
|
6
|
+
import { useField } from "../field.js";
|
|
7
|
+
import { richTextContentClasses, isRichText, textToRichText, richTextIsEmpty } from "../../display/RichText/utils.js";
|
|
8
|
+
import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, ListBulletIcon, ListNumberedIcon, QuoteIcon, ImageIcon, MinimizeIcon, MaximizeIcon } from "../../../foundations/icons/icons.js";
|
|
9
|
+
const DEFAULT_LABELS = {
|
|
10
|
+
bold: "Negrito (Ctrl+B)",
|
|
11
|
+
italic: "Itálico (Ctrl+I)",
|
|
12
|
+
underline: "Sublinhado (Ctrl+U)",
|
|
13
|
+
strike: "Riscado",
|
|
14
|
+
bulletList: "Lista com marcas",
|
|
15
|
+
orderedList: "Lista numerada",
|
|
16
|
+
quote: "Citação",
|
|
17
|
+
image: "Inserir imagem",
|
|
18
|
+
maximize: "Maximizar",
|
|
19
|
+
restore: "Restaurar"
|
|
20
|
+
};
|
|
21
|
+
function imageFiles(data) {
|
|
22
|
+
return Array.from(data?.files ?? []).filter((f) => f.type.startsWith("image/"));
|
|
23
|
+
}
|
|
24
|
+
function editorValue(editor) {
|
|
25
|
+
const html = editor.getHTML();
|
|
26
|
+
return richTextIsEmpty(html) ? "" : html;
|
|
27
|
+
}
|
|
28
|
+
function ToolbarButton({ label, active, disabled, onClick, children }) {
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
"button",
|
|
31
|
+
{
|
|
32
|
+
type: "button",
|
|
33
|
+
"aria-label": label,
|
|
34
|
+
title: label,
|
|
35
|
+
"aria-pressed": active === void 0 ? void 0 : active,
|
|
36
|
+
disabled,
|
|
37
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
38
|
+
onClick,
|
|
39
|
+
className: "inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 " + (active ? "bg-primary/10 text-primary dark:bg-slate-700 dark:text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground dark:hover:bg-slate-800"),
|
|
40
|
+
children
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const Separator = () => /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-1 h-4 w-px bg-border" });
|
|
45
|
+
function RichTextEditor({
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
placeholder,
|
|
49
|
+
invalid,
|
|
50
|
+
disabled,
|
|
51
|
+
minHeight = 80,
|
|
52
|
+
maxHeight,
|
|
53
|
+
onUploadImage,
|
|
54
|
+
maximizable,
|
|
55
|
+
onSubmit,
|
|
56
|
+
submitOnEnter,
|
|
57
|
+
autoFocus,
|
|
58
|
+
"aria-label": ariaLabel,
|
|
59
|
+
id,
|
|
60
|
+
className = "",
|
|
61
|
+
labels: labelsProp
|
|
62
|
+
}) {
|
|
63
|
+
const labels = { ...DEFAULT_LABELS, ...labelsProp };
|
|
64
|
+
const field = useField();
|
|
65
|
+
const fileInputRef = useRef(null);
|
|
66
|
+
const [maximized, setMaximized] = useState(false);
|
|
67
|
+
const onChangeRef = useRef(onChange);
|
|
68
|
+
onChangeRef.current = onChange;
|
|
69
|
+
const uploadRef = useRef(onUploadImage);
|
|
70
|
+
uploadRef.current = onUploadImage;
|
|
71
|
+
const submitRef = useRef({ onSubmit, submitOnEnter });
|
|
72
|
+
submitRef.current = { onSubmit, submitOnEnter };
|
|
73
|
+
const emitted = useRef(null);
|
|
74
|
+
const editorRef = useRef(null);
|
|
75
|
+
const insertImages = useCallback(async (files) => {
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
try {
|
|
78
|
+
const src = await uploadRef.current(file);
|
|
79
|
+
editorRef.current?.chain().focus().setImage({ src }).run();
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
const editor = useEditor({
|
|
85
|
+
extensions: [
|
|
86
|
+
StarterKit.configure({
|
|
87
|
+
heading: false,
|
|
88
|
+
codeBlock: false,
|
|
89
|
+
horizontalRule: false,
|
|
90
|
+
link: { openOnClick: false, autolink: true, defaultProtocol: "https" }
|
|
91
|
+
}),
|
|
92
|
+
Image.configure({ inline: false, allowBase64: true }),
|
|
93
|
+
Extension.create({
|
|
94
|
+
name: "submitShortcut",
|
|
95
|
+
addKeyboardShortcuts() {
|
|
96
|
+
const submit = () => {
|
|
97
|
+
const { onSubmit: send } = submitRef.current;
|
|
98
|
+
if (!send) return false;
|
|
99
|
+
send();
|
|
100
|
+
return true;
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
"Mod-Enter": submit,
|
|
104
|
+
Enter: ({ editor: ed }) => {
|
|
105
|
+
if (!submitRef.current.submitOnEnter) return false;
|
|
106
|
+
if (ed.isActive("bulletList") || ed.isActive("orderedList")) return false;
|
|
107
|
+
return submit();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
],
|
|
113
|
+
content: isRichText(value) ? value : textToRichText(value),
|
|
114
|
+
editable: !disabled,
|
|
115
|
+
autofocus: autoFocus ? "end" : false,
|
|
116
|
+
onUpdate: ({ editor: ed }) => {
|
|
117
|
+
const html = editorValue(ed);
|
|
118
|
+
emitted.current = html;
|
|
119
|
+
onChangeRef.current(html);
|
|
120
|
+
},
|
|
121
|
+
editorProps: {
|
|
122
|
+
attributes: {
|
|
123
|
+
class: `px-2.5 py-2 text-control leading-normal text-foreground outline-none ${richTextContentClasses}`,
|
|
124
|
+
role: "textbox",
|
|
125
|
+
"aria-multiline": "true"
|
|
126
|
+
},
|
|
127
|
+
// Colar/largar imagens só onde há sítio para as guardar.
|
|
128
|
+
handlePaste: (_view, event) => {
|
|
129
|
+
if (!uploadRef.current) return false;
|
|
130
|
+
const files = imageFiles(event.clipboardData);
|
|
131
|
+
if (!files.length) return false;
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
event.stopPropagation();
|
|
134
|
+
void insertImages(files);
|
|
135
|
+
return true;
|
|
136
|
+
},
|
|
137
|
+
handleDrop: (_view, event) => {
|
|
138
|
+
if (!uploadRef.current) return false;
|
|
139
|
+
const files = imageFiles(event.dataTransfer);
|
|
140
|
+
if (!files.length) return false;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
event.stopPropagation();
|
|
143
|
+
void insertImages(files);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
editorRef.current = editor;
|
|
149
|
+
const state = useEditorState({
|
|
150
|
+
editor,
|
|
151
|
+
selector: ({ editor: ed }) => ed ? {
|
|
152
|
+
empty: ed.isEmpty,
|
|
153
|
+
bold: ed.isActive("bold"),
|
|
154
|
+
italic: ed.isActive("italic"),
|
|
155
|
+
underline: ed.isActive("underline"),
|
|
156
|
+
strike: ed.isActive("strike"),
|
|
157
|
+
bulletList: ed.isActive("bulletList"),
|
|
158
|
+
orderedList: ed.isActive("orderedList"),
|
|
159
|
+
quote: ed.isActive("blockquote")
|
|
160
|
+
} : null
|
|
161
|
+
});
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!editor || value === emitted.current) return;
|
|
164
|
+
if (!value && editor.isEmpty) return;
|
|
165
|
+
editor.commands.setContent(isRichText(value) ? value : textToRichText(value), { emitUpdate: false });
|
|
166
|
+
emitted.current = value;
|
|
167
|
+
}, [value, editor]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
editor?.setEditable(!disabled);
|
|
170
|
+
}, [editor, disabled]);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!editor) return;
|
|
173
|
+
const el = editor.view.dom;
|
|
174
|
+
const set = (name, v) => v ? el.setAttribute(name, v) : el.removeAttribute(name);
|
|
175
|
+
set("id", id ?? field.id);
|
|
176
|
+
set("aria-label", ariaLabel);
|
|
177
|
+
set("aria-invalid", invalid ? "true" : void 0);
|
|
178
|
+
set("aria-required", field.required ? "true" : void 0);
|
|
179
|
+
set("aria-disabled", disabled ? "true" : void 0);
|
|
180
|
+
}, [editor, id, field.id, field.required, ariaLabel, invalid, disabled]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!maximized) return;
|
|
183
|
+
const onKey = (e) => {
|
|
184
|
+
if (e.key === "Escape") {
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
setMaximized(false);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
window.addEventListener("keydown", onKey, true);
|
|
190
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
191
|
+
}, [maximized]);
|
|
192
|
+
if (!editor || !state) return null;
|
|
193
|
+
const run = (fn) => () => fn(editor.chain().focus()).run();
|
|
194
|
+
const writeArea = maximized ? {} : { minHeight, maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight };
|
|
195
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
196
|
+
maximized && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[60] bg-black/40 backdrop-blur-sm", onClick: () => setMaximized(false) }),
|
|
197
|
+
/* @__PURE__ */ jsxs(
|
|
198
|
+
"div",
|
|
199
|
+
{
|
|
200
|
+
className: "overflow-clip rounded-lg border bg-card transition-[border-color,box-shadow] duration-150 dark:bg-slate-900 " + (invalid ? "border-destructive ring-3 ring-destructive/15 " : "border-border focus-within:border-primary/40 focus-within:ring-3 focus-within:ring-primary/10 dark:border-slate-600 ") + (disabled ? "bg-muted opacity-70 " : "") + (maximized ? "fixed inset-4 z-[61] flex flex-col shadow-2xl md:inset-10 " : "w-full min-w-0 ") + className,
|
|
201
|
+
children: [
|
|
202
|
+
/* @__PURE__ */ jsxs("div", { role: "toolbar", "aria-label": "Formatação", className: "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 border-b border-border bg-muted/40 px-1.5 py-1 dark:border-slate-700 dark:bg-slate-800/60", children: [
|
|
203
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.bold, active: state.bold, disabled, onClick: run((c) => c.toggleBold()), children: /* @__PURE__ */ jsx(BoldIcon, { size: 14 }) }),
|
|
204
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.italic, active: state.italic, disabled, onClick: run((c) => c.toggleItalic()), children: /* @__PURE__ */ jsx(ItalicIcon, { size: 14 }) }),
|
|
205
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.underline, active: state.underline, disabled, onClick: run((c) => c.toggleUnderline()), children: /* @__PURE__ */ jsx(UnderlineIcon, { size: 14 }) }),
|
|
206
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.strike, active: state.strike, disabled, onClick: run((c) => c.toggleStrike()), children: /* @__PURE__ */ jsx(StrikethroughIcon, { size: 14 }) }),
|
|
207
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
208
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.bulletList, active: state.bulletList, disabled, onClick: run((c) => c.toggleBulletList()), children: /* @__PURE__ */ jsx(ListBulletIcon, { size: 14 }) }),
|
|
209
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.orderedList, active: state.orderedList, disabled, onClick: run((c) => c.toggleOrderedList()), children: /* @__PURE__ */ jsx(ListNumberedIcon, { size: 14 }) }),
|
|
210
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.quote, active: state.quote, disabled, onClick: run((c) => c.toggleBlockquote()), children: /* @__PURE__ */ jsx(QuoteIcon, { size: 14 }) }),
|
|
211
|
+
onUploadImage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
213
|
+
/* @__PURE__ */ jsx(ToolbarButton, { label: labels.image, disabled, onClick: () => fileInputRef.current?.click(), children: /* @__PURE__ */ jsx(ImageIcon, { size: 14 }) }),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"input",
|
|
216
|
+
{
|
|
217
|
+
ref: fileInputRef,
|
|
218
|
+
type: "file",
|
|
219
|
+
accept: "image/*",
|
|
220
|
+
multiple: true,
|
|
221
|
+
hidden: true,
|
|
222
|
+
onChange: (e) => {
|
|
223
|
+
const files = Array.from(e.target.files ?? []);
|
|
224
|
+
e.target.value = "";
|
|
225
|
+
void insertImages(files);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
] }),
|
|
230
|
+
maximizable && /* @__PURE__ */ jsx("span", { className: "ml-auto", children: /* @__PURE__ */ jsx(ToolbarButton, { label: maximized ? labels.restore : labels.maximize, onClick: () => setMaximized((m) => !m), children: maximized ? /* @__PURE__ */ jsx(MinimizeIcon, { size: 14 }) : /* @__PURE__ */ jsx(MaximizeIcon, { size: 14 }) }) })
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsxs(
|
|
233
|
+
"div",
|
|
234
|
+
{
|
|
235
|
+
className: `relative cursor-text ${maximized ? "flex-1 overflow-y-auto" : "overflow-y-auto"} [&>div]:h-full [&_.ProseMirror]:min-h-full`,
|
|
236
|
+
style: writeArea,
|
|
237
|
+
onClick: () => !disabled && !editor.isFocused && editor.chain().focus().run(),
|
|
238
|
+
children: [
|
|
239
|
+
state.empty && placeholder && /* @__PURE__ */ jsx("p", { "aria-hidden": true, className: "pointer-events-none absolute top-2 left-2.5 m-0 text-control text-muted-foreground/70 select-none", children: placeholder }),
|
|
240
|
+
/* @__PURE__ */ jsx(EditorContent, { editor, className: "h-full" })
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
] });
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
RichTextEditor,
|
|
251
|
+
RichTextEditor as default
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=RichTextEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.js","sources":["../../../../../../src/v1/primitives/forms/RichTextEditor/RichTextEditor.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react'\r\nimport { EditorContent, Extension, useEditor, useEditorState, type Editor } from '@tiptap/react'\r\nimport StarterKit from '@tiptap/starter-kit'\r\nimport Image from '@tiptap/extension-image'\r\nimport { useField } from '../field'\r\nimport { isRichText, richTextContentClasses, richTextIsEmpty, textToRichText } from '../../display/RichText/utils'\r\nimport {\r\n BoldIcon,\r\n ItalicIcon,\r\n UnderlineIcon,\r\n StrikethroughIcon,\r\n ListBulletIcon,\r\n ListNumberedIcon,\r\n QuoteIcon,\r\n ImageIcon,\r\n MaximizeIcon,\r\n MinimizeIcon,\r\n} from '../../../foundations/icons/icons'\r\n\r\nexport interface RichTextEditorLabels {\r\n bold: string\r\n italic: string\r\n underline: string\r\n strike: string\r\n bulletList: string\r\n orderedList: string\r\n quote: string\r\n image: string\r\n maximize: string\r\n restore: string\r\n}\r\n\r\nconst DEFAULT_LABELS: RichTextEditorLabels = {\r\n bold: 'Negrito (Ctrl+B)',\r\n italic: 'Itálico (Ctrl+I)',\r\n underline: 'Sublinhado (Ctrl+U)',\r\n strike: 'Riscado',\r\n bulletList: 'Lista com marcas',\r\n orderedList: 'Lista numerada',\r\n quote: 'Citação',\r\n image: 'Inserir imagem',\r\n maximize: 'Maximizar',\r\n restore: 'Restaurar',\r\n}\r\n\r\nexport interface RichTextEditorProps {\r\n /** O texto, em HTML. Texto simples (antigo, sem formatação) também serve: cada linha vira um parágrafo. */\r\n value: string\r\n /** Recebe o HTML a cada alteração; `''` quando fica vazio (para validar \"obrigatório\" sem olhar para `<p></p>`). */\r\n onChange: (html: string) => void\r\n placeholder?: string\r\n /** Marca o campo a vermelho (ex: obrigatório por preencher ao submeter). */\r\n invalid?: boolean\r\n disabled?: boolean\r\n /** Altura mínima da área de escrita, em px. Por omissão 80. */\r\n minHeight?: number\r\n /** Altura máxima (px ou CSS, ex: `'50vh'`): cresce com o texto até aqui e depois faz scroll por dentro. Sem isto, cresce sempre. */\r\n maxHeight?: number | string\r\n /**\r\n * Onde guardar uma imagem (escolhida na barra, colada ou largada); devolve o `src` a pôr no texto.\r\n * Sem isto, o editor não aceita imagens.\r\n */\r\n onUploadImage?: (file: File) => Promise<string>\r\n /** Mostra na barra o botão para escrever com o editor em ecrã quase inteiro (Esc volta). */\r\n maximizable?: boolean\r\n /**\r\n * Enviar com o teclado (ex: uma conversa): Ctrl+Enter chama-o sempre. Com `submitOnEnter`,\r\n * também o Enter — exceto dentro de uma lista, onde o Enter cria o item seguinte. Shift+Enter muda de linha.\r\n */\r\n onSubmit?: () => void\r\n submitOnEnter?: boolean\r\n autoFocus?: boolean\r\n /** Nome acessível, quando não há etiqueta à volta (ex: \"Escrever comentário\"). */\r\n 'aria-label'?: string\r\n id?: string\r\n /** Aplica-se à caixa à volta. */\r\n className?: string\r\n /** Textos da barra (para outra língua). */\r\n labels?: Partial<RichTextEditorLabels>\r\n}\r\n\r\nfunction imageFiles(data: DataTransfer | null): File[] {\r\n return Array.from(data?.files ?? []).filter((f) => f.type.startsWith('image/'))\r\n}\r\n\r\n/** O HTML que o editor tem, ou `''` se estiver vazio. */\r\nfunction editorValue(editor: Editor): string {\r\n const html = editor.getHTML()\r\n return richTextIsEmpty(html) ? '' : html\r\n}\r\n\r\nfunction ToolbarButton({ label, active, disabled, onClick, children }: {\r\n label: string\r\n active?: boolean\r\n disabled?: boolean\r\n onClick: () => void\r\n children: React.ReactNode\r\n}) {\r\n return (\r\n <button\r\n type=\"button\"\r\n aria-label={label}\r\n title={label}\r\n aria-pressed={active === undefined ? undefined : active}\r\n disabled={disabled}\r\n // Não tira o foco ao editor: a formatação aplica-se ao que está selecionado.\r\n onMouseDown={(e) => e.preventDefault()}\r\n onClick={onClick}\r\n className={\r\n 'inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md transition-colors duration-150 ' +\r\n 'focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ' +\r\n (active\r\n ? 'bg-primary/10 text-primary dark:bg-slate-700 dark:text-foreground'\r\n : 'text-muted-foreground hover:bg-muted hover:text-foreground dark:hover:bg-slate-800')\r\n }\r\n >\r\n {children}\r\n </button>\r\n )\r\n}\r\n\r\nconst Separator = () => <span aria-hidden className=\"mx-1 h-4 w-px bg-border\" />\r\n\r\n/**\r\n * Texto com formatação, que se vê enquanto se escreve: negrito, itálico, sublinhado, riscado,\r\n * listas, citações e (com `onUploadImage`) imagens. Atalhos habituais: Ctrl+B, Ctrl+I, Ctrl+U;\r\n * \"- \" ou \"1. \" no início da linha começam uma lista.\r\n *\r\n * Guarda HTML. Para o mostrar, usar sempre o [RichText](?path=/docs/v1-primitives-display-richtext--docs),\r\n * que o limpa antes de entrar na página. Para validar \"obrigatório\", o `onChange` dá `''` quando está vazio\r\n * (ou `richTextIsEmpty`).\r\n *\r\n * Para uma conversa: `onSubmit` + `submitOnEnter` (Enter envia, Shift+Enter muda de linha) e `maxHeight`\r\n * para crescer com o texto até um limite.\r\n *\r\n * Dentro de um [FormField](?path=/docs/v1-components-forms-formfield--docs) fica com o `id` do campo.\r\n */\r\nexport function RichTextEditor({\r\n value,\r\n onChange,\r\n placeholder,\r\n invalid,\r\n disabled,\r\n minHeight = 80,\r\n maxHeight,\r\n onUploadImage,\r\n maximizable,\r\n onSubmit,\r\n submitOnEnter,\r\n autoFocus,\r\n 'aria-label': ariaLabel,\r\n id,\r\n className = '',\r\n labels: labelsProp,\r\n}: RichTextEditorProps) {\r\n const labels = { ...DEFAULT_LABELS, ...labelsProp }\r\n const field = useField()\r\n const fileInputRef = useRef<HTMLInputElement>(null)\r\n const [maximized, setMaximized] = useState(false)\r\n\r\n // Em refs: o editor é criado uma vez e os handlers do ProseMirror ficariam presos à primeira versão das props.\r\n const onChangeRef = useRef(onChange)\r\n onChangeRef.current = onChange\r\n const uploadRef = useRef(onUploadImage)\r\n uploadRef.current = onUploadImage\r\n const submitRef = useRef({ onSubmit, submitOnEnter })\r\n submitRef.current = { onSubmit, submitOnEnter }\r\n // O último valor que o editor deu: um `value` igual vem dele e não volta a ser posto lá dentro.\r\n const emitted = useRef<string | null>(null)\r\n const editorRef = useRef<Editor | null>(null)\r\n\r\n const insertImages = useCallback(async (files: File[]) => {\r\n for (const file of files) {\r\n try {\r\n const src = await uploadRef.current!(file)\r\n editorRef.current?.chain().focus().setImage({ src }).run()\r\n } catch {\r\n // Falhar a subir uma imagem não leva o texto já escrito; quem chama mostra o erro pelo seu lado.\r\n }\r\n }\r\n }, [])\r\n\r\n const editor = useEditor({\r\n extensions: [\r\n StarterKit.configure({\r\n heading: false,\r\n codeBlock: false,\r\n horizontalRule: false,\r\n link: { openOnClick: false, autolink: true, defaultProtocol: 'https' },\r\n }),\r\n Image.configure({ inline: false, allowBase64: true }),\r\n Extension.create({\r\n name: 'submitShortcut',\r\n addKeyboardShortcuts() {\r\n const submit = () => {\r\n const { onSubmit: send } = submitRef.current\r\n if (!send) return false\r\n send()\r\n return true\r\n }\r\n return {\r\n 'Mod-Enter': submit,\r\n Enter: ({ editor: ed }) => {\r\n if (!submitRef.current.submitOnEnter) return false\r\n if (ed.isActive('bulletList') || ed.isActive('orderedList')) return false\r\n return submit()\r\n },\r\n }\r\n },\r\n }),\r\n ],\r\n content: isRichText(value) ? value : textToRichText(value),\r\n editable: !disabled,\r\n autofocus: autoFocus ? 'end' : false,\r\n onUpdate: ({ editor: ed }) => {\r\n const html = editorValue(ed)\r\n emitted.current = html\r\n onChangeRef.current(html)\r\n },\r\n editorProps: {\r\n attributes: {\r\n class: `px-2.5 py-2 text-control leading-normal text-foreground outline-none ${richTextContentClasses}`,\r\n role: 'textbox',\r\n 'aria-multiline': 'true',\r\n },\r\n // Colar/largar imagens só onde há sítio para as guardar.\r\n handlePaste: (_view, event) => {\r\n if (!uploadRef.current) return false\r\n const files = imageFiles(event.clipboardData)\r\n if (!files.length) return false\r\n event.preventDefault()\r\n event.stopPropagation()\r\n void insertImages(files)\r\n return true\r\n },\r\n handleDrop: (_view, event) => {\r\n if (!uploadRef.current) return false\r\n const files = imageFiles((event as DragEvent).dataTransfer)\r\n if (!files.length) return false\r\n event.preventDefault()\r\n event.stopPropagation()\r\n void insertImages(files)\r\n return true\r\n },\r\n },\r\n })\r\n editorRef.current = editor\r\n\r\n // A barra só volta a desenhar quando muda o que mostra (o editor não redesenha a cada tecla).\r\n const state = useEditorState({\r\n editor,\r\n selector: ({ editor: ed }) =>\r\n ed\r\n ? {\r\n empty: ed.isEmpty,\r\n bold: ed.isActive('bold'),\r\n italic: ed.isActive('italic'),\r\n underline: ed.isActive('underline'),\r\n strike: ed.isActive('strike'),\r\n bulletList: ed.isActive('bulletList'),\r\n orderedList: ed.isActive('orderedList'),\r\n quote: ed.isActive('blockquote'),\r\n }\r\n : null,\r\n })\r\n\r\n // Valor mudado por fora (ex: limpar depois de enviar, abrir outro registo).\r\n useEffect(() => {\r\n if (!editor || value === emitted.current) return\r\n if (!value && editor.isEmpty) return\r\n editor.commands.setContent(isRichText(value) ? value : textToRichText(value), { emitUpdate: false })\r\n emitted.current = value\r\n }, [value, editor])\r\n\r\n useEffect(() => {\r\n editor?.setEditable(!disabled)\r\n }, [editor, disabled])\r\n\r\n // Atributos que dependem das props, no próprio campo editável.\r\n useEffect(() => {\r\n if (!editor) return\r\n const el = editor.view.dom as HTMLElement\r\n const set = (name: string, v: string | undefined) => (v ? el.setAttribute(name, v) : el.removeAttribute(name))\r\n set('id', id ?? field.id)\r\n set('aria-label', ariaLabel)\r\n set('aria-invalid', invalid ? 'true' : undefined)\r\n set('aria-required', field.required ? 'true' : undefined)\r\n set('aria-disabled', disabled ? 'true' : undefined)\r\n }, [editor, id, field.id, field.required, ariaLabel, invalid, disabled])\r\n\r\n // Esc sai do modo maximizado (e só isso — não fecha a janela à volta).\r\n useEffect(() => {\r\n if (!maximized) return\r\n const onKey = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation()\r\n setMaximized(false)\r\n }\r\n }\r\n window.addEventListener('keydown', onKey, true)\r\n return () => window.removeEventListener('keydown', onKey, true)\r\n }, [maximized])\r\n\r\n if (!editor || !state) return null\r\n\r\n const run = (fn: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) => () => fn(editor.chain().focus()).run()\r\n const writeArea: React.CSSProperties = maximized\r\n ? {}\r\n : { minHeight, maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight }\r\n\r\n return (\r\n <>\r\n {maximized && <div className=\"fixed inset-0 z-[60] bg-black/40 backdrop-blur-sm\" onClick={() => setMaximized(false)} />}\r\n {/* overflow-clip e não overflow-hidden: o hidden faria disto um contentor de scroll e a barra deixava de ficar presa. */}\r\n <div\r\n className={\r\n 'overflow-clip rounded-lg border bg-card transition-[border-color,box-shadow] duration-150 dark:bg-slate-900 ' +\r\n (invalid\r\n ? 'border-destructive ring-3 ring-destructive/15 '\r\n : 'border-border focus-within:border-primary/40 focus-within:ring-3 focus-within:ring-primary/10 dark:border-slate-600 ') +\r\n (disabled ? 'bg-muted opacity-70 ' : '') +\r\n (maximized ? 'fixed inset-4 z-[61] flex flex-col shadow-2xl md:inset-10 ' : 'w-full min-w-0 ') +\r\n className\r\n }\r\n >\r\n <div role=\"toolbar\" aria-label=\"Formatação\" className=\"sticky top-0 z-10 flex flex-wrap items-center gap-0.5 border-b border-border bg-muted/40 px-1.5 py-1 dark:border-slate-700 dark:bg-slate-800/60\">\r\n <ToolbarButton label={labels.bold} active={state.bold} disabled={disabled} onClick={run((c) => c.toggleBold())}>\r\n <BoldIcon size={14} />\r\n </ToolbarButton>\r\n <ToolbarButton label={labels.italic} active={state.italic} disabled={disabled} onClick={run((c) => c.toggleItalic())}>\r\n <ItalicIcon size={14} />\r\n </ToolbarButton>\r\n <ToolbarButton label={labels.underline} active={state.underline} disabled={disabled} onClick={run((c) => c.toggleUnderline())}>\r\n <UnderlineIcon size={14} />\r\n </ToolbarButton>\r\n <ToolbarButton label={labels.strike} active={state.strike} disabled={disabled} onClick={run((c) => c.toggleStrike())}>\r\n <StrikethroughIcon size={14} />\r\n </ToolbarButton>\r\n <Separator />\r\n <ToolbarButton label={labels.bulletList} active={state.bulletList} disabled={disabled} onClick={run((c) => c.toggleBulletList())}>\r\n <ListBulletIcon size={14} />\r\n </ToolbarButton>\r\n <ToolbarButton label={labels.orderedList} active={state.orderedList} disabled={disabled} onClick={run((c) => c.toggleOrderedList())}>\r\n <ListNumberedIcon size={14} />\r\n </ToolbarButton>\r\n <ToolbarButton label={labels.quote} active={state.quote} disabled={disabled} onClick={run((c) => c.toggleBlockquote())}>\r\n <QuoteIcon size={14} />\r\n </ToolbarButton>\r\n {onUploadImage && (\r\n <>\r\n <Separator />\r\n <ToolbarButton label={labels.image} disabled={disabled} onClick={() => fileInputRef.current?.click()}>\r\n <ImageIcon size={14} />\r\n </ToolbarButton>\r\n <input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\"image/*\"\r\n multiple\r\n hidden\r\n onChange={(e) => {\r\n const files = Array.from(e.target.files ?? [])\r\n e.target.value = ''\r\n void insertImages(files)\r\n }}\r\n />\r\n </>\r\n )}\r\n {maximizable && (\r\n <span className=\"ml-auto\">\r\n <ToolbarButton label={maximized ? labels.restore : labels.maximize} onClick={() => setMaximized((m) => !m)}>\r\n {maximized ? <MinimizeIcon size={14} /> : <MaximizeIcon size={14} />}\r\n </ToolbarButton>\r\n </span>\r\n )}\r\n </div>\r\n {/* A área de escrita: clicar em qualquer ponto dela põe o cursor no editor. */}\r\n <div\r\n className={`relative cursor-text ${maximized ? 'flex-1 overflow-y-auto' : 'overflow-y-auto'} [&>div]:h-full [&_.ProseMirror]:min-h-full`}\r\n style={writeArea}\r\n onClick={() => !disabled && !editor.isFocused && editor.chain().focus().run()}\r\n >\r\n {state.empty && placeholder && (\r\n <p aria-hidden className=\"pointer-events-none absolute top-2 left-2.5 m-0 text-control text-muted-foreground/70 select-none\">\r\n {placeholder}\r\n </p>\r\n )}\r\n <EditorContent editor={editor} className=\"h-full\" />\r\n </div>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nexport default RichTextEditor\r\n"],"names":[],"mappings":"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"}
|