@12-apps/ui 6.10.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,303 @@
1
+ import {
2
+ floatAnimation,
3
+ getColorFromTheme
4
+ } from "./chunk-PQWPMIGH.js";
5
+ import "./chunk-PZ5AY32C.js";
6
+
7
+ // src/components/form/Textarea/TextareaRichEditor.tsx
8
+ import Code from "@mui/icons-material/Code";
9
+ import FormatBold from "@mui/icons-material/FormatBold";
10
+ import FormatColorFill from "@mui/icons-material/FormatColorFill";
11
+ import FormatColorText from "@mui/icons-material/FormatColorText";
12
+ import FormatItalic from "@mui/icons-material/FormatItalic";
13
+ import FormatListBulleted from "@mui/icons-material/FormatListBulleted";
14
+ import FormatListNumbered from "@mui/icons-material/FormatListNumbered";
15
+ import FormatQuote from "@mui/icons-material/FormatQuote";
16
+ import FormatUnderlined from "@mui/icons-material/FormatUnderlined";
17
+ import Link from "@mui/icons-material/Link";
18
+ import { alpha, Box, Divider, IconButton, Tooltip } from "@mui/material";
19
+ import { styled } from "@mui/material";
20
+ import { useEffect, useRef, useState } from "react";
21
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
+ var RichToolbar = styled(Box)(({ theme, glass }) => ({
23
+ display: "flex",
24
+ alignItems: "center",
25
+ gap: theme.spacing(0.5),
26
+ padding: theme.spacing(1),
27
+ borderRadius: `${theme.spacing(1)} ${theme.spacing(1)} 0 0`,
28
+ borderBottom: `1px solid ${theme.palette.divider}`,
29
+ background: glass ? alpha(theme.palette.background.paper, 0.1) : theme.palette.background.paper,
30
+ ...glass && {
31
+ backdropFilter: "blur(15px)"
32
+ },
33
+ "& .MuiDivider-root": {
34
+ height: 24,
35
+ margin: `0 ${theme.spacing(1)}`
36
+ }
37
+ }));
38
+ var ToolbarButton = styled(IconButton, {
39
+ shouldForwardProp: (prop) => prop !== "active"
40
+ })(({ theme, active }) => ({
41
+ padding: theme.spacing(0.75),
42
+ borderRadius: theme.spacing(0.5),
43
+ color: active ? theme.palette.primary.main : theme.palette.text.secondary,
44
+ backgroundColor: active ? alpha(theme.palette.primary.main, 0.1) : "transparent",
45
+ transition: "all 0.2s ease",
46
+ position: "relative",
47
+ overflow: "hidden",
48
+ "&::before": {
49
+ content: '""',
50
+ position: "absolute",
51
+ top: "50%",
52
+ left: "50%",
53
+ width: 0,
54
+ height: 0,
55
+ borderRadius: "50%",
56
+ backgroundColor: alpha(theme.palette.primary.main, 0.3),
57
+ transform: "translate(-50%, -50%)",
58
+ transition: "width 0.3s, height 0.3s"
59
+ },
60
+ "&:hover": {
61
+ color: theme.palette.primary.main,
62
+ backgroundColor: alpha(theme.palette.primary.main, 0.15),
63
+ transform: "translateY(-1px)",
64
+ animation: `${floatAnimation} 1.5s ease-in-out infinite`,
65
+ "&::before": {
66
+ width: "100%",
67
+ height: "100%"
68
+ }
69
+ },
70
+ "&:active": {
71
+ transform: "scale(0.95)"
72
+ }
73
+ }));
74
+ var CharacterCount = styled(Box)(({ theme, limit, count }) => {
75
+ const isWarning = limit && count > limit * 0.8;
76
+ const isError = limit && count > limit;
77
+ return {
78
+ position: "absolute",
79
+ bottom: theme.spacing(1),
80
+ right: theme.spacing(1),
81
+ fontSize: "0.75rem",
82
+ color: isError ? theme.palette.error.main : isWarning ? theme.palette.warning.main : theme.palette.text.secondary,
83
+ padding: "2px 6px",
84
+ borderRadius: theme.spacing(0.5),
85
+ backgroundColor: alpha(theme.palette.background.paper, 0.8),
86
+ backdropFilter: "blur(10px)",
87
+ transition: "all 0.3s ease"
88
+ };
89
+ });
90
+ var ContentEditableDiv = styled("div")(({ theme, error, focused, glass, customColor = "primary" }) => {
91
+ if (!theme) return {};
92
+ const colorPalette = getColorFromTheme(theme, customColor);
93
+ const errorColor = theme.palette.error;
94
+ return {
95
+ minHeight: "120px",
96
+ padding: theme.spacing(1.5),
97
+ borderRadius: `0 0 ${theme.spacing(1)} ${theme.spacing(1)}`,
98
+ border: `2px solid ${error ? errorColor.main : focused ? colorPalette.main : theme.palette.divider}`,
99
+ borderTop: "none",
100
+ backgroundColor: glass ? alpha(theme.palette.background.paper, 0.1) : theme.palette.background.paper,
101
+ color: theme.palette.text.primary,
102
+ fontFamily: theme.typography.fontFamily,
103
+ fontSize: "1rem",
104
+ lineHeight: 1.5,
105
+ outline: "none",
106
+ transition: "all 0.3s ease",
107
+ cursor: "text",
108
+ ...glass && {
109
+ backdropFilter: "blur(20px)"
110
+ },
111
+ "&:hover": {
112
+ backgroundColor: glass ? alpha(theme.palette.background.paper, 0.15) : alpha(theme.palette.background.paper, 0.9)
113
+ },
114
+ "&:focus": {
115
+ borderColor: error ? errorColor.main : colorPalette.main,
116
+ boxShadow: `0 0 0 3px ${alpha(error ? errorColor.main : colorPalette.main, 0.1)}`
117
+ },
118
+ "& > *": {
119
+ margin: "0.5em 0"
120
+ },
121
+ '&[contenteditable="true"]:empty::before': {
122
+ content: "attr(data-placeholder)",
123
+ color: theme.palette.text.secondary,
124
+ opacity: 0.6
125
+ }
126
+ };
127
+ });
128
+ var formatText = (command, value) => {
129
+ document.execCommand(command, false, value);
130
+ };
131
+ var useRichEditorState = ({
132
+ value,
133
+ onChange
134
+ }) => {
135
+ const [focused, setFocused] = useState(false);
136
+ const [characterCount, setCharacterCount] = useState(0);
137
+ const [activeFormats, setActiveFormats] = useState({
138
+ bold: false,
139
+ italic: false,
140
+ underline: false,
141
+ list: false,
142
+ orderedList: false,
143
+ quote: false,
144
+ code: false
145
+ });
146
+ const contentRef = useRef(null);
147
+ useEffect(() => {
148
+ if (contentRef.current && value !== void 0) {
149
+ if (contentRef.current.innerHTML !== value) {
150
+ contentRef.current.innerHTML = value;
151
+ }
152
+ setCharacterCount(contentRef.current.innerText.length);
153
+ }
154
+ }, [value]);
155
+ const handleInput = () => {
156
+ if (contentRef.current) {
157
+ const html = contentRef.current.innerHTML;
158
+ onChange?.(html);
159
+ setCharacterCount(contentRef.current.innerText.length);
160
+ updateActiveFormats();
161
+ }
162
+ };
163
+ const updateActiveFormats = () => {
164
+ setActiveFormats({
165
+ bold: document.queryCommandState("bold"),
166
+ italic: document.queryCommandState("italic"),
167
+ underline: document.queryCommandState("underline"),
168
+ list: document.queryCommandState("insertUnorderedList"),
169
+ orderedList: document.queryCommandState("insertOrderedList"),
170
+ quote: false,
171
+ code: false
172
+ });
173
+ };
174
+ const handleFormat = (command, value2) => {
175
+ formatText(command, value2);
176
+ updateActiveFormats();
177
+ contentRef.current?.focus();
178
+ };
179
+ return {
180
+ focused,
181
+ setFocused,
182
+ characterCount,
183
+ activeFormats,
184
+ contentRef,
185
+ handleInput,
186
+ handleFormat,
187
+ updateActiveFormats
188
+ };
189
+ };
190
+ var InlineFormatButtons = ({ copy, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(Fragment, { children: [
191
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.bold, arrow: true, children: /* @__PURE__ */ jsx(
192
+ ToolbarButton,
193
+ {
194
+ size: "small",
195
+ active: activeFormats.bold,
196
+ onClick: () => onCommand("bold"),
197
+ children: /* @__PURE__ */ jsx(FormatBold, { fontSize: "small" })
198
+ }
199
+ ) }),
200
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.italic, arrow: true, children: /* @__PURE__ */ jsx(
201
+ ToolbarButton,
202
+ {
203
+ size: "small",
204
+ active: activeFormats.italic,
205
+ onClick: () => onCommand("italic"),
206
+ children: /* @__PURE__ */ jsx(FormatItalic, { fontSize: "small" })
207
+ }
208
+ ) }),
209
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.underline, arrow: true, children: /* @__PURE__ */ jsx(
210
+ ToolbarButton,
211
+ {
212
+ size: "small",
213
+ active: activeFormats.underline,
214
+ onClick: () => onCommand("underline"),
215
+ children: /* @__PURE__ */ jsx(FormatUnderlined, { fontSize: "small" })
216
+ }
217
+ ) })
218
+ ] });
219
+ var BlockFormatButtons = ({ copy, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(Fragment, { children: [
220
+ /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
221
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.bulletList, arrow: true, children: /* @__PURE__ */ jsx(
222
+ ToolbarButton,
223
+ {
224
+ size: "small",
225
+ active: activeFormats.list,
226
+ onClick: () => onCommand("insertUnorderedList"),
227
+ children: /* @__PURE__ */ jsx(FormatListBulleted, { fontSize: "small" })
228
+ }
229
+ ) }),
230
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.numberedList, arrow: true, children: /* @__PURE__ */ jsx(
231
+ ToolbarButton,
232
+ {
233
+ size: "small",
234
+ active: activeFormats.orderedList,
235
+ onClick: () => onCommand("insertOrderedList"),
236
+ children: /* @__PURE__ */ jsx(FormatListNumbered, { fontSize: "small" })
237
+ }
238
+ ) }),
239
+ /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
240
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.quote, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", onClick: () => onCommand("formatBlock", "blockquote"), children: /* @__PURE__ */ jsx(FormatQuote, { fontSize: "small" }) }) }),
241
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.code, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", onClick: () => onCommand("formatBlock", "pre"), children: /* @__PURE__ */ jsx(Code, { fontSize: "small" }) }) }),
242
+ /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
243
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.insertLink, arrow: true, children: /* @__PURE__ */ jsx(
244
+ ToolbarButton,
245
+ {
246
+ size: "small",
247
+ onClick: () => {
248
+ const url = window.prompt(copy.linkPrompt);
249
+ if (url) onCommand("createLink", url);
250
+ },
251
+ children: /* @__PURE__ */ jsx(Link, { fontSize: "small" })
252
+ }
253
+ ) }),
254
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.textColor, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", children: /* @__PURE__ */ jsx(FormatColorText, { fontSize: "small" }) }) }),
255
+ /* @__PURE__ */ jsx(Tooltip, { title: copy.backgroundColor, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", children: /* @__PURE__ */ jsx(FormatColorFill, { fontSize: "small" }) }) })
256
+ ] });
257
+ var EditorToolbar = ({ copy, glass, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(RichToolbar, { glass, children: [
258
+ /* @__PURE__ */ jsx(InlineFormatButtons, { copy, activeFormats, onCommand }),
259
+ /* @__PURE__ */ jsx(BlockFormatButtons, { copy, activeFormats, onCommand })
260
+ ] });
261
+ var TextareaRichEditor = ({ copy, value, onChange, placeholder, error, glass, color, characterLimit }) => {
262
+ const {
263
+ focused,
264
+ setFocused,
265
+ characterCount,
266
+ activeFormats,
267
+ contentRef,
268
+ handleInput,
269
+ handleFormat,
270
+ updateActiveFormats
271
+ } = useRichEditorState({ value, onChange });
272
+ return /* @__PURE__ */ jsxs(Box, { sx: { position: "relative" }, children: [
273
+ /* @__PURE__ */ jsx(EditorToolbar, { copy, glass, activeFormats, onCommand: handleFormat }),
274
+ /* @__PURE__ */ jsx(
275
+ ContentEditableDiv,
276
+ {
277
+ ref: contentRef,
278
+ contentEditable: true,
279
+ "data-placeholder": placeholder,
280
+ error,
281
+ focused,
282
+ glass,
283
+ customColor: color,
284
+ onFocus: () => setFocused(true),
285
+ onBlur: () => setFocused(false),
286
+ onInput: handleInput,
287
+ onKeyUp: updateActiveFormats,
288
+ onMouseUp: updateActiveFormats,
289
+ role: "textbox",
290
+ "aria-multiline": "true",
291
+ "aria-label": placeholder || "Rich text editor"
292
+ }
293
+ ),
294
+ characterLimit && /* @__PURE__ */ jsxs(CharacterCount, { limit: characterLimit, count: characterCount, children: [
295
+ characterCount,
296
+ characterLimit ? `/${characterLimit}` : ""
297
+ ] })
298
+ ] });
299
+ };
300
+ export {
301
+ TextareaRichEditor
302
+ };
303
+ //# sourceMappingURL=TextareaRichEditor-ZFB44VWZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/form/Textarea/TextareaRichEditor.tsx"],"sourcesContent":["import Code from '@mui/icons-material/Code';\nimport FormatBold from '@mui/icons-material/FormatBold';\nimport FormatColorFill from '@mui/icons-material/FormatColorFill';\nimport FormatColorText from '@mui/icons-material/FormatColorText';\nimport FormatItalic from '@mui/icons-material/FormatItalic';\nimport FormatListBulleted from '@mui/icons-material/FormatListBulleted';\nimport FormatListNumbered from '@mui/icons-material/FormatListNumbered';\nimport FormatQuote from '@mui/icons-material/FormatQuote';\nimport FormatUnderlined from '@mui/icons-material/FormatUnderlined';\nimport Link from '@mui/icons-material/Link';\nimport { alpha, Box, Divider, IconButton, Tooltip } from '@mui/material';\nimport type { Theme } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React, { useEffect, useRef, useState } from 'react';\n\nimport { floatAnimation, getColorFromTheme } from './Textarea.styles';\nimport type { RichEditorToolbarCopy } from '../../../copy';\n\nconst RichToolbar = styled(Box)<{ glass?: boolean }>(({ theme, glass }) => ({\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(0.5),\n padding: theme.spacing(1),\n borderRadius: `${theme.spacing(1)} ${theme.spacing(1)} 0 0`,\n borderBottom: `1px solid ${theme.palette.divider}`,\n background: glass ? alpha(theme.palette.background.paper, 0.1) : theme.palette.background.paper,\n ...(glass && {\n backdropFilter: 'blur(15px)' }),\n '& .MuiDivider-root': {\n height: 24,\n margin: `0 ${theme.spacing(1)}` } }));\n\nconst ToolbarButton = styled(IconButton, {\n shouldForwardProp: (prop) => prop !== 'active' })<{ active?: boolean }>(({ theme, active }) => ({\n padding: theme.spacing(0.75),\n borderRadius: theme.spacing(0.5),\n color: active ? theme.palette.primary.main : theme.palette.text.secondary,\n backgroundColor: active ? alpha(theme.palette.primary.main, 0.1) : 'transparent',\n transition: 'all 0.2s ease',\n position: 'relative',\n overflow: 'hidden',\n\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n top: '50%',\n left: '50%',\n width: 0,\n height: 0,\n borderRadius: '50%',\n backgroundColor: alpha(theme.palette.primary.main, 0.3),\n transform: 'translate(-50%, -50%)',\n transition: 'width 0.3s, height 0.3s' },\n\n '&:hover': {\n color: theme.palette.primary.main,\n backgroundColor: alpha(theme.palette.primary.main, 0.15),\n transform: 'translateY(-1px)',\n animation: `${floatAnimation} 1.5s ease-in-out infinite`,\n\n '&::before': {\n width: '100%',\n height: '100%' } },\n\n '&:active': {\n transform: 'scale(0.95)' } }));\n\nconst CharacterCount = styled(Box)<{ limit?: number; count: number }>(({ theme, limit, count }) => {\n const isWarning = limit && count > limit * 0.8;\n const isError = limit && count > limit;\n\n return {\n position: 'absolute',\n bottom: theme.spacing(1),\n right: theme.spacing(1),\n fontSize: '0.75rem',\n color: isError\n ? theme.palette.error.main\n : isWarning\n ? theme.palette.warning.main\n : theme.palette.text.secondary,\n padding: '2px 6px',\n borderRadius: theme.spacing(0.5),\n backgroundColor: alpha(theme.palette.background.paper, 0.8),\n backdropFilter: 'blur(10px)',\n transition: 'all 0.3s ease' };\n});\n\nconst ContentEditableDiv = styled('div')<{\n error?: boolean;\n focused?: boolean;\n glass?: boolean;\n customColor?: string;\n theme?: Theme;\n}>(({ theme, error, focused, glass, customColor = 'primary' }) => {\n if (!theme) return {};\n const colorPalette = getColorFromTheme(theme, customColor);\n const errorColor = theme.palette.error;\n\n return {\n minHeight: '120px',\n padding: theme.spacing(1.5),\n borderRadius: `0 0 ${theme.spacing(1)} ${theme.spacing(1)}`,\n border: `2px solid ${error ? errorColor.main : focused ? colorPalette.main : theme.palette.divider}`,\n borderTop: 'none',\n backgroundColor: glass\n ? alpha(theme.palette.background.paper, 0.1)\n : theme.palette.background.paper,\n color: theme.palette.text.primary,\n fontFamily: theme.typography.fontFamily,\n fontSize: '1rem',\n lineHeight: 1.5,\n outline: 'none',\n transition: 'all 0.3s ease',\n cursor: 'text',\n\n ...(glass && {\n backdropFilter: 'blur(20px)' }),\n\n '&:hover': {\n backgroundColor: glass\n ? alpha(theme.palette.background.paper, 0.15)\n : alpha(theme.palette.background.paper, 0.9) },\n\n '&:focus': {\n borderColor: error ? errorColor.main : colorPalette.main,\n boxShadow: `0 0 0 3px ${alpha(error ? errorColor.main : colorPalette.main, 0.1)}` },\n\n '& > *': {\n margin: '0.5em 0' },\n\n '&[contenteditable=\"true\"]:empty::before': {\n content: 'attr(data-placeholder)',\n color: theme.palette.text.secondary,\n opacity: 0.6 } };\n});\n\n// Rich text formatting functions\nconst formatText = (command: string, value?: string) => {\n document.execCommand(command, false, value);\n};\n\n// Editable state: what is typed, how long it is, and which formats apply at the\n// caret. Kept apart from the markup below.\nconst useRichEditorState = ({\n value,\n onChange }: {\n value?: string;\n onChange?: (html: string) => void;\n}) => {\n const [focused, setFocused] = useState(false);\n const [characterCount, setCharacterCount] = useState(0);\n const [activeFormats, setActiveFormats] = useState({\n bold: false,\n italic: false,\n underline: false,\n list: false,\n orderedList: false,\n quote: false,\n code: false });\n const contentRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (contentRef.current && value !== undefined) {\n if (contentRef.current.innerHTML !== value) {\n contentRef.current.innerHTML = value;\n }\n setCharacterCount(contentRef.current.innerText.length);\n }\n }, [value]);\n\n const handleInput = () => {\n if (contentRef.current) {\n const html = contentRef.current.innerHTML;\n onChange?.(html);\n setCharacterCount(contentRef.current.innerText.length);\n updateActiveFormats();\n }\n };\n\n const updateActiveFormats = () => {\n setActiveFormats({\n bold: document.queryCommandState('bold'),\n italic: document.queryCommandState('italic'),\n underline: document.queryCommandState('underline'),\n list: document.queryCommandState('insertUnorderedList'),\n orderedList: document.queryCommandState('insertOrderedList'),\n quote: false,\n code: false });\n };\n\n const handleFormat = (command: string, value?: string) => {\n formatText(command, value);\n updateActiveFormats();\n contentRef.current?.focus();\n };\n\n return {\n focused,\n setFocused,\n characterCount,\n activeFormats,\n contentRef,\n handleInput,\n handleFormat,\n updateActiveFormats };\n};\n\nconst InlineFormatButtons: React.FC<{\n copy: RichEditorToolbarCopy;\n activeFormats: Record<string, boolean>;\n onCommand: (command: string, value?: string) => void;\n}> = ({ copy, activeFormats, onCommand }) => (\n <>\n <Tooltip title={copy.bold} arrow>\n <ToolbarButton\n size=\"small\"\n active={activeFormats.bold}\n onClick={() => onCommand('bold')}\n >\n <FormatBold fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.italic} arrow>\n <ToolbarButton\n size=\"small\"\n active={activeFormats.italic}\n onClick={() => onCommand('italic')}\n >\n <FormatItalic fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.underline} arrow>\n <ToolbarButton\n size=\"small\"\n active={activeFormats.underline}\n onClick={() => onCommand('underline')}\n >\n <FormatUnderlined fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n </>\n);\n\nconst BlockFormatButtons: React.FC<{\n copy: RichEditorToolbarCopy;\n activeFormats: Record<string, boolean>;\n onCommand: (command: string, value?: string) => void;\n}> = ({ copy, activeFormats, onCommand }) => (\n <>\n <Divider orientation=\"vertical\" flexItem />\n\n <Tooltip title={copy.bulletList} arrow>\n <ToolbarButton\n size=\"small\"\n active={activeFormats.list}\n onClick={() => onCommand('insertUnorderedList')}\n >\n <FormatListBulleted fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.numberedList} arrow>\n <ToolbarButton\n size=\"small\"\n active={activeFormats.orderedList}\n onClick={() => onCommand('insertOrderedList')}\n >\n <FormatListNumbered fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Divider orientation=\"vertical\" flexItem />\n\n <Tooltip title={copy.quote} arrow>\n <ToolbarButton size=\"small\" onClick={() => onCommand('formatBlock', 'blockquote')}>\n <FormatQuote fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.code} arrow>\n <ToolbarButton size=\"small\" onClick={() => onCommand('formatBlock', 'pre')}>\n <Code fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Divider orientation=\"vertical\" flexItem />\n\n <Tooltip title={copy.insertLink} arrow>\n <ToolbarButton\n size=\"small\"\n onClick={() => {\n const url = window.prompt(copy.linkPrompt);\n if (url) onCommand('createLink', url);\n }}\n >\n <Link fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.textColor} arrow>\n <ToolbarButton size=\"small\">\n <FormatColorText fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n\n <Tooltip title={copy.backgroundColor} arrow>\n <ToolbarButton size=\"small\">\n <FormatColorFill fontSize=\"small\" />\n </ToolbarButton>\n </Tooltip>\n </>\n);\n\n// The formatting buttons. Split out so the editor body is state, handlers and\n// the editable region.\nconst EditorToolbar: React.FC<{\n copy: RichEditorToolbarCopy;\n glass?: boolean;\n activeFormats: Record<string, boolean>;\n onCommand: (command: string, value?: string) => void;\n}> = ({ copy, glass, activeFormats, onCommand }) => (\n <RichToolbar glass={glass}>\n <InlineFormatButtons copy={copy} activeFormats={activeFormats} onCommand={onCommand} />\n <BlockFormatButtons copy={copy} activeFormats={activeFormats} onCommand={onCommand} />\n </RichToolbar>\n);\n\nexport const TextareaRichEditor: React.FC<{\n /**\n * The ten toolbar buttons' tooltips — which are also their accessible names,\n * since every one of them is a glyph — plus the link prompt. REQUIRED: this\n * package ships no default copy.\n */\n copy: RichEditorToolbarCopy;\n value?: string;\n onChange?: (html: string) => void;\n placeholder?: string;\n error?: boolean;\n glass?: boolean;\n color?: string;\n characterLimit?: number;\n}> = ({ copy, value, onChange, placeholder, error, glass, color, characterLimit }) => {\n const {\n focused,\n setFocused,\n characterCount,\n activeFormats,\n contentRef,\n handleInput,\n handleFormat,\n updateActiveFormats } = useRichEditorState({ value, onChange });\n\n return (\n <Box sx={{ position: 'relative' }}>\n <EditorToolbar copy={copy} glass={glass} activeFormats={activeFormats} onCommand={handleFormat} />\n\n <ContentEditableDiv\n ref={contentRef}\n contentEditable\n data-placeholder={placeholder}\n error={error}\n focused={focused}\n glass={glass}\n customColor={color}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n onInput={handleInput}\n onKeyUp={updateActiveFormats}\n onMouseUp={updateActiveFormats}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-label={placeholder || 'Rich text editor'}\n />\n\n {characterLimit && (\n <CharacterCount limit={characterLimit} count={characterCount}>\n {characterCount}\n {characterLimit ? `/${characterLimit}` : ''}\n </CharacterCount>\n )}\n </Box>\n );\n};\n"],"mappings":"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","names":["value"]}
@@ -0,0 +1,111 @@
1
+ // src/components/data-display/DataViews/data-views-day-input.tsx
2
+ import { TextField } from "@mui/material";
3
+ import { useEffect, useState } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ var SEGMENT_WIDTH = { year: 4, month: 2, day: 2 };
6
+ function segmentsOf(mask) {
7
+ const tokens = mask.split(/[^A-Za-z]+/).filter(Boolean);
8
+ if (tokens.length !== 3) return ["day", "month", "year"];
9
+ const segments = tokens.map((token) => {
10
+ const head = token.slice(0, 1).toLowerCase();
11
+ if (head === "y" || head === "a") return "year";
12
+ return head === "m" ? "month" : "day";
13
+ });
14
+ return new Set(segments).size === 3 ? segments : ["day", "month", "year"];
15
+ }
16
+ var separatorOf = (mask) => mask.replace(/[A-Za-z]/g, "").slice(0, 1) || "/";
17
+ function isoToMasked(iso, mask) {
18
+ const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
19
+ if (!parts) return "";
20
+ const value = {
21
+ year: parts[1],
22
+ month: parts[2],
23
+ day: parts[3]
24
+ };
25
+ return segmentsOf(mask).map((segment) => value[segment]).join(separatorOf(mask));
26
+ }
27
+ function maskedToIso(text, mask) {
28
+ const segments = segmentsOf(mask);
29
+ const digits = text.replace(/\D/g, "");
30
+ if (digits.length !== 8) return "";
31
+ const value = {};
32
+ let cursor = 0;
33
+ for (const segment of segments) {
34
+ value[segment] = digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]);
35
+ cursor += SEGMENT_WIDTH[segment];
36
+ }
37
+ const year = Number(value.year);
38
+ const month = Number(value.month);
39
+ const day = Number(value.day);
40
+ if (year < 1e3) return "";
41
+ const probe = new Date(Date.UTC(year, month - 1, day));
42
+ if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return "";
43
+ return `${value.year}-${value.month}-${value.day}`;
44
+ }
45
+ function maskDate(raw, mask) {
46
+ const segments = segmentsOf(mask);
47
+ const separator = separatorOf(mask);
48
+ const digits = raw.replace(/\D/g, "").slice(0, 8);
49
+ const out = [];
50
+ let cursor = 0;
51
+ for (const segment of segments) {
52
+ if (cursor >= digits.length) break;
53
+ out.push(digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]));
54
+ cursor += SEGMENT_WIDTH[segment];
55
+ }
56
+ return out.join(separator);
57
+ }
58
+ function dayInputProps(testId, describedBy) {
59
+ return {
60
+ "data-testid": testId,
61
+ maxLength: 10,
62
+ inputMode: "numeric",
63
+ autoComplete: "off",
64
+ ...describedBy ? { "aria-describedby": describedBy } : {}
65
+ };
66
+ }
67
+ function DayBoundInput({
68
+ label,
69
+ value,
70
+ onChange,
71
+ testId,
72
+ error = false,
73
+ describedBy,
74
+ mask
75
+ }) {
76
+ const applied = value ?? "";
77
+ const [text, setText] = useState(() => isoToMasked(applied, mask));
78
+ useEffect(() => {
79
+ setText(
80
+ (current) => maskedToIso(current, mask) === applied ? current : isoToMasked(applied, mask)
81
+ );
82
+ }, [applied, mask]);
83
+ return /* @__PURE__ */ jsx(
84
+ TextField,
85
+ {
86
+ size: "small",
87
+ type: "text",
88
+ label,
89
+ error,
90
+ placeholder: mask,
91
+ value: text,
92
+ onChange: (event) => {
93
+ const next = maskDate(event.target.value, mask);
94
+ setText(next);
95
+ if (next === "") return onChange(void 0);
96
+ const iso = maskedToIso(next, mask);
97
+ if (iso) onChange(iso);
98
+ },
99
+ onBlur: () => setText(isoToMasked(applied, mask)),
100
+ InputLabelProps: { shrink: true },
101
+ inputProps: dayInputProps(testId, describedBy),
102
+ sx: { width: 165 }
103
+ }
104
+ );
105
+ }
106
+
107
+ export {
108
+ isoToMasked,
109
+ DayBoundInput
110
+ };
111
+ //# sourceMappingURL=chunk-FKWNB4LW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/DataViews/data-views-day-input.tsx"],"sourcesContent":["'use client';\n\n/**\n * A day bound as a MASKED `dd/mm/aaaa` text field, replacing `<input type=\"date\">`.\n *\n * The native control is three separate segments wearing one box. Typing a date\n * means filling a segment, being jumped to the next, and starting over — and\n * which segment comes first is the BROWSER's locale, not ours, so the same\n * field reads `dd/mm/aaaa` for one merchant and `mm/dd/yyyy` for the next with\n * nothing in our code saying which. There is no way to type a date as one\n * continuous run of digits, which is how people actually type dates.\n *\n * A mask gives that back: eight digits straight through, separators inserted as\n * you go, backspace walking back through them.\n *\n * Measured, driving both controls in Chromium with the same eight keystrokes\n * (`01072026`, one `keydown` at a time):\n *\n * native → field ends EMPTY, no filter applied, 0 requests\n * masked → field reads `01/07/2026`, filter applied, 1 request\n *\n * The native result is the whole complaint: you type a date and nothing lands.\n *\n * The second half of the fix is WHEN the bound is written. A partially-typed\n * year is itself a valid date — `2026` passes through `0002`, `0020`, `0202` on\n * the way — so committing on every keystroke would send the grid off to fetch\n * years nobody meant. A bound is written only once the text is a WHOLE, real\n * date, which is what keeps the run above at one request rather than four.\n */\nimport { TextField } from '@mui/material';\nimport React, { useEffect, useState } from 'react';\n\n\n/**\n * THE MASK IS THE ORDER, and both halves of this file read it.\n *\n * The placeholder a host supplies (`copy.filters.dayMask` — `dd/mm/aaaa` for\n * pt-BR, `mm/dd/yyyy` for en-US) is not decoration: it tells the reader which\n * segment comes first. Everything below therefore derives the segment order\n * FROM that same string, so the shape on screen and the shape the parser\n * expects cannot disagree.\n *\n * They did disagree, and the failure was silent in the worst direction. The\n * mask/parse pair was hardcoded day-first (the `Br` in the old names was\n * accurate) while the placeholder followed the copy pack — so under `en-US` the\n * field ASKED for `mm/dd/yyyy`, and a reader who typed exactly that got a day\n * of 12 and a month of 31, which is not a date, which commits nothing. No\n * error, no filter, a full list that reads like an answer. The en-US pack's own\n * comment already said the mask \"is the shape that gets a date entered\n * backwards\"; nothing enforced it.\n */\n\n/** What one segment of a mask is, and how many digits it takes. */\ntype DaySegment = 'year' | 'month' | 'day';\nconst SEGMENT_WIDTH: Readonly<Record<DaySegment, number>> = { year: 4, month: 2, day: 2 };\n\n/**\n * The segments a mask asks for, in its own order.\n *\n * Keyed on each token's FIRST letter so one rule covers every spelling a host\n * might use — `aaaa`/`yyyy` for the year, `mm` for the month, `dd`/`tt` for the\n * day. An unreadable mask falls back to day-first rather than throwing: this\n * runs on every keystroke, and a field that renders is recoverable where one\n * that crashes the grid is not.\n */\nfunction segmentsOf(mask: string): readonly DaySegment[] {\n const tokens = mask.split(/[^A-Za-z]+/).filter(Boolean);\n if (tokens.length !== 3) return ['day', 'month', 'year'];\n const segments = tokens.map((token): DaySegment => {\n const head = token.slice(0, 1).toLowerCase();\n if (head === 'y' || head === 'a') return 'year';\n return head === 'm' ? 'month' : 'day';\n });\n // A mask naming the same segment twice describes no date. Fall back rather\n // than build a parser that can never succeed.\n return new Set(segments).size === 3 ? segments : ['day', 'month', 'year'];\n}\n\n/** The separator the mask uses, defaulting to `/`. */\nconst separatorOf = (mask: string): string => mask.replace(/[A-Za-z]/g, '').slice(0, 1) || '/';\n\n/** `AAAA-MM-DD` (the wire) → the mask's own order (what the reader sees). */\nexport function isoToMasked(iso: string, mask: string): string {\n const parts = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (!parts) return '';\n const value: Record<DaySegment, string> = {\n year: parts[1] as string,\n month: parts[2] as string,\n day: parts[3] as string,\n };\n return segmentsOf(mask)\n .map((segment) => value[segment])\n .join(separatorOf(mask));\n}\n\n/**\n * The mask's order → `AAAA-MM-DD`, but ONLY for a day that exists.\n *\n * `31/02/2026` parses fine as three numbers and is not a date, and `Date` would\n * silently roll it into March rather than reject it — so the round trip through\n * UTC is the check: if the month or day comes back changed, the input was not a\n * day.\n */\nexport function maskedToIso(text: string, mask: string): string {\n const segments = segmentsOf(mask);\n const digits = text.replace(/\\D/g, '');\n if (digits.length !== 8) return '';\n const value = {} as Record<DaySegment, string>;\n let cursor = 0;\n for (const segment of segments) {\n value[segment] = digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]);\n cursor += SEGMENT_WIDTH[segment];\n }\n const year = Number(value.year);\n const month = Number(value.month);\n const day = Number(value.day);\n if (year < 1000) return '';\n const probe = new Date(Date.UTC(year, month - 1, day));\n if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return '';\n return `${value.year}-${value.month}-${value.day}`;\n}\n\n/**\n * Digits only, capped at the mask's width, with the separators put back as you\n * type.\n *\n * Deriving the whole string from the digits — rather than editing the text in\n * place — is what makes backspace behave. Four digits of `dd/mm/aaaa` render as\n * `06/08`, not `06/08/`, so deleting the first digit of the year removes the\n * trailing separator with it instead of leaving a dead keystroke that changes\n * nothing.\n */\nexport function maskDate(raw: string, mask: string): string {\n const segments = segmentsOf(mask);\n const separator = separatorOf(mask);\n const digits = raw.replace(/\\D/g, '').slice(0, 8);\n const out: string[] = [];\n let cursor = 0;\n for (const segment of segments) {\n if (cursor >= digits.length) break;\n out.push(digits.slice(cursor, cursor + SEGMENT_WIDTH[segment]));\n cursor += SEGMENT_WIDTH[segment];\n }\n return out.join(separator);\n}\n\n/**\n * What has to land on the `<input>` itself rather than on the `TextField`.\n *\n * Anything `TextField` does not recognise is spread onto its root `FormControl`\n * div. `inputMode` went up there once: the attribute sat on a non-editable\n * wrapper, meant nothing, and the phone went on offering the letter keyboard\n * for a field that only takes digits (the `dd/mm/aaaa` hint under a QWERTY\n * layout). Only the `<input>` this reaches decides which keypad opens — and the\n * same is true of `aria-describedby`, which on the wrapper points at a\n * description no screen reader will read out for the field.\n *\n * `numeric` and not `tel`: both raise a keypad, but the phone one is a dialler\n * with `+ * #` on it — keys that cannot occur in a date. A paste carrying\n * separators still lands, because the MASK is what filters the text; the keypad\n * only narrows what a thumb can reach for.\n *\n * Autofill off for the same reason: the browser's saved-value strip covers the\n * keypad to suggest names and addresses, none of which are a date, and none of\n * which the mask would keep.\n */\nfunction dayInputProps(testId: string, describedBy: string | undefined) {\n return {\n 'data-testid': testId,\n maxLength: 10,\n inputMode: 'numeric' as const,\n autoComplete: 'off',\n ...(describedBy ? { 'aria-describedby': describedBy } : {}),\n };\n}\n\n/**\n * One end of a day window. The BOUND stays `AAAA-MM-DD` — the form the backend's\n * inclusive day comparison already speaks, and the form the chip label reads —\n * so only the typing surface changes.\n */\nexport function DayBoundInput({\n label,\n value,\n onChange,\n testId,\n error = false,\n describedBy,\n mask,\n}: {\n label: string;\n /** The applied bound, `AAAA-MM-DD`, or undefined when this end is open. */\n value: string | undefined;\n onChange: (bound: string | undefined) => void;\n testId: string;\n /**\n * Paint the field as rejected. It only ever writes a real day by itself, so\n * this is for a caller judging the PAIR: a window whose end precedes its\n * start is two valid days and one impossible period, and the field it was\n * typed into is the only place to say which half is being complained about.\n */\n error?: boolean;\n /** Id of the element carrying that complaint, so it is read WITH the field. */\n describedBy?: string;\n /**\n * The day mask, which IS the placeholder — so the expected ORDER is on\n * screen before the first keystroke rather than discovered by getting it\n * wrong. `dd/mm/aaaa` is one locale's order and one locale's letters.\n *\n * A PROP rather than a context read: this field has two callers, and only\n * one of them is inside DataViews. `DateRangePicker` mounts it standalone\n * with its own message table, so a context read here throws for every host\n * of that picker — which is exactly what it did.\n */\n mask: string;\n}): React.JSX.Element {\n const applied = value ?? '';\n const [text, setText] = useState(() => isoToMasked(applied, mask));\n\n // The bound is the APPLIED filter; `text` is what is being typed. Re-sync only\n // when the applied value moved somewhere the text does not already mean — a\n // \"Limpar\", a saved view, the other pill resetting the pair. Without that\n // guard the field's own write bounces straight back mid-edit and resets the\n // caret, which is the native control's worst habit reproduced by hand.\n useEffect(() => {\n setText((current) =>\n maskedToIso(current, mask) === applied ? current : isoToMasked(applied, mask),\n );\n }, [applied, mask]);\n\n return (\n <TextField\n size=\"small\"\n type=\"text\"\n label={label}\n error={error}\n placeholder={mask}\n value={text}\n onChange={(event) => {\n const next = maskDate(event.target.value, mask);\n setText(next);\n // Emptying clears the bound. A COMPLETE day applies it. A partial one\n // does NEITHER — it leaves whatever is applied alone, so typing is never\n // fought and a half-typed year never reaches the backend.\n if (next === '') return onChange(undefined);\n const iso = maskedToIso(next, mask);\n if (iso) onChange(iso);\n }}\n // Leaving a half-typed (or impossible) date on screen would show a filter\n // the list is not actually using. On blur the field snaps back to what IS\n // applied; a complete date already equals it, so this only ever tidies.\n onBlur={() => setText(isoToMasked(applied, mask))}\n // The placeholder occupies the space an un-shrunk label would render into.\n InputLabelProps={{ shrink: true }}\n inputProps={dayInputProps(testId, describedBy)}\n sx={{ width: 165 }}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,94 @@
1
+ // src/components/form/Textarea/Textarea.styles.ts
2
+ import { alpha, keyframes } from "@mui/material";
3
+ var glowAnimation = keyframes`
4
+ 0% {
5
+ box-shadow: 0 0 5px currentColor;
6
+ }
7
+ 50% {
8
+ box-shadow: 0 0 20px currentColor, 0 0 30px currentColor;
9
+ }
10
+ 100% {
11
+ box-shadow: 0 0 5px currentColor;
12
+ }
13
+ `;
14
+ var floatAnimation = keyframes`
15
+ 0% {
16
+ transform: translateY(0px);
17
+ }
18
+ 50% {
19
+ transform: translateY(-3px);
20
+ }
21
+ 100% {
22
+ transform: translateY(0px);
23
+ }
24
+ `;
25
+ var neutralPalette = (theme) => {
26
+ const grey = theme.palette.grey;
27
+ return {
28
+ main: grey?.[700] || "#616161",
29
+ dark: grey?.[800] || "#424242",
30
+ light: grey?.[500] || "#9e9e9e",
31
+ contrastText: "#fff"
32
+ };
33
+ };
34
+ var pick = (...candidates) => candidates.find(Boolean) ?? "";
35
+ var withFallbacks = (theme, palette) => ({
36
+ main: pick(palette?.main, theme.palette.primary.main),
37
+ dark: pick(palette?.dark, palette?.main, theme.palette.primary.dark),
38
+ light: pick(palette?.light, palette?.main, theme.palette.primary.light),
39
+ contrastText: pick(palette?.contrastText, "#fff")
40
+ });
41
+ var getColorFromTheme = (theme, color) => {
42
+ if (color === "neutral") return neutralPalette(theme);
43
+ const colorMap = {
44
+ primary: theme.palette.primary,
45
+ secondary: theme.palette.secondary,
46
+ success: theme.palette.success,
47
+ warning: theme.palette.warning,
48
+ info: theme.palette.info,
49
+ danger: theme.palette.error
50
+ };
51
+ return withFallbacks(theme, colorMap[color] || theme.palette.primary);
52
+ };
53
+ var textareaEmphasisStyles = ({
54
+ theme,
55
+ colorPalette,
56
+ glass,
57
+ gradient,
58
+ glow
59
+ }) => {
60
+ const glassStyles = glass ? {
61
+ backgroundColor: alpha(theme.palette.background.paper, 0.1),
62
+ backdropFilter: "blur(20px)",
63
+ border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
64
+ "&:hover": {
65
+ backgroundColor: alpha(theme.palette.background.paper, 0.15),
66
+ backdropFilter: "blur(25px)"
67
+ },
68
+ "&:focus": {
69
+ backgroundColor: alpha(theme.palette.background.paper, 0.2),
70
+ backdropFilter: "blur(30px)"
71
+ }
72
+ } : {};
73
+ const gradientStyles = gradient ? {
74
+ background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,
75
+ linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.light}) border-box`,
76
+ border: "2px solid transparent",
77
+ "&:focus": {
78
+ background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,
79
+ linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.dark}) border-box`
80
+ }
81
+ } : {};
82
+ const glowStyles = glow ? {
83
+ animation: `${glowAnimation} 2s ease-in-out infinite`,
84
+ boxShadow: `0 0 10px ${alpha(colorPalette.main, 0.3)}`
85
+ } : {};
86
+ return { ...glassStyles, ...gradientStyles, ...glowStyles };
87
+ };
88
+
89
+ export {
90
+ floatAnimation,
91
+ getColorFromTheme,
92
+ textareaEmphasisStyles
93
+ };
94
+ //# sourceMappingURL=chunk-PQWPMIGH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/form/Textarea/Textarea.styles.ts"],"sourcesContent":["import { alpha, keyframes } from '@mui/material';\nimport type { CSSObject, Theme } from '@mui/material';\n\nconst glowAnimation = keyframes`\n 0% {\n box-shadow: 0 0 5px currentColor;\n }\n 50% {\n box-shadow: 0 0 20px currentColor, 0 0 30px currentColor;\n }\n 100% {\n box-shadow: 0 0 5px currentColor;\n }\n`;\n\n// Ripple animation for buttons (commented out - not currently used)\n// const rippleAnimation = keyframes`\n// 0% {\n// transform: scale(0);\n// opacity: 1;\n// }\n// 100% {\n// transform: scale(4);\n// opacity: 0;\n// }\n// `;\n\n// Float animation for rich text toolbar\nexport const floatAnimation = keyframes`\n 0% {\n transform: translateY(0px);\n }\n 50% {\n transform: translateY(-3px);\n }\n 100% {\n transform: translateY(0px);\n }\n`;\n\ntype ResolvedPalette = {\n main: string;\n dark: string;\n light: string;\n contrastText: string;\n};\n\ntype PartialPalette = { main: string; dark?: string; light?: string; contrastText?: string };\n\n// grey is indexed by weight rather than main/dark/light, so neutral is built by\n// hand. The literals are the MUI grey values, used if the theme omits a weight.\nconst neutralPalette = (theme: Theme): ResolvedPalette => {\n const grey = theme.palette.grey as unknown as Record<number, string>;\n\n return {\n main: grey?.[700] || '#616161',\n dark: grey?.[800] || '#424242',\n light: grey?.[500] || '#9e9e9e',\n contrastText: '#fff',\n };\n};\n\n// Each slot falls back to the palette's own main, then to primary — a custom\n// theme can define main without dark or light.\nconst pick = (...candidates: Array<string | undefined>): string =>\n candidates.find(Boolean) ?? '';\n\nconst withFallbacks = (theme: Theme, palette: PartialPalette): ResolvedPalette => ({\n main: pick(palette?.main, theme.palette.primary.main),\n dark: pick(palette?.dark, palette?.main, theme.palette.primary.dark),\n light: pick(palette?.light, palette?.main, theme.palette.primary.light),\n contrastText: pick(palette?.contrastText, '#fff'),\n});\n\nexport const getColorFromTheme = (theme: Theme, color: string): ResolvedPalette => {\n if (color === 'neutral') return neutralPalette(theme);\n\n const colorMap: Record<string, PartialPalette> = {\n primary: theme.palette.primary,\n secondary: theme.palette.secondary,\n success: theme.palette.success,\n warning: theme.palette.warning,\n info: theme.palette.info,\n danger: theme.palette.error,\n };\n\n return withFallbacks(theme, colorMap[color] || theme.palette.primary);\n};\n\n// glass, gradient and glow are independent flags that each layer styles onto the\n// base. Spelled out here rather than inline so the styled() callback keeps one\n// branch instead of three.\nexport const textareaEmphasisStyles = ({\n theme,\n colorPalette,\n glass,\n gradient,\n glow,\n}: {\n theme: Theme;\n colorPalette: { main: string; light?: string; dark?: string };\n glass?: boolean;\n gradient?: boolean;\n glow?: boolean;\n}): CSSObject => {\nconst glassStyles = glass\n ? {\n backgroundColor: alpha(theme.palette.background.paper, 0.1),\n backdropFilter: 'blur(20px)',\n border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,\n '&:hover': {\n backgroundColor: alpha(theme.palette.background.paper, 0.15),\n backdropFilter: 'blur(25px)',\n },\n '&:focus': {\n backgroundColor: alpha(theme.palette.background.paper, 0.2),\n backdropFilter: 'blur(30px)',\n },\n }\n : {};\n\n// Gradient border effect\nconst gradientStyles = gradient\n ? {\n background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,\n linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.light}) border-box`,\n border: '2px solid transparent',\n '&:focus': {\n background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,\n linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.dark}) border-box`,\n },\n }\n : {};\n\n// Glow effect\nconst glowStyles = glow\n ? {\n animation: `${glowAnimation} 2s ease-in-out infinite`,\n boxShadow: `0 0 10px ${alpha(colorPalette.main, 0.3)}`,\n }\n : {};\n\n return { ...glassStyles, ...gradientStyles, ...glowStyles };\n};\n"],"mappings":";AAAA,SAAS,OAAO,iBAAiB;AAGjC,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBf,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuB9B,IAAM,iBAAiB,CAAC,UAAkC;AACxD,QAAM,OAAO,MAAM,QAAQ;AAE3B,SAAO;AAAA,IACL,MAAM,OAAO,GAAG,KAAK;AAAA,IACrB,MAAM,OAAO,GAAG,KAAK;AAAA,IACrB,OAAO,OAAO,GAAG,KAAK;AAAA,IACtB,cAAc;AAAA,EAChB;AACF;AAIA,IAAM,OAAO,IAAI,eACf,WAAW,KAAK,OAAO,KAAK;AAE9B,IAAM,gBAAgB,CAAC,OAAc,aAA8C;AAAA,EACjF,MAAM,KAAK,SAAS,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAAA,EACpD,MAAM,KAAK,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAAA,EACnE,OAAO,KAAK,SAAS,OAAO,SAAS,MAAM,MAAM,QAAQ,QAAQ,KAAK;AAAA,EACtE,cAAc,KAAK,SAAS,cAAc,MAAM;AAClD;AAEO,IAAM,oBAAoB,CAAC,OAAc,UAAmC;AACjF,MAAI,UAAU,UAAW,QAAO,eAAe,KAAK;AAEpD,QAAM,WAA2C;AAAA,IAC/C,SAAS,MAAM,QAAQ;AAAA,IACvB,WAAW,MAAM,QAAQ;AAAA,IACzB,SAAS,MAAM,QAAQ;AAAA,IACvB,SAAS,MAAM,QAAQ;AAAA,IACvB,MAAM,MAAM,QAAQ;AAAA,IACpB,QAAQ,MAAM,QAAQ;AAAA,EACxB;AAEA,SAAO,cAAc,OAAO,SAAS,KAAK,KAAK,MAAM,QAAQ,OAAO;AACtE;AAKO,IAAM,yBAAyB,CAAC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMiB;AACjB,QAAM,cAAc,QAChB;AAAA,IACE,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,GAAG;AAAA,IAC1D,gBAAgB;AAAA,IAChB,QAAQ,aAAa,MAAM,MAAM,QAAQ,SAAS,GAAG,CAAC;AAAA,IACtD,WAAW;AAAA,MACT,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,IAAI;AAAA,MAC3D,gBAAgB;AAAA,IAClB;AAAA,IACA,WAAW;AAAA,MACT,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,GAAG;AAAA,MAC1D,gBAAgB;AAAA,IAClB;AAAA,EACF,IACA,CAAC;AAGL,QAAM,iBAAiB,WACnB;AAAA,IACE,YAAY,mBAAmB,MAAM,QAAQ,WAAW,KAAK,KAAK,MAAM,QAAQ,WAAW,KAAK;AAAA,yCAC7D,aAAa,IAAI,KAAK,aAAa,KAAK;AAAA,IAC3E,QAAQ;AAAA,IACR,WAAW;AAAA,MACT,YAAY,mBAAmB,MAAM,QAAQ,WAAW,KAAK,KAAK,MAAM,QAAQ,WAAW,KAAK;AAAA,2CAC7D,aAAa,IAAI,KAAK,aAAa,IAAI;AAAA,IAC5E;AAAA,EACF,IACA,CAAC;AAGL,QAAM,aAAa,OACf;AAAA,IACE,WAAW,GAAG,aAAa;AAAA,IAC3B,WAAW,YAAY,MAAM,aAAa,MAAM,GAAG,CAAC;AAAA,EACtD,IACA,CAAC;AAEH,SAAO,EAAE,GAAG,aAAa,GAAG,gBAAgB,GAAG,WAAW;AAC5D;","names":[]}
@@ -22,7 +22,7 @@ import {
22
22
  } from "../chunk-S4R6CKIG.js";
23
23
  import {
24
24
  DayBoundInput
25
- } from "../chunk-TSG5C7MP.js";
25
+ } from "../chunk-FKWNB4LW.js";
26
26
  import {
27
27
  CategorySelect
28
28
  } from "../chunk-AR5LUIBC.js";
@@ -5,8 +5,8 @@ import {
5
5
  } from "../chunk-KXARS46L.js";
6
6
  import {
7
7
  DayBoundInput,
8
- isoToBr
9
- } from "../chunk-TSG5C7MP.js";
8
+ isoToMasked
9
+ } from "../chunk-FKWNB4LW.js";
10
10
  import "../chunk-PZ5AY32C.js";
11
11
 
12
12
  // src/components/form/DateRangePicker/DateRangePicker.tsx
@@ -104,6 +104,7 @@ function resolveDayRange(draft, maxRangeDays) {
104
104
  return { ok: true, window: { from, to }, days };
105
105
  }
106
106
  function defaultMessages() {
107
+ const dayMask = "dd/mm/yyyy";
107
108
  return {
108
109
  from: "Start date",
109
110
  to: "End date",
@@ -111,9 +112,9 @@ function defaultMessages() {
111
112
  incomplete: "Choose both dates.",
112
113
  reversed: "The end date must be on or after the start date.",
113
114
  overMax: ({ maxRangeDays }) => `Choose a range of at most ${maxRangeDays} days.`,
114
- summary: ({ from, to }) => `${isoToBr(from)} \u2013 ${isoToBr(to)}`,
115
+ summary: ({ from, to }) => `${isoToMasked(from, dayMask)} \u2013 ${isoToMasked(to, dayMask)}`,
115
116
  calendarLabel: "Calendar",
116
- dayMask: "dd/mm/yyyy"
117
+ dayMask
117
118
  };
118
119
  }
119
120
  function statusMessage(status, messages, maxRangeDays) {