@madie/madie-design-system 1.2.64 → 1.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/RichTextEditor/RichTextEditor.stories.js +20 -0
- package/components/RichTextEditor/index.jsx +87 -4
- package/dist/browser.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/styles/components/_rich-text-editor.scss +11 -2
- package/styles/qppds/components/_rich-text-editor.scss +10 -2
- package/test/components/RichTextEditor.test.js +42 -0
|
@@ -63,4 +63,24 @@ export const DisabledTextEditor = () => {
|
|
|
63
63
|
/>
|
|
64
64
|
);
|
|
65
65
|
};
|
|
66
|
+
|
|
67
|
+
export const TextEditorWithError = () => {
|
|
68
|
+
const [value, setValue] = useState(
|
|
69
|
+
"<p>There is an error</p>"
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleChange = (selectedVal) => {
|
|
73
|
+
setValue(selectedVal);
|
|
74
|
+
};
|
|
75
|
+
return (
|
|
76
|
+
<RichTextEditor
|
|
77
|
+
label="Test Text Editor"
|
|
78
|
+
error={true}
|
|
79
|
+
helperText="This field is required"
|
|
80
|
+
required={true}
|
|
81
|
+
onChange={handleChange}
|
|
82
|
+
content={value}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
66
86
|
TextEditor.storyName = "Rich Text Editor";
|
|
@@ -11,7 +11,7 @@ import TableHeader from "@tiptap/extension-table-header";
|
|
|
11
11
|
import TableRow from "@tiptap/extension-table-row";
|
|
12
12
|
import Underline from "@tiptap/extension-underline";
|
|
13
13
|
import StarterKit from "@tiptap/starter-kit";
|
|
14
|
-
import { IconButton } from "@mui/material";
|
|
14
|
+
import { FormHelperText, IconButton } from "@mui/material";
|
|
15
15
|
import FormatBoldIcon from "@mui/icons-material/FormatBold";
|
|
16
16
|
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
|
|
17
17
|
import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
|
|
@@ -28,7 +28,12 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
return (
|
|
31
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
className="control-group"
|
|
33
|
+
role="toolbar"
|
|
34
|
+
aria-label="Text formatting toolbar"
|
|
35
|
+
data-testid="rich-text-editor-toolbar"
|
|
36
|
+
>
|
|
32
37
|
<div className="button-group">
|
|
33
38
|
<Tooltip
|
|
34
39
|
data-testid="bold-tooltip"
|
|
@@ -42,8 +47,11 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
42
47
|
onClick={() =>
|
|
43
48
|
editor.chain().focus().toggleBold().run()
|
|
44
49
|
}
|
|
50
|
+
aria-label="Bold"
|
|
51
|
+
aria-pressed={editor.isActive("bold")}
|
|
45
52
|
className={editor.isActive("bold") ? "is-active" : ""}
|
|
46
53
|
disabled={disabled}
|
|
54
|
+
type="button"
|
|
47
55
|
>
|
|
48
56
|
<FormatBoldIcon />
|
|
49
57
|
</IconButton>
|
|
@@ -60,8 +68,11 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
60
68
|
onClick={() =>
|
|
61
69
|
editor.chain().focus().toggleItalic().run()
|
|
62
70
|
}
|
|
71
|
+
aria-label="Italic"
|
|
72
|
+
aria-pressed={editor.isActive("italic")}
|
|
63
73
|
className={editor.isActive("italic") ? "is-active" : ""}
|
|
64
74
|
disabled={disabled}
|
|
75
|
+
type="button"
|
|
65
76
|
>
|
|
66
77
|
<FormatItalicIcon />
|
|
67
78
|
</IconButton>
|
|
@@ -78,10 +89,13 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
78
89
|
onClick={() =>
|
|
79
90
|
editor.chain().focus().toggleUnderline().run()
|
|
80
91
|
}
|
|
92
|
+
aria-label="Underline"
|
|
93
|
+
aria-pressed={editor.isActive("underline")}
|
|
81
94
|
className={
|
|
82
95
|
editor.isActive("underline") ? "is-active" : ""
|
|
83
96
|
}
|
|
84
97
|
disabled={disabled}
|
|
98
|
+
type="button"
|
|
85
99
|
>
|
|
86
100
|
<FormatUnderlinedIcon />
|
|
87
101
|
</IconButton>
|
|
@@ -98,9 +112,12 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
98
112
|
onClick={() =>
|
|
99
113
|
editor.chain().focus().toggleStrike().run()
|
|
100
114
|
}
|
|
115
|
+
aria-label="Strikethrough"
|
|
116
|
+
aria-pressed={editor.isActive("strike")}
|
|
101
117
|
className={editor.isActive("strike") ? "is-active" : ""}
|
|
102
118
|
style={{ borderRight: "solid 1px #9c9c9c" }}
|
|
103
119
|
disabled={disabled}
|
|
120
|
+
type="button"
|
|
104
121
|
>
|
|
105
122
|
<StrikethroughSIcon />
|
|
106
123
|
</IconButton>
|
|
@@ -117,10 +134,13 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
117
134
|
onClick={() =>
|
|
118
135
|
editor.chain().focus().toggleOrderedList().run()
|
|
119
136
|
}
|
|
137
|
+
aria-label="Ordered List"
|
|
138
|
+
aria-pressed={editor.isActive("orderedList")}
|
|
120
139
|
className={
|
|
121
140
|
editor.isActive("orderedList") ? "is-active" : ""
|
|
122
141
|
}
|
|
123
142
|
disabled={disabled}
|
|
143
|
+
type="button"
|
|
124
144
|
>
|
|
125
145
|
<FormatListNumberedIcon />
|
|
126
146
|
</IconButton>
|
|
@@ -137,11 +157,14 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
137
157
|
onClick={() =>
|
|
138
158
|
editor.chain().focus().toggleBulletList().run()
|
|
139
159
|
}
|
|
160
|
+
aria-label="Bulleted List"
|
|
161
|
+
aria-pressed={editor.isActive("bulletList")}
|
|
140
162
|
className={
|
|
141
163
|
editor.isActive("bulletList") ? "is-active" : ""
|
|
142
164
|
}
|
|
143
165
|
style={{ borderRight: "solid 1px #9c9c9c" }}
|
|
144
166
|
disabled={disabled}
|
|
167
|
+
type="button"
|
|
145
168
|
>
|
|
146
169
|
<FormatListBulletedIcon />
|
|
147
170
|
</IconButton>
|
|
@@ -162,10 +185,12 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
162
185
|
withHeaderRow: true,
|
|
163
186
|
})
|
|
164
187
|
}
|
|
188
|
+
aria-label="Insert Table"
|
|
165
189
|
className={
|
|
166
190
|
editor.isActive("insertTable") ? "is-active" : ""
|
|
167
191
|
}
|
|
168
192
|
disabled={disabled}
|
|
193
|
+
type="button"
|
|
169
194
|
>
|
|
170
195
|
<TableChartIcon />
|
|
171
196
|
</IconButton>
|
|
@@ -176,11 +201,14 @@ const MenuBar = ({ editor, disabled }) => {
|
|
|
176
201
|
};
|
|
177
202
|
|
|
178
203
|
const RichTextEditor = ({
|
|
204
|
+
name,
|
|
179
205
|
id,
|
|
180
206
|
error = false,
|
|
207
|
+
helperText,
|
|
181
208
|
required = false,
|
|
182
209
|
label,
|
|
183
210
|
onChange,
|
|
211
|
+
onBlur,
|
|
184
212
|
content,
|
|
185
213
|
disabled = false,
|
|
186
214
|
readOnly = false,
|
|
@@ -211,12 +239,28 @@ const RichTextEditor = ({
|
|
|
211
239
|
},
|
|
212
240
|
[content, disabled]
|
|
213
241
|
);
|
|
242
|
+
|
|
243
|
+
React.useEffect(() => {
|
|
244
|
+
if (!editor || !onBlur || !name) return;
|
|
245
|
+
|
|
246
|
+
const handleBlur = () => {
|
|
247
|
+
onBlur({ target: { name } });
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
editor.on("blur", handleBlur);
|
|
251
|
+
|
|
252
|
+
return () => {
|
|
253
|
+
editor.off("blur", handleBlur);
|
|
254
|
+
};
|
|
255
|
+
}, [editor, onBlur, name]);
|
|
256
|
+
|
|
214
257
|
return (
|
|
215
258
|
<div
|
|
216
259
|
className="rich-text-editor"
|
|
217
260
|
data-testid={`${kebabCase(label)}-rich-text-editor`}
|
|
218
261
|
>
|
|
219
262
|
<InputLabel
|
|
263
|
+
id={`${id}-label`}
|
|
220
264
|
shrink
|
|
221
265
|
required={required}
|
|
222
266
|
error={error}
|
|
@@ -256,10 +300,33 @@ const RichTextEditor = ({
|
|
|
256
300
|
>
|
|
257
301
|
{label}
|
|
258
302
|
</InputLabel>
|
|
303
|
+
|
|
304
|
+
{helperText && (
|
|
305
|
+
<FormHelperText
|
|
306
|
+
tabIndex={0}
|
|
307
|
+
aria-live="polite"
|
|
308
|
+
id={`${id}-helper-text`}
|
|
309
|
+
data-testid={`${id}-helper-text`}
|
|
310
|
+
sx={[
|
|
311
|
+
{
|
|
312
|
+
margin: "4px 0px 0px 0px",
|
|
313
|
+
color: "#515151",
|
|
314
|
+
lineHeight: 1,
|
|
315
|
+
},
|
|
316
|
+
error && {
|
|
317
|
+
color: "#AE1C1C !important",
|
|
318
|
+
},
|
|
319
|
+
]}
|
|
320
|
+
>
|
|
321
|
+
{helperText}
|
|
322
|
+
</FormHelperText>
|
|
323
|
+
)}
|
|
324
|
+
|
|
259
325
|
{readOnly ? (
|
|
260
326
|
<p
|
|
327
|
+
className="rich-text-editor_read_only"
|
|
261
328
|
data-testid={`${id}-value`}
|
|
262
|
-
aria-labelledby={label}
|
|
329
|
+
aria-labelledby={`${id}-label`}
|
|
263
330
|
dangerouslySetInnerHTML={{
|
|
264
331
|
__html: content ? DOMPurify.sanitize(content) : "-",
|
|
265
332
|
}}
|
|
@@ -267,7 +334,20 @@ const RichTextEditor = ({
|
|
|
267
334
|
) : (
|
|
268
335
|
<>
|
|
269
336
|
<MenuBar editor={editor} disabled={disabled} />
|
|
270
|
-
<EditorContent
|
|
337
|
+
<EditorContent
|
|
338
|
+
id={id}
|
|
339
|
+
tabIndex={0}
|
|
340
|
+
data-testid="rich-text-editor-content"
|
|
341
|
+
editor={editor}
|
|
342
|
+
aria-labelledby={`${id}-label`}
|
|
343
|
+
aria-describedby={
|
|
344
|
+
helperText ? `${id}-helper-text` : undefined
|
|
345
|
+
}
|
|
346
|
+
aria-multiline="true"
|
|
347
|
+
aria-required={required || undefined}
|
|
348
|
+
aria-invalid={error || undefined}
|
|
349
|
+
className={`ProseMirror ${error ? "has-error" : ""}`}
|
|
350
|
+
/>
|
|
271
351
|
</>
|
|
272
352
|
)}
|
|
273
353
|
</div>
|
|
@@ -276,10 +356,13 @@ const RichTextEditor = ({
|
|
|
276
356
|
|
|
277
357
|
RichTextEditor.propTypes = {
|
|
278
358
|
id: PropTypes.string,
|
|
359
|
+
name: PropTypes.string,
|
|
279
360
|
error: PropTypes.bool,
|
|
361
|
+
helperText: PropTypes.string,
|
|
280
362
|
required: PropTypes.bool,
|
|
281
363
|
label: PropTypes.string,
|
|
282
364
|
onChange: PropTypes.func,
|
|
365
|
+
onBlur: PropTypes.func,
|
|
283
366
|
content: PropTypes.any,
|
|
284
367
|
disabled: PropTypes.bool,
|
|
285
368
|
readOnly: PropTypes.bool,
|