@12-apps/ui 6.10.0 → 6.10.1

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,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":[]}
@@ -1,401 +1,24 @@
1
+ import {
2
+ getColorFromTheme,
3
+ textareaEmphasisStyles
4
+ } from "../chunk-PQWPMIGH.js";
1
5
  import "../chunk-PZ5AY32C.js";
2
6
 
3
7
  // src/components/form/Textarea/Textarea.tsx
4
8
  import {
5
- alpha as alpha3,
6
- Box as Box2,
9
+ alpha,
10
+ Box,
7
11
  FormHelperText,
8
12
  InputLabel,
9
13
  TextareaAutosize
10
14
  } from "@mui/material";
11
- import { styled as styled2 } from "@mui/material";
12
- import { useState as useState2 } from "react";
13
-
14
- // src/components/form/Textarea/Textarea.styles.ts
15
- import { alpha, keyframes } from "@mui/material";
16
- var glowAnimation = keyframes`
17
- 0% {
18
- box-shadow: 0 0 5px currentColor;
19
- }
20
- 50% {
21
- box-shadow: 0 0 20px currentColor, 0 0 30px currentColor;
22
- }
23
- 100% {
24
- box-shadow: 0 0 5px currentColor;
25
- }
26
- `;
27
- var floatAnimation = keyframes`
28
- 0% {
29
- transform: translateY(0px);
30
- }
31
- 50% {
32
- transform: translateY(-3px);
33
- }
34
- 100% {
35
- transform: translateY(0px);
36
- }
37
- `;
38
- var neutralPalette = (theme) => {
39
- const grey = theme.palette.grey;
40
- return {
41
- main: grey?.[700] || "#616161",
42
- dark: grey?.[800] || "#424242",
43
- light: grey?.[500] || "#9e9e9e",
44
- contrastText: "#fff"
45
- };
46
- };
47
- var pick = (...candidates) => candidates.find(Boolean) ?? "";
48
- var withFallbacks = (theme, palette) => ({
49
- main: pick(palette?.main, theme.palette.primary.main),
50
- dark: pick(palette?.dark, palette?.main, theme.palette.primary.dark),
51
- light: pick(palette?.light, palette?.main, theme.palette.primary.light),
52
- contrastText: pick(palette?.contrastText, "#fff")
53
- });
54
- var getColorFromTheme = (theme, color) => {
55
- if (color === "neutral") return neutralPalette(theme);
56
- const colorMap = {
57
- primary: theme.palette.primary,
58
- secondary: theme.palette.secondary,
59
- success: theme.palette.success,
60
- warning: theme.palette.warning,
61
- info: theme.palette.info,
62
- danger: theme.palette.error
63
- };
64
- return withFallbacks(theme, colorMap[color] || theme.palette.primary);
65
- };
66
- var textareaEmphasisStyles = ({
67
- theme,
68
- colorPalette,
69
- glass,
70
- gradient,
71
- glow
72
- }) => {
73
- const glassStyles = glass ? {
74
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
75
- backdropFilter: "blur(20px)",
76
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
77
- "&:hover": {
78
- backgroundColor: alpha(theme.palette.background.paper, 0.15),
79
- backdropFilter: "blur(25px)"
80
- },
81
- "&:focus": {
82
- backgroundColor: alpha(theme.palette.background.paper, 0.2),
83
- backdropFilter: "blur(30px)"
84
- }
85
- } : {};
86
- const gradientStyles = gradient ? {
87
- background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,
88
- linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.light}) border-box`,
89
- border: "2px solid transparent",
90
- "&:focus": {
91
- background: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}) padding-box,
92
- linear-gradient(135deg, ${colorPalette.main}, ${colorPalette.dark}) border-box`
93
- }
94
- } : {};
95
- const glowStyles = glow ? {
96
- animation: `${glowAnimation} 2s ease-in-out infinite`,
97
- boxShadow: `0 0 10px ${alpha(colorPalette.main, 0.3)}`
98
- } : {};
99
- return { ...glassStyles, ...gradientStyles, ...glowStyles };
100
- };
101
-
102
- // src/components/form/Textarea/TextareaRichEditor.tsx
103
- import Code from "@mui/icons-material/Code";
104
- import FormatBold from "@mui/icons-material/FormatBold";
105
- import FormatColorFill from "@mui/icons-material/FormatColorFill";
106
- import FormatColorText from "@mui/icons-material/FormatColorText";
107
- import FormatItalic from "@mui/icons-material/FormatItalic";
108
- import FormatListBulleted from "@mui/icons-material/FormatListBulleted";
109
- import FormatListNumbered from "@mui/icons-material/FormatListNumbered";
110
- import FormatQuote from "@mui/icons-material/FormatQuote";
111
- import FormatUnderlined from "@mui/icons-material/FormatUnderlined";
112
- import Link from "@mui/icons-material/Link";
113
- import { alpha as alpha2, Box, Divider, IconButton, Tooltip } from "@mui/material";
114
15
  import { styled } from "@mui/material";
115
- import { useEffect, useRef, useState } from "react";
116
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
117
- var RichToolbar = styled(Box)(({ theme, glass }) => ({
118
- display: "flex",
119
- alignItems: "center",
120
- gap: theme.spacing(0.5),
121
- padding: theme.spacing(1),
122
- borderRadius: `${theme.spacing(1)} ${theme.spacing(1)} 0 0`,
123
- borderBottom: `1px solid ${theme.palette.divider}`,
124
- background: glass ? alpha2(theme.palette.background.paper, 0.1) : theme.palette.background.paper,
125
- ...glass && {
126
- backdropFilter: "blur(15px)"
127
- },
128
- "& .MuiDivider-root": {
129
- height: 24,
130
- margin: `0 ${theme.spacing(1)}`
131
- }
132
- }));
133
- var ToolbarButton = styled(IconButton, {
134
- shouldForwardProp: (prop) => prop !== "active"
135
- })(({ theme, active }) => ({
136
- padding: theme.spacing(0.75),
137
- borderRadius: theme.spacing(0.5),
138
- color: active ? theme.palette.primary.main : theme.palette.text.secondary,
139
- backgroundColor: active ? alpha2(theme.palette.primary.main, 0.1) : "transparent",
140
- transition: "all 0.2s ease",
141
- position: "relative",
142
- overflow: "hidden",
143
- "&::before": {
144
- content: '""',
145
- position: "absolute",
146
- top: "50%",
147
- left: "50%",
148
- width: 0,
149
- height: 0,
150
- borderRadius: "50%",
151
- backgroundColor: alpha2(theme.palette.primary.main, 0.3),
152
- transform: "translate(-50%, -50%)",
153
- transition: "width 0.3s, height 0.3s"
154
- },
155
- "&:hover": {
156
- color: theme.palette.primary.main,
157
- backgroundColor: alpha2(theme.palette.primary.main, 0.15),
158
- transform: "translateY(-1px)",
159
- animation: `${floatAnimation} 1.5s ease-in-out infinite`,
160
- "&::before": {
161
- width: "100%",
162
- height: "100%"
163
- }
164
- },
165
- "&:active": {
166
- transform: "scale(0.95)"
167
- }
16
+ import { lazy, Suspense, useState } from "react";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ var TextareaRichEditor = lazy(async () => ({
19
+ default: (await import("../TextareaRichEditor-ZFB44VWZ.js")).TextareaRichEditor
168
20
  }));
169
- var CharacterCount = styled(Box)(({ theme, limit, count }) => {
170
- const isWarning = limit && count > limit * 0.8;
171
- const isError = limit && count > limit;
172
- return {
173
- position: "absolute",
174
- bottom: theme.spacing(1),
175
- right: theme.spacing(1),
176
- fontSize: "0.75rem",
177
- color: isError ? theme.palette.error.main : isWarning ? theme.palette.warning.main : theme.palette.text.secondary,
178
- padding: "2px 6px",
179
- borderRadius: theme.spacing(0.5),
180
- backgroundColor: alpha2(theme.palette.background.paper, 0.8),
181
- backdropFilter: "blur(10px)",
182
- transition: "all 0.3s ease"
183
- };
184
- });
185
- var ContentEditableDiv = styled("div")(({ theme, error, focused, glass, customColor = "primary" }) => {
186
- if (!theme) return {};
187
- const colorPalette = getColorFromTheme(theme, customColor);
188
- const errorColor = theme.palette.error;
189
- return {
190
- minHeight: "120px",
191
- padding: theme.spacing(1.5),
192
- borderRadius: `0 0 ${theme.spacing(1)} ${theme.spacing(1)}`,
193
- border: `2px solid ${error ? errorColor.main : focused ? colorPalette.main : theme.palette.divider}`,
194
- borderTop: "none",
195
- backgroundColor: glass ? alpha2(theme.palette.background.paper, 0.1) : theme.palette.background.paper,
196
- color: theme.palette.text.primary,
197
- fontFamily: theme.typography.fontFamily,
198
- fontSize: "1rem",
199
- lineHeight: 1.5,
200
- outline: "none",
201
- transition: "all 0.3s ease",
202
- cursor: "text",
203
- ...glass && {
204
- backdropFilter: "blur(20px)"
205
- },
206
- "&:hover": {
207
- backgroundColor: glass ? alpha2(theme.palette.background.paper, 0.15) : alpha2(theme.palette.background.paper, 0.9)
208
- },
209
- "&:focus": {
210
- borderColor: error ? errorColor.main : colorPalette.main,
211
- boxShadow: `0 0 0 3px ${alpha2(error ? errorColor.main : colorPalette.main, 0.1)}`
212
- },
213
- "& > *": {
214
- margin: "0.5em 0"
215
- },
216
- '&[contenteditable="true"]:empty::before': {
217
- content: "attr(data-placeholder)",
218
- color: theme.palette.text.secondary,
219
- opacity: 0.6
220
- }
221
- };
222
- });
223
- var formatText = (command, value) => {
224
- document.execCommand(command, false, value);
225
- };
226
- var useRichEditorState = ({
227
- value,
228
- onChange
229
- }) => {
230
- const [focused, setFocused] = useState(false);
231
- const [characterCount, setCharacterCount] = useState(0);
232
- const [activeFormats, setActiveFormats] = useState({
233
- bold: false,
234
- italic: false,
235
- underline: false,
236
- list: false,
237
- orderedList: false,
238
- quote: false,
239
- code: false
240
- });
241
- const contentRef = useRef(null);
242
- useEffect(() => {
243
- if (contentRef.current && value !== void 0) {
244
- if (contentRef.current.innerHTML !== value) {
245
- contentRef.current.innerHTML = value;
246
- }
247
- setCharacterCount(contentRef.current.innerText.length);
248
- }
249
- }, [value]);
250
- const handleInput = () => {
251
- if (contentRef.current) {
252
- const html = contentRef.current.innerHTML;
253
- onChange?.(html);
254
- setCharacterCount(contentRef.current.innerText.length);
255
- updateActiveFormats();
256
- }
257
- };
258
- const updateActiveFormats = () => {
259
- setActiveFormats({
260
- bold: document.queryCommandState("bold"),
261
- italic: document.queryCommandState("italic"),
262
- underline: document.queryCommandState("underline"),
263
- list: document.queryCommandState("insertUnorderedList"),
264
- orderedList: document.queryCommandState("insertOrderedList"),
265
- quote: false,
266
- code: false
267
- });
268
- };
269
- const handleFormat = (command, value2) => {
270
- formatText(command, value2);
271
- updateActiveFormats();
272
- contentRef.current?.focus();
273
- };
274
- return {
275
- focused,
276
- setFocused,
277
- characterCount,
278
- activeFormats,
279
- contentRef,
280
- handleInput,
281
- handleFormat,
282
- updateActiveFormats
283
- };
284
- };
285
- var InlineFormatButtons = ({ copy, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(Fragment, { children: [
286
- /* @__PURE__ */ jsx(Tooltip, { title: copy.bold, arrow: true, children: /* @__PURE__ */ jsx(
287
- ToolbarButton,
288
- {
289
- size: "small",
290
- active: activeFormats.bold,
291
- onClick: () => onCommand("bold"),
292
- children: /* @__PURE__ */ jsx(FormatBold, { fontSize: "small" })
293
- }
294
- ) }),
295
- /* @__PURE__ */ jsx(Tooltip, { title: copy.italic, arrow: true, children: /* @__PURE__ */ jsx(
296
- ToolbarButton,
297
- {
298
- size: "small",
299
- active: activeFormats.italic,
300
- onClick: () => onCommand("italic"),
301
- children: /* @__PURE__ */ jsx(FormatItalic, { fontSize: "small" })
302
- }
303
- ) }),
304
- /* @__PURE__ */ jsx(Tooltip, { title: copy.underline, arrow: true, children: /* @__PURE__ */ jsx(
305
- ToolbarButton,
306
- {
307
- size: "small",
308
- active: activeFormats.underline,
309
- onClick: () => onCommand("underline"),
310
- children: /* @__PURE__ */ jsx(FormatUnderlined, { fontSize: "small" })
311
- }
312
- ) })
313
- ] });
314
- var BlockFormatButtons = ({ copy, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(Fragment, { children: [
315
- /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
316
- /* @__PURE__ */ jsx(Tooltip, { title: copy.bulletList, arrow: true, children: /* @__PURE__ */ jsx(
317
- ToolbarButton,
318
- {
319
- size: "small",
320
- active: activeFormats.list,
321
- onClick: () => onCommand("insertUnorderedList"),
322
- children: /* @__PURE__ */ jsx(FormatListBulleted, { fontSize: "small" })
323
- }
324
- ) }),
325
- /* @__PURE__ */ jsx(Tooltip, { title: copy.numberedList, arrow: true, children: /* @__PURE__ */ jsx(
326
- ToolbarButton,
327
- {
328
- size: "small",
329
- active: activeFormats.orderedList,
330
- onClick: () => onCommand("insertOrderedList"),
331
- children: /* @__PURE__ */ jsx(FormatListNumbered, { fontSize: "small" })
332
- }
333
- ) }),
334
- /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
335
- /* @__PURE__ */ jsx(Tooltip, { title: copy.quote, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", onClick: () => onCommand("formatBlock", "blockquote"), children: /* @__PURE__ */ jsx(FormatQuote, { fontSize: "small" }) }) }),
336
- /* @__PURE__ */ jsx(Tooltip, { title: copy.code, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", onClick: () => onCommand("formatBlock", "pre"), children: /* @__PURE__ */ jsx(Code, { fontSize: "small" }) }) }),
337
- /* @__PURE__ */ jsx(Divider, { orientation: "vertical", flexItem: true }),
338
- /* @__PURE__ */ jsx(Tooltip, { title: copy.insertLink, arrow: true, children: /* @__PURE__ */ jsx(
339
- ToolbarButton,
340
- {
341
- size: "small",
342
- onClick: () => {
343
- const url = window.prompt(copy.linkPrompt);
344
- if (url) onCommand("createLink", url);
345
- },
346
- children: /* @__PURE__ */ jsx(Link, { fontSize: "small" })
347
- }
348
- ) }),
349
- /* @__PURE__ */ jsx(Tooltip, { title: copy.textColor, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", children: /* @__PURE__ */ jsx(FormatColorText, { fontSize: "small" }) }) }),
350
- /* @__PURE__ */ jsx(Tooltip, { title: copy.backgroundColor, arrow: true, children: /* @__PURE__ */ jsx(ToolbarButton, { size: "small", children: /* @__PURE__ */ jsx(FormatColorFill, { fontSize: "small" }) }) })
351
- ] });
352
- var EditorToolbar = ({ copy, glass, activeFormats, onCommand }) => /* @__PURE__ */ jsxs(RichToolbar, { glass, children: [
353
- /* @__PURE__ */ jsx(InlineFormatButtons, { copy, activeFormats, onCommand }),
354
- /* @__PURE__ */ jsx(BlockFormatButtons, { copy, activeFormats, onCommand })
355
- ] });
356
- var TextareaRichEditor = ({ copy, value, onChange, placeholder, error, glass, color, characterLimit }) => {
357
- const {
358
- focused,
359
- setFocused,
360
- characterCount,
361
- activeFormats,
362
- contentRef,
363
- handleInput,
364
- handleFormat,
365
- updateActiveFormats
366
- } = useRichEditorState({ value, onChange });
367
- return /* @__PURE__ */ jsxs(Box, { sx: { position: "relative" }, children: [
368
- /* @__PURE__ */ jsx(EditorToolbar, { copy, glass, activeFormats, onCommand: handleFormat }),
369
- /* @__PURE__ */ jsx(
370
- ContentEditableDiv,
371
- {
372
- ref: contentRef,
373
- contentEditable: true,
374
- "data-placeholder": placeholder,
375
- error,
376
- focused,
377
- glass,
378
- customColor: color,
379
- onFocus: () => setFocused(true),
380
- onBlur: () => setFocused(false),
381
- onInput: handleInput,
382
- onKeyUp: updateActiveFormats,
383
- onMouseUp: updateActiveFormats,
384
- role: "textbox",
385
- "aria-multiline": "true",
386
- "aria-label": placeholder || "Rich text editor"
387
- }
388
- ),
389
- characterLimit && /* @__PURE__ */ jsxs(CharacterCount, { limit: characterLimit, count: characterCount, children: [
390
- characterCount,
391
- characterLimit ? `/${characterLimit}` : ""
392
- ] })
393
- ] });
394
- };
395
-
396
- // src/components/form/Textarea/Textarea.tsx
397
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
398
- var StyledTextarea = styled2(TextareaAutosize, {
21
+ var StyledTextarea = styled(TextareaAutosize, {
399
22
  shouldForwardProp: (prop) => !["customVariant", "customColor", "customSize", "glow", "glass", "gradient", "error"].includes(
400
23
  prop
401
24
  )
@@ -436,12 +59,12 @@ var StyledTextarea = styled2(TextareaAutosize, {
436
59
  },
437
60
  "&:hover": {
438
61
  borderColor: error ? errorColor.dark : colorPalette.main,
439
- backgroundColor: alpha3(theme.palette.background.paper, 0.8)
62
+ backgroundColor: alpha(theme.palette.background.paper, 0.8)
440
63
  },
441
64
  "&:focus": {
442
65
  borderColor: error ? errorColor.main : colorPalette.main,
443
66
  backgroundColor: theme.palette.background.paper,
444
- boxShadow: `0 0 0 3px ${alpha3(error ? errorColor.main : colorPalette.main, 0.1)}`
67
+ boxShadow: `0 0 0 3px ${alpha(error ? errorColor.main : colorPalette.main, 0.1)}`
445
68
  }
446
69
  };
447
70
  return {
@@ -449,18 +72,18 @@ var StyledTextarea = styled2(TextareaAutosize, {
449
72
  ...textareaEmphasisStyles({ theme, colorPalette, glass, gradient, glow })
450
73
  };
451
74
  });
452
- var StyledLabel = styled2(InputLabel, {
75
+ var StyledLabel = styled(InputLabel, {
453
76
  shouldForwardProp: (prop) => !["glass", "error"].includes(prop)
454
77
  })(({ theme, glass, error }) => ({
455
78
  marginBottom: theme.spacing(1),
456
79
  fontWeight: 500,
457
80
  color: error ? theme.palette.error.main : theme.palette.text.primary,
458
81
  ...glass && {
459
- backgroundColor: alpha3(theme.palette.background.paper, 0.1),
82
+ backgroundColor: alpha(theme.palette.background.paper, 0.1),
460
83
  backdropFilter: "blur(10px)",
461
84
  padding: "4px 8px",
462
85
  borderRadius: "4px",
463
- border: `1px solid ${alpha3(theme.palette.divider, 0.2)}`,
86
+ border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
464
87
  display: "inline-block"
465
88
  }
466
89
  }));
@@ -480,20 +103,20 @@ var TEXTAREA_DEFAULTS = {
480
103
  var definedProps = (props) => Object.fromEntries(
481
104
  Object.entries(props).filter(([, value]) => value !== void 0)
482
105
  );
483
- var TextareaShell = ({ testId, label, glassLabel, error, helperText, children }) => /* @__PURE__ */ jsxs2(
484
- Box2,
106
+ var TextareaShell = ({ testId, label, glassLabel, error, helperText, children }) => /* @__PURE__ */ jsxs(
107
+ Box,
485
108
  {
486
109
  sx: { position: "relative", width: "100%" },
487
110
  "data-testid": testId ? `${testId}-wrapper` : void 0,
488
111
  children: [
489
- label && /* @__PURE__ */ jsx2(StyledLabel, { glass: glassLabel, error, children: label }),
112
+ label && /* @__PURE__ */ jsx(StyledLabel, { glass: glassLabel, error, children: label }),
490
113
  children,
491
- helperText && /* @__PURE__ */ jsx2(FormHelperText, { error, sx: { mt: 1 }, children: helperText })
114
+ helperText && /* @__PURE__ */ jsx(FormHelperText, { error, sx: { mt: 1 }, children: helperText })
492
115
  ]
493
116
  }
494
117
  );
495
- var TextareaIcon = ({ icon, iconPosition }) => /* @__PURE__ */ jsx2(
496
- Box2,
118
+ var TextareaIcon = ({ icon, iconPosition }) => /* @__PURE__ */ jsx(
119
+ Box,
497
120
  {
498
121
  sx: {
499
122
  position: "absolute",
@@ -506,6 +129,12 @@ var TextareaIcon = ({ icon, iconPosition }) => /* @__PURE__ */ jsx2(
506
129
  children: icon
507
130
  }
508
131
  );
132
+ function RichVariant({
133
+ shell,
134
+ editor
135
+ }) {
136
+ return /* @__PURE__ */ jsx(TextareaShell, { ...shell, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(TextareaRichEditor, { ...editor }) }) });
137
+ }
509
138
  var Textarea = (textareaProps) => {
510
139
  const {
511
140
  variant,
@@ -528,7 +157,7 @@ var Textarea = (textareaProps) => {
528
157
  ...props
529
158
  } = { ...TEXTAREA_DEFAULTS, ...definedProps(textareaProps) };
530
159
  const { richEditorCopy } = textareaProps;
531
- const [richTextValue, setRichTextValue] = useState2("");
160
+ const [richTextValue, setRichTextValue] = useState("");
532
161
  const hasIcon = Boolean(icon);
533
162
  const testId = dataTestId || dataTestIdCamelCase;
534
163
  const textareaStyle = {
@@ -541,23 +170,26 @@ var Textarea = (textareaProps) => {
541
170
  }
542
171
  };
543
172
  if (variant === "rich" && richEditorCopy) {
544
- return /* @__PURE__ */ jsx2(TextareaShell, { testId, label, glassLabel, error, helperText, children: /* @__PURE__ */ jsx2(
545
- TextareaRichEditor,
173
+ return /* @__PURE__ */ jsx(
174
+ RichVariant,
546
175
  {
547
- copy: richEditorCopy,
548
- value: richTextValue,
549
- onChange: setRichTextValue,
550
- placeholder: props.placeholder,
551
- error,
552
- glass,
553
- color,
554
- characterLimit: props.maxLength
176
+ shell: { testId, label, glassLabel, error, helperText },
177
+ editor: {
178
+ copy: richEditorCopy,
179
+ value: richTextValue,
180
+ onChange: setRichTextValue,
181
+ placeholder: props.placeholder,
182
+ error,
183
+ glass,
184
+ color,
185
+ characterLimit: props.maxLength
186
+ }
555
187
  }
556
- ) });
188
+ );
557
189
  }
558
- return /* @__PURE__ */ jsx2(TextareaShell, { testId, label, glassLabel, error, helperText, children: /* @__PURE__ */ jsxs2(Box2, { sx: { position: "relative" }, children: [
559
- hasIcon && /* @__PURE__ */ jsx2(TextareaIcon, { icon, iconPosition }),
560
- /* @__PURE__ */ jsx2(
190
+ return /* @__PURE__ */ jsx(TextareaShell, { testId, label, glassLabel, error, helperText, children: /* @__PURE__ */ jsxs(Box, { sx: { position: "relative" }, children: [
191
+ hasIcon && /* @__PURE__ */ jsx(TextareaIcon, { icon, iconPosition }),
192
+ /* @__PURE__ */ jsx(
561
193
  StyledTextarea,
562
194
  {
563
195
  customVariant: variant,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/form/Textarea/Textarea.tsx","../../src/components/form/Textarea/Textarea.styles.ts","../../src/components/form/Textarea/TextareaRichEditor.tsx"],"sourcesContent":["import {\n alpha,\n Box,\n FormHelperText,\n InputLabel,\n TextareaAutosize } from '@mui/material';\nimport type { Theme } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React, { useState } from 'react';\n\nimport {\n getColorFromTheme,\n textareaEmphasisStyles } from './Textarea.styles';\nimport { TextareaRichEditor } from './TextareaRichEditor';\nimport type { TextareaProps } from './Textarea.types';\n\n// Interface for styled component props\ninterface StyledTextareaProps {\n customVariant?: string;\n customColor?: string;\n customSize?: string;\n glow?: boolean;\n glass?: boolean;\n gradient?: boolean;\n error?: boolean;\n theme?: Theme;\n}\n\n// Glow animation for enhanced visual effects\nconst StyledTextarea = styled(TextareaAutosize, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'customColor', 'customSize', 'glow', 'glass', 'gradient', 'error'].includes(\n prop as string,\n ) })<StyledTextareaProps>(({\n theme,\n customVariant,\n customColor = 'primary',\n customSize = 'md',\n glow,\n glass,\n gradient,\n error }) => {\n if (!theme) return {};\n const colorPalette = getColorFromTheme(theme, customColor);\n const errorColor = theme.palette.error;\n\n const sizeMap = {\n xs: { padding: '6px 8px', fontSize: '0.75rem', minHeight: '60px' },\n sm: { padding: '8px 10px', fontSize: '0.875rem', minHeight: '80px' },\n md: { padding: '10px 12px', fontSize: '1rem', minHeight: '100px' },\n lg: { padding: '12px 14px', fontSize: '1.125rem', minHeight: '120px' },\n xl: { padding: '14px 16px', fontSize: '1.25rem', minHeight: '140px' } };\n\n const baseStyles = {\n width: '100%',\n fontFamily: theme.typography.fontFamily,\n borderRadius: theme.spacing(1),\n border: `2px solid ${error ? errorColor.main : theme.palette.divider}`,\n backgroundColor: theme.palette.background.paper,\n color: theme.palette.text.primary,\n transition: 'all 0.3s ease',\n resize: customVariant === 'resizable' ? 'vertical' : 'none',\n outline: 'none',\n ...sizeMap[customSize as keyof typeof sizeMap],\n\n '&::placeholder': {\n color: theme.palette.text.secondary,\n opacity: 0.7 },\n\n '&:hover': {\n borderColor: error ? errorColor.dark : colorPalette.main,\n backgroundColor: alpha(theme.palette.background.paper, 0.8) },\n\n '&:focus': {\n borderColor: error ? errorColor.main : colorPalette.main,\n backgroundColor: theme.palette.background.paper,\n boxShadow: `0 0 0 3px ${alpha(error ? errorColor.main : colorPalette.main, 0.1)}` } };\n\n // Glass morphism effect\n return {\n ...baseStyles,\n ...textareaEmphasisStyles({ theme, colorPalette, glass, gradient, glow }) };\n});\n\nconst StyledLabel = styled(InputLabel, {\n shouldForwardProp: (prop) => !['glass', 'error'].includes(prop as string) })<{ glass?: boolean; error?: boolean }>(({ theme, glass, error }) => ({\n marginBottom: theme.spacing(1),\n fontWeight: 500,\n color: error ? theme.palette.error.main : theme.palette.text.primary,\n ...(glass && {\n backgroundColor: alpha(theme.palette.background.paper, 0.1),\n backdropFilter: 'blur(10px)',\n padding: '4px 8px',\n borderRadius: '4px',\n border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,\n display: 'inline-block' }) }));\n\n// IconWrapper - replaced with inline Box component for better TypeScript compatibility\n// const IconWrapper = styled(Box)<{ position: 'start' | 'end' }>(({ theme, position }) => {\n// const positionStyles = position === 'start' ? { left: '12px' } : { right: '12px' };\n// return {\n// position: 'absolute' as const,\n// top: '12px',\n// ...positionStyles,\n// color: theme.palette.text.secondary,\n// pointerEvents: 'none' as const,\n// zIndex: 1,\n// };\n// });\n\n// Rich text toolbar styling\n\nconst ICON_GUTTER = '40px';\n\nconst TEXTAREA_DEFAULTS = {\n variant: 'default',\n color: 'primary',\n size: 'md',\n error: false,\n glassLabel: false,\n glow: false,\n glass: false,\n gradient: false,\n iconPosition: 'start',\n minRows: 3 } satisfies Partial<TextareaProps>;\n\n// Strips explicitly-undefined props before the merge, so `prop={undefined}`\n// still falls back to the default as a destructuring default would.\nconst definedProps = (props: TextareaProps): Partial<TextareaProps> =>\n Object.fromEntries(\n Object.entries(props).filter(([, value]) => value !== undefined),\n ) as Partial<TextareaProps>;\n\n// Label above, helper text below — identical for the plain and rich variants,\n// which previously duplicated both.\nconst TextareaShell: React.FC<{\n testId?: string;\n label?: React.ReactNode;\n glassLabel: boolean;\n error: boolean;\n helperText?: React.ReactNode;\n children: React.ReactNode;\n}> = ({ testId, label, glassLabel, error, helperText, children }) => (\n <Box\n sx={{ position: 'relative', width: '100%' }}\n data-testid={testId ? `${testId}-wrapper` : undefined}\n >\n {label && (\n <StyledLabel glass={glassLabel} error={error}>\n {label}\n </StyledLabel>\n )}\n\n {children}\n\n {helperText && (\n <FormHelperText error={error} sx={{ mt: 1 }}>\n {helperText}\n </FormHelperText>\n )}\n </Box>\n);\n\nconst TextareaIcon: React.FC<{\n icon?: React.ReactNode;\n iconPosition: 'start' | 'end';\n}> = ({ icon, iconPosition }) => (\n <Box\n sx={{\n position: 'absolute',\n top: '12px',\n ...(iconPosition === 'start' ? { left: '12px' } : { right: '12px' }),\n color: 'text.secondary',\n pointerEvents: 'none',\n zIndex: 1 }}\n >\n {icon}\n </Box>\n);\n\nexport const Textarea: React.FC<TextareaProps> = (textareaProps) => {\n const {\n variant,\n color,\n size,\n error,\n helperText,\n label,\n glassLabel,\n glow,\n glass,\n gradient,\n icon,\n iconPosition,\n minRows,\n maxRows,\n style,\n 'data-testid': dataTestId,\n dataTestId: dataTestIdCamelCase,\n ...props\n } = { ...TEXTAREA_DEFAULTS, ...definedProps(textareaProps) };\n\n // Read off the props rather than the merged object: the spread widens every\n // key to optional, which would lose what the variant union already proved.\n const { richEditorCopy } = textareaProps;\n\n const [richTextValue, setRichTextValue] = useState('');\n const hasIcon = Boolean(icon);\n const testId = dataTestId || dataTestIdCamelCase;\n\n const textareaStyle = {\n ...style,\n // Same gap either side; only which side it applies to changes. The previous\n // version had `iconPosition === 'start' ? '40px' : '40px'`, a ternary whose\n // two branches were identical.\n ...(hasIcon && {\n [iconPosition === 'start' ? 'paddingLeft' : 'paddingRight']: ICON_GUTTER }) };\n\n // If rich text variant, use the rich text editor\n if (variant === 'rich' && richEditorCopy) {\n return (\n <TextareaShell testId={testId} label={label} glassLabel={glassLabel} error={error} helperText={helperText}>\n <TextareaRichEditor\n copy={richEditorCopy}\n value={richTextValue}\n onChange={setRichTextValue}\n placeholder={props.placeholder}\n error={error}\n glass={glass}\n color={color}\n characterLimit={props.maxLength}\n />\n </TextareaShell>\n );\n }\n\n return (\n <TextareaShell testId={testId} label={label} glassLabel={glassLabel} error={error} helperText={helperText}>\n <Box sx={{ position: 'relative' }}>\n {hasIcon && <TextareaIcon icon={icon} iconPosition={iconPosition} />}\n\n <StyledTextarea\n customVariant={variant}\n customColor={color}\n customSize={size}\n glow={glow}\n glass={glass}\n gradient={gradient}\n error={error}\n minRows={variant === 'autosize' ? minRows : undefined}\n maxRows={variant === 'autosize' ? maxRows : undefined}\n style={textareaStyle}\n aria-label={label || props['aria-label']}\n data-testid={testId}\n {...props}\n />\n </Box>\n </TextareaShell>\n );\n};\n\nTextarea.displayName = 'Textarea';\n","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","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":["alpha","Box","styled","useState","alpha","alpha","value","jsx","jsxs","styled","alpha","Box","useState"]}
1
+ {"version":3,"sources":["../../src/components/form/Textarea/Textarea.tsx"],"sourcesContent":["import {\n alpha,\n Box,\n FormHelperText,\n InputLabel,\n TextareaAutosize } from '@mui/material';\nimport type { Theme } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React, { lazy, Suspense, useState } from 'react';\n\nimport {\n getColorFromTheme,\n textareaEmphasisStyles } from './Textarea.styles';\n\n/**\n * The rich editor is LOADED ON DEMAND, not imported (12-51 follow-up).\n *\n * It renders only for `variant === 'rich'`, but a static import made every\n * consumer of a plain `<Textarea>` pay for it — 387 lines of toolbar plus the\n * MUI `Tooltip`, `IconButton` and `Divider` its formatting buttons need. That\n * is not theoretical: an adopting storefront imports this entry for a one-line\n * order note, and `Tooltip` (8.7 KiB) reached its EAGER chunk through here\n * while nothing on that critical path renders a tooltip at all.\n *\n * `variant` is a prop, so the branch cannot be decided at build time and no\n * amount of tree-shaking removes it. A lazy boundary is what expresses \"only\n * the rich variant pays\", and it costs the rich variant one microtask it can\n * afford — it is a text editor a user has already chosen to open, never a\n * first-paint element.\n */\nconst TextareaRichEditor = lazy(async () => ({\n default: (await import('./TextareaRichEditor')).TextareaRichEditor }));\nimport type { TextareaProps } from './Textarea.types';\n\n// Interface for styled component props\ninterface StyledTextareaProps {\n customVariant?: string;\n customColor?: string;\n customSize?: string;\n glow?: boolean;\n glass?: boolean;\n gradient?: boolean;\n error?: boolean;\n theme?: Theme;\n}\n\n// Glow animation for enhanced visual effects\nconst StyledTextarea = styled(TextareaAutosize, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'customColor', 'customSize', 'glow', 'glass', 'gradient', 'error'].includes(\n prop as string,\n ) })<StyledTextareaProps>(({\n theme,\n customVariant,\n customColor = 'primary',\n customSize = 'md',\n glow,\n glass,\n gradient,\n error }) => {\n if (!theme) return {};\n const colorPalette = getColorFromTheme(theme, customColor);\n const errorColor = theme.palette.error;\n\n const sizeMap = {\n xs: { padding: '6px 8px', fontSize: '0.75rem', minHeight: '60px' },\n sm: { padding: '8px 10px', fontSize: '0.875rem', minHeight: '80px' },\n md: { padding: '10px 12px', fontSize: '1rem', minHeight: '100px' },\n lg: { padding: '12px 14px', fontSize: '1.125rem', minHeight: '120px' },\n xl: { padding: '14px 16px', fontSize: '1.25rem', minHeight: '140px' } };\n\n const baseStyles = {\n width: '100%',\n fontFamily: theme.typography.fontFamily,\n borderRadius: theme.spacing(1),\n border: `2px solid ${error ? errorColor.main : theme.palette.divider}`,\n backgroundColor: theme.palette.background.paper,\n color: theme.palette.text.primary,\n transition: 'all 0.3s ease',\n resize: customVariant === 'resizable' ? 'vertical' : 'none',\n outline: 'none',\n ...sizeMap[customSize as keyof typeof sizeMap],\n\n '&::placeholder': {\n color: theme.palette.text.secondary,\n opacity: 0.7 },\n\n '&:hover': {\n borderColor: error ? errorColor.dark : colorPalette.main,\n backgroundColor: alpha(theme.palette.background.paper, 0.8) },\n\n '&:focus': {\n borderColor: error ? errorColor.main : colorPalette.main,\n backgroundColor: theme.palette.background.paper,\n boxShadow: `0 0 0 3px ${alpha(error ? errorColor.main : colorPalette.main, 0.1)}` } };\n\n // Glass morphism effect\n return {\n ...baseStyles,\n ...textareaEmphasisStyles({ theme, colorPalette, glass, gradient, glow }) };\n});\n\nconst StyledLabel = styled(InputLabel, {\n shouldForwardProp: (prop) => !['glass', 'error'].includes(prop as string) })<{ glass?: boolean; error?: boolean }>(({ theme, glass, error }) => ({\n marginBottom: theme.spacing(1),\n fontWeight: 500,\n color: error ? theme.palette.error.main : theme.palette.text.primary,\n ...(glass && {\n backgroundColor: alpha(theme.palette.background.paper, 0.1),\n backdropFilter: 'blur(10px)',\n padding: '4px 8px',\n borderRadius: '4px',\n border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,\n display: 'inline-block' }) }));\n\n// IconWrapper - replaced with inline Box component for better TypeScript compatibility\n// const IconWrapper = styled(Box)<{ position: 'start' | 'end' }>(({ theme, position }) => {\n// const positionStyles = position === 'start' ? { left: '12px' } : { right: '12px' };\n// return {\n// position: 'absolute' as const,\n// top: '12px',\n// ...positionStyles,\n// color: theme.palette.text.secondary,\n// pointerEvents: 'none' as const,\n// zIndex: 1,\n// };\n// });\n\n// Rich text toolbar styling\n\nconst ICON_GUTTER = '40px';\n\nconst TEXTAREA_DEFAULTS = {\n variant: 'default',\n color: 'primary',\n size: 'md',\n error: false,\n glassLabel: false,\n glow: false,\n glass: false,\n gradient: false,\n iconPosition: 'start',\n minRows: 3 } satisfies Partial<TextareaProps>;\n\n// Strips explicitly-undefined props before the merge, so `prop={undefined}`\n// still falls back to the default as a destructuring default would.\nconst definedProps = (props: TextareaProps): Partial<TextareaProps> =>\n Object.fromEntries(\n Object.entries(props).filter(([, value]) => value !== undefined),\n ) as Partial<TextareaProps>;\n\n// Label above, helper text below — identical for the plain and rich variants,\n// which previously duplicated both.\nconst TextareaShell: React.FC<{\n testId?: string;\n label?: React.ReactNode;\n glassLabel: boolean;\n error: boolean;\n helperText?: React.ReactNode;\n children: React.ReactNode;\n}> = ({ testId, label, glassLabel, error, helperText, children }) => (\n <Box\n sx={{ position: 'relative', width: '100%' }}\n data-testid={testId ? `${testId}-wrapper` : undefined}\n >\n {label && (\n <StyledLabel glass={glassLabel} error={error}>\n {label}\n </StyledLabel>\n )}\n\n {children}\n\n {helperText && (\n <FormHelperText error={error} sx={{ mt: 1 }}>\n {helperText}\n </FormHelperText>\n )}\n </Box>\n);\n\nconst TextareaIcon: React.FC<{\n icon?: React.ReactNode;\n iconPosition: 'start' | 'end';\n}> = ({ icon, iconPosition }) => (\n <Box\n sx={{\n position: 'absolute',\n top: '12px',\n ...(iconPosition === 'start' ? { left: '12px' } : { right: '12px' }),\n color: 'text.secondary',\n pointerEvents: 'none',\n zIndex: 1 }}\n >\n {icon}\n </Box>\n);\n\n/**\n * The rich variant, kept out of the main component's body.\n *\n * Two reasons rather than one: the `Suspense` boundary and its props belong\n * together with the lazy import above, and the component that renders every\n * OTHER variant stays inside the size the complexity gate allows without\n * anything being suppressed.\n */\nfunction RichVariant({\n shell,\n editor }: {\n shell: Omit<React.ComponentProps<typeof TextareaShell>, 'children'>;\n editor: React.ComponentProps<typeof TextareaRichEditor>;\n}): React.JSX.Element {\n return (\n <TextareaShell {...shell}>\n {/*\n `null` rather than a spinner: the editor resolves from the same document\n the caller is already rendering, so a fallback would flash for a frame\n and read as a glitch rather than as loading.\n */}\n <Suspense fallback={null}>\n <TextareaRichEditor {...editor} />\n </Suspense>\n </TextareaShell>\n );\n}\n\nexport const Textarea: React.FC<TextareaProps> = (textareaProps) => {\n const {\n variant,\n color,\n size,\n error,\n helperText,\n label,\n glassLabel,\n glow,\n glass,\n gradient,\n icon,\n iconPosition,\n minRows,\n maxRows,\n style,\n 'data-testid': dataTestId,\n dataTestId: dataTestIdCamelCase,\n ...props\n } = { ...TEXTAREA_DEFAULTS, ...definedProps(textareaProps) };\n\n // Read off the props rather than the merged object: the spread widens every\n // key to optional, which would lose what the variant union already proved.\n const { richEditorCopy } = textareaProps;\n\n const [richTextValue, setRichTextValue] = useState('');\n const hasIcon = Boolean(icon);\n const testId = dataTestId || dataTestIdCamelCase;\n\n const textareaStyle = {\n ...style,\n // Same gap either side; only which side it applies to changes. The previous\n // version had `iconPosition === 'start' ? '40px' : '40px'`, a ternary whose\n // two branches were identical.\n ...(hasIcon && {\n [iconPosition === 'start' ? 'paddingLeft' : 'paddingRight']: ICON_GUTTER }) };\n\n // If rich text variant, use the rich text editor\n if (variant === 'rich' && richEditorCopy) {\n return (\n <RichVariant\n shell={{ testId, label, glassLabel, error, helperText }}\n editor={{\n copy: richEditorCopy,\n value: richTextValue,\n onChange: setRichTextValue,\n placeholder: props.placeholder,\n error,\n glass,\n color,\n characterLimit: props.maxLength }}\n />\n );\n }\n\n return (\n <TextareaShell testId={testId} label={label} glassLabel={glassLabel} error={error} helperText={helperText}>\n <Box sx={{ position: 'relative' }}>\n {hasIcon && <TextareaIcon icon={icon} iconPosition={iconPosition} />}\n\n <StyledTextarea\n customVariant={variant}\n customColor={color}\n customSize={size}\n glow={glow}\n glass={glass}\n gradient={gradient}\n error={error}\n minRows={variant === 'autosize' ? minRows : undefined}\n maxRows={variant === 'autosize' ? maxRows : undefined}\n style={textareaStyle}\n aria-label={label || props['aria-label']}\n data-testid={testId}\n {...props}\n />\n </Box>\n </TextareaShell>\n );\n};\n\nTextarea.displayName = 'Textarea';\n"],"mappings":";;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAAwB;AAE1B,SAAS,cAAc;AACvB,SAAgB,MAAM,UAAU,gBAAgB;AAyJ9C,SAKI,KALJ;AAnIF,IAAM,qBAAqB,KAAK,aAAa;AAAA,EAC3C,UAAU,MAAM,OAAO,mCAAsB,GAAG;AAAmB,EAAE;AAgBvE,IAAM,iBAAiB,OAAO,kBAAkB;AAAA,EAC9C,mBAAmB,CAAC,SAClB,CAAC,CAAC,iBAAiB,eAAe,cAAc,QAAQ,SAAS,YAAY,OAAO,EAAE;AAAA,IACpF;AAAA,EACF;AAAE,CAAC,EAAuB,CAAC;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAM,MAAM;AACZ,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,eAAe,kBAAkB,OAAO,WAAW;AACzD,QAAM,aAAa,MAAM,QAAQ;AAEjC,QAAM,UAAU;AAAA,IACd,IAAI,EAAE,SAAS,WAAW,UAAU,WAAW,WAAW,OAAO;AAAA,IACjE,IAAI,EAAE,SAAS,YAAY,UAAU,YAAY,WAAW,OAAO;AAAA,IACnE,IAAI,EAAE,SAAS,aAAa,UAAU,QAAQ,WAAW,QAAQ;AAAA,IACjE,IAAI,EAAE,SAAS,aAAa,UAAU,YAAY,WAAW,QAAQ;AAAA,IACrE,IAAI,EAAE,SAAS,aAAa,UAAU,WAAW,WAAW,QAAQ;AAAA,EAAE;AAExE,QAAM,aAAa;AAAA,IACjB,OAAO;AAAA,IACP,YAAY,MAAM,WAAW;AAAA,IAC7B,cAAc,MAAM,QAAQ,CAAC;AAAA,IAC7B,QAAQ,aAAa,QAAQ,WAAW,OAAO,MAAM,QAAQ,OAAO;AAAA,IACpE,iBAAiB,MAAM,QAAQ,WAAW;AAAA,IAC1C,OAAO,MAAM,QAAQ,KAAK;AAAA,IAC1B,YAAY;AAAA,IACZ,QAAQ,kBAAkB,cAAc,aAAa;AAAA,IACrD,SAAS;AAAA,IACT,GAAG,QAAQ,UAAkC;AAAA,IAE7C,kBAAkB;AAAA,MAChB,OAAO,MAAM,QAAQ,KAAK;AAAA,MAC1B,SAAS;AAAA,IAAI;AAAA,IAEf,WAAW;AAAA,MACT,aAAa,QAAQ,WAAW,OAAO,aAAa;AAAA,MACpD,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,GAAG;AAAA,IAAE;AAAA,IAE9D,WAAW;AAAA,MACT,aAAa,QAAQ,WAAW,OAAO,aAAa;AAAA,MACpD,iBAAiB,MAAM,QAAQ,WAAW;AAAA,MAC1C,WAAW,aAAa,MAAM,QAAQ,WAAW,OAAO,aAAa,MAAM,GAAG,CAAC;AAAA,IAAG;AAAA,EAAE;AAGxF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,uBAAuB,EAAE,OAAO,cAAc,OAAO,UAAU,KAAK,CAAC;AAAA,EAAE;AAC9E,CAAC;AAED,IAAM,cAAc,OAAO,YAAY;AAAA,EACrC,mBAAmB,CAAC,SAAS,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,IAAc;AAAE,CAAC,EAAwC,CAAC,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,EACjJ,cAAc,MAAM,QAAQ,CAAC;AAAA,EAC7B,YAAY;AAAA,EACZ,OAAO,QAAQ,MAAM,QAAQ,MAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,EAC7D,GAAI,SAAS;AAAA,IACX,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,GAAG;AAAA,IAC1D,gBAAgB;AAAA,IAChB,SAAS;AAAA,IACT,cAAc;AAAA,IACd,QAAQ,aAAa,MAAM,MAAM,QAAQ,SAAS,GAAG,CAAC;AAAA,IACtD,SAAS;AAAA,EAAe;AAAG,EAAE;AAiBjC,IAAM,cAAc;AAEpB,IAAM,oBAAoB;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,cAAc;AAAA,EACd,SAAS;AAAE;AAIb,IAAM,eAAe,CAAC,UACpB,OAAO;AAAA,EACL,OAAO,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS;AACjE;AAIF,IAAM,gBAOD,CAAC,EAAE,QAAQ,OAAO,YAAY,OAAO,YAAY,SAAS,MAC7D;AAAA,EAAC;AAAA;AAAA,IACC,IAAI,EAAE,UAAU,YAAY,OAAO,OAAO;AAAA,IAC1C,eAAa,SAAS,GAAG,MAAM,aAAa;AAAA,IAE3C;AAAA,eACC,oBAAC,eAAY,OAAO,YAAY,OAC7B,iBACH;AAAA,MAGD;AAAA,MAEA,cACC,oBAAC,kBAAe,OAAc,IAAI,EAAE,IAAI,EAAE,GACvC,sBACH;AAAA;AAAA;AAEJ;AAGF,IAAM,eAGD,CAAC,EAAE,MAAM,aAAa,MACzB;AAAA,EAAC;AAAA;AAAA,IACC,IAAI;AAAA,MACF,UAAU;AAAA,MACV,KAAK;AAAA,MACL,GAAI,iBAAiB,UAAU,EAAE,MAAM,OAAO,IAAI,EAAE,OAAO,OAAO;AAAA,MAClE,OAAO;AAAA,MACP,eAAe;AAAA,MACf,QAAQ;AAAA,IAAE;AAAA,IAEX;AAAA;AACH;AAWF,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAO,GAGa;AACpB,SACE,oBAAC,iBAAe,GAAG,OAMjB,8BAAC,YAAS,UAAU,MAClB,8BAAC,sBAAoB,GAAG,QAAQ,GAClC,GACF;AAEJ;AAEO,IAAM,WAAoC,CAAC,kBAAkB;AAClE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,GAAG;AAAA,EACL,IAAI,EAAE,GAAG,mBAAmB,GAAG,aAAa,aAAa,EAAE;AAI3D,QAAM,EAAE,eAAe,IAAI;AAE3B,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,EAAE;AACrD,QAAM,UAAU,QAAQ,IAAI;AAC5B,QAAM,SAAS,cAAc;AAE7B,QAAM,gBAAgB;AAAA,IACpB,GAAG;AAAA;AAAA;AAAA;AAAA,IAIH,GAAI,WAAW;AAAA,MACb,CAAC,iBAAiB,UAAU,gBAAgB,cAAc,GAAG;AAAA,IAAY;AAAA,EAAG;AAGhF,MAAI,YAAY,UAAU,gBAAgB;AACxC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,QAAQ,OAAO,YAAY,OAAO,WAAW;AAAA,QACtD,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,UACP,UAAU;AAAA,UACV,aAAa,MAAM;AAAA,UACnB;AAAA,UACA;AAAA,UACA;AAAA,UACA,gBAAgB,MAAM;AAAA,QAAU;AAAA;AAAA,IACpC;AAAA,EAEJ;AAEA,SACE,oBAAC,iBAAc,QAAgB,OAAc,YAAwB,OAAc,YACjF,+BAAC,OAAI,IAAI,EAAE,UAAU,WAAW,GAC7B;AAAA,eAAW,oBAAC,gBAAa,MAAY,cAA4B;AAAA,IAElE;AAAA,MAAC;AAAA;AAAA,QACC,eAAe;AAAA,QACf,aAAa;AAAA,QACb,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,YAAY,aAAa,UAAU;AAAA,QAC5C,SAAS,YAAY,aAAa,UAAU;AAAA,QAC5C,OAAO;AAAA,QACP,cAAY,SAAS,MAAM,YAAY;AAAA,QACvC,eAAa;AAAA,QACZ,GAAG;AAAA;AAAA,IACN;AAAA,KACF,GACF;AAEJ;AAEA,SAAS,cAAc;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/Textarea/Textarea.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsKtD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA+E5C,CAAC"}
1
+ {"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/Textarea/Textarea.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmC,MAAM,OAAO,CAAC;AAwBxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAkMtD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA+E5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.10.0",
3
+ "version": "6.10.1",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -6,12 +6,30 @@ import {
6
6
  TextareaAutosize } from '@mui/material';
7
7
  import type { Theme } from '@mui/material';
8
8
  import { styled } from '@mui/material';
9
- import React, { useState } from 'react';
9
+ import React, { lazy, Suspense, useState } from 'react';
10
10
 
11
11
  import {
12
12
  getColorFromTheme,
13
13
  textareaEmphasisStyles } from './Textarea.styles';
14
- import { TextareaRichEditor } from './TextareaRichEditor';
14
+
15
+ /**
16
+ * The rich editor is LOADED ON DEMAND, not imported (12-51 follow-up).
17
+ *
18
+ * It renders only for `variant === 'rich'`, but a static import made every
19
+ * consumer of a plain `<Textarea>` pay for it — 387 lines of toolbar plus the
20
+ * MUI `Tooltip`, `IconButton` and `Divider` its formatting buttons need. That
21
+ * is not theoretical: an adopting storefront imports this entry for a one-line
22
+ * order note, and `Tooltip` (8.7 KiB) reached its EAGER chunk through here
23
+ * while nothing on that critical path renders a tooltip at all.
24
+ *
25
+ * `variant` is a prop, so the branch cannot be decided at build time and no
26
+ * amount of tree-shaking removes it. A lazy boundary is what expresses "only
27
+ * the rich variant pays", and it costs the rich variant one microtask it can
28
+ * afford — it is a text editor a user has already chosen to open, never a
29
+ * first-paint element.
30
+ */
31
+ const TextareaRichEditor = lazy(async () => ({
32
+ default: (await import('./TextareaRichEditor')).TextareaRichEditor }));
15
33
  import type { TextareaProps } from './Textarea.types';
16
34
 
17
35
  // Interface for styled component props
@@ -178,6 +196,34 @@ const TextareaIcon: React.FC<{
178
196
  </Box>
179
197
  );
180
198
 
199
+ /**
200
+ * The rich variant, kept out of the main component's body.
201
+ *
202
+ * Two reasons rather than one: the `Suspense` boundary and its props belong
203
+ * together with the lazy import above, and the component that renders every
204
+ * OTHER variant stays inside the size the complexity gate allows without
205
+ * anything being suppressed.
206
+ */
207
+ function RichVariant({
208
+ shell,
209
+ editor }: {
210
+ shell: Omit<React.ComponentProps<typeof TextareaShell>, 'children'>;
211
+ editor: React.ComponentProps<typeof TextareaRichEditor>;
212
+ }): React.JSX.Element {
213
+ return (
214
+ <TextareaShell {...shell}>
215
+ {/*
216
+ `null` rather than a spinner: the editor resolves from the same document
217
+ the caller is already rendering, so a fallback would flash for a frame
218
+ and read as a glitch rather than as loading.
219
+ */}
220
+ <Suspense fallback={null}>
221
+ <TextareaRichEditor {...editor} />
222
+ </Suspense>
223
+ </TextareaShell>
224
+ );
225
+ }
226
+
181
227
  export const Textarea: React.FC<TextareaProps> = (textareaProps) => {
182
228
  const {
183
229
  variant,
@@ -219,18 +265,18 @@ export const Textarea: React.FC<TextareaProps> = (textareaProps) => {
219
265
  // If rich text variant, use the rich text editor
220
266
  if (variant === 'rich' && richEditorCopy) {
221
267
  return (
222
- <TextareaShell testId={testId} label={label} glassLabel={glassLabel} error={error} helperText={helperText}>
223
- <TextareaRichEditor
224
- copy={richEditorCopy}
225
- value={richTextValue}
226
- onChange={setRichTextValue}
227
- placeholder={props.placeholder}
228
- error={error}
229
- glass={glass}
230
- color={color}
231
- characterLimit={props.maxLength}
232
- />
233
- </TextareaShell>
268
+ <RichVariant
269
+ shell={{ testId, label, glassLabel, error, helperText }}
270
+ editor={{
271
+ copy: richEditorCopy,
272
+ value: richTextValue,
273
+ onChange: setRichTextValue,
274
+ placeholder: props.placeholder,
275
+ error,
276
+ glass,
277
+ color,
278
+ characterLimit: props.maxLength }}
279
+ />
234
280
  );
235
281
  }
236
282