@squiz/formatted-text-editor 1.12.0-alpha.45 → 1.12.0-alpha.46
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/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.js +1 -1
- package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.js +2 -3
- package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +1 -1
- package/lib/index.css +29 -331
- package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +0 -1
- package/lib/ui/ToolbarDropdown/ToolbarDropdown.js +14 -6
- package/lib/ui/{DropdownButton/DropdownButton.js → ToolbarDropdownButton/ToolbarDropdownButton.js} +1 -1
- package/package.json +2 -3
- package/src/Editor/_editor.scss +1 -1
- package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.spec.tsx +56 -0
- package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.tsx +1 -1
- package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.spec.tsx +30 -0
- package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.tsx +2 -3
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.spec.tsx +47 -0
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +1 -1
- package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.spec.tsx +51 -0
- package/src/index.scss +1 -1
- package/src/ui/ToolbarDropdown/ToolbarDropdown.spec.tsx +78 -0
- package/src/ui/ToolbarDropdown/ToolbarDropdown.tsx +22 -12
- package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +25 -21
- package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.spec.tsx +48 -0
- package/src/ui/{DropdownButton/DropdownButton.tsx → ToolbarDropdownButton/ToolbarDropdownButton.tsx} +1 -0
- package/src/ui/{DropdownButton/_dropdown-button.scss → ToolbarDropdownButton/_toolbar-dropdown-button.scss} +7 -1
- package/tailwind.config.cjs +1 -1
- /package/lib/ui/{DropdownButton/DropdownButton.d.ts → ToolbarDropdownButton/ToolbarDropdownButton.d.ts} +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
|
-
import DropdownButton from '../../../../ui/
|
3
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
4
4
|
const HeadingButton = ({ level }) => {
|
5
5
|
const { toggleHeading } = useCommands();
|
6
6
|
const chain = useChainedCommands();
|
@@ -1,17 +1,16 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
|
-
import DropdownButton from '../../../../ui/
|
3
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
4
4
|
const ParagraphButton = () => {
|
5
5
|
const { convertParagraph } = useCommands();
|
6
6
|
const chain = useChainedCommands();
|
7
7
|
const active = useActive();
|
8
|
-
const enabled = convertParagraph.enabled();
|
9
8
|
const handleSelect = () => {
|
10
9
|
if (convertParagraph.enabled()) {
|
11
10
|
chain.convertParagraph().focus().run();
|
12
11
|
}
|
13
12
|
};
|
14
|
-
return (React.createElement(DropdownButton, { handleOnClick: handleSelect, isDisabled:
|
13
|
+
return (React.createElement(DropdownButton, { handleOnClick: handleSelect, isDisabled: false, isActive: active.paragraph(), label: "Paragraph" },
|
15
14
|
React.createElement("p", null, "Paragraph")));
|
16
15
|
};
|
17
16
|
export default ParagraphButton;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useActive } from '@remirror/react';
|
3
|
-
import DropdownButton from '../../../../ui/
|
3
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
4
4
|
const PreformattedButton = () => {
|
5
5
|
const { togglePreformatted } = useCommands();
|
6
6
|
const active = useActive();
|
package/lib/index.css
CHANGED
@@ -213,309 +213,6 @@ video {
|
|
213
213
|
[hidden] {
|
214
214
|
display: none;
|
215
215
|
}
|
216
|
-
[type=text],
|
217
|
-
[type=email],
|
218
|
-
[type=url],
|
219
|
-
[type=password],
|
220
|
-
[type=number],
|
221
|
-
[type=date],
|
222
|
-
[type=datetime-local],
|
223
|
-
[type=month],
|
224
|
-
[type=search],
|
225
|
-
[type=tel],
|
226
|
-
[type=time],
|
227
|
-
[type=week],
|
228
|
-
[multiple],
|
229
|
-
textarea,
|
230
|
-
select {
|
231
|
-
appearance: none;
|
232
|
-
background-color: #fff;
|
233
|
-
border-color: #949494;
|
234
|
-
border-width: 1px;
|
235
|
-
border-radius: 0px;
|
236
|
-
padding-top: 0.5rem;
|
237
|
-
padding-right: 0.75rem;
|
238
|
-
padding-bottom: 0.5rem;
|
239
|
-
padding-left: 0.75rem;
|
240
|
-
font-size: 1rem;
|
241
|
-
line-height: 1.5rem;
|
242
|
-
--tw-shadow: 0 0 #0000;
|
243
|
-
}
|
244
|
-
[type=text]:focus,
|
245
|
-
[type=email]:focus,
|
246
|
-
[type=url]:focus,
|
247
|
-
[type=password]:focus,
|
248
|
-
[type=number]:focus,
|
249
|
-
[type=date]:focus,
|
250
|
-
[type=datetime-local]:focus,
|
251
|
-
[type=month]:focus,
|
252
|
-
[type=search]:focus,
|
253
|
-
[type=tel]:focus,
|
254
|
-
[type=time]:focus,
|
255
|
-
[type=week]:focus,
|
256
|
-
[multiple]:focus,
|
257
|
-
textarea:focus,
|
258
|
-
select:focus {
|
259
|
-
outline: 2px solid transparent;
|
260
|
-
outline-offset: 2px;
|
261
|
-
--tw-ring-inset: var(--tw-empty, );
|
262
|
-
--tw-ring-offset-width: 0px;
|
263
|
-
--tw-ring-offset-color: #fff;
|
264
|
-
--tw-ring-color: #1C64F2;
|
265
|
-
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
266
|
-
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
267
|
-
box-shadow:
|
268
|
-
var(--tw-ring-offset-shadow),
|
269
|
-
var(--tw-ring-shadow),
|
270
|
-
var(--tw-shadow);
|
271
|
-
border-color: #1C64F2;
|
272
|
-
}
|
273
|
-
input::placeholder,
|
274
|
-
textarea::placeholder {
|
275
|
-
color: #949494;
|
276
|
-
opacity: 1;
|
277
|
-
}
|
278
|
-
::-webkit-datetime-edit-fields-wrapper {
|
279
|
-
padding: 0;
|
280
|
-
}
|
281
|
-
::-webkit-date-and-time-value {
|
282
|
-
min-height: 1.5em;
|
283
|
-
}
|
284
|
-
select:not([size]) {
|
285
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23949494' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
286
|
-
background-position: right 0.5rem center;
|
287
|
-
background-repeat: no-repeat;
|
288
|
-
background-size: 1.5em 1.5em;
|
289
|
-
padding-right: 2.5rem;
|
290
|
-
print-color-adjust: exact;
|
291
|
-
}
|
292
|
-
[multiple] {
|
293
|
-
background-image: initial;
|
294
|
-
background-position: initial;
|
295
|
-
background-repeat: unset;
|
296
|
-
background-size: initial;
|
297
|
-
padding-right: 0.75rem;
|
298
|
-
print-color-adjust: unset;
|
299
|
-
}
|
300
|
-
[type=checkbox],
|
301
|
-
[type=radio] {
|
302
|
-
appearance: none;
|
303
|
-
padding: 0;
|
304
|
-
print-color-adjust: exact;
|
305
|
-
display: inline-block;
|
306
|
-
vertical-align: middle;
|
307
|
-
background-origin: border-box;
|
308
|
-
user-select: none;
|
309
|
-
flex-shrink: 0;
|
310
|
-
height: 1rem;
|
311
|
-
width: 1rem;
|
312
|
-
color: #1C64F2;
|
313
|
-
background-color: #fff;
|
314
|
-
border-color: #949494;
|
315
|
-
border-width: 1px;
|
316
|
-
--tw-shadow: 0 0 #0000;
|
317
|
-
}
|
318
|
-
[type=checkbox] {
|
319
|
-
border-radius: 0px;
|
320
|
-
}
|
321
|
-
[type=radio] {
|
322
|
-
border-radius: 100%;
|
323
|
-
}
|
324
|
-
[type=checkbox]:focus,
|
325
|
-
[type=radio]:focus {
|
326
|
-
outline: 2px solid transparent;
|
327
|
-
outline-offset: 2px;
|
328
|
-
--tw-ring-inset: var(--tw-empty, );
|
329
|
-
--tw-ring-offset-width: 2px;
|
330
|
-
--tw-ring-offset-color: #fff;
|
331
|
-
--tw-ring-color: #1C64F2;
|
332
|
-
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
333
|
-
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
334
|
-
box-shadow:
|
335
|
-
var(--tw-ring-offset-shadow),
|
336
|
-
var(--tw-ring-shadow),
|
337
|
-
var(--tw-shadow);
|
338
|
-
}
|
339
|
-
[type=checkbox]:checked,
|
340
|
-
[type=radio]:checked,
|
341
|
-
.dark [type=checkbox]:checked,
|
342
|
-
.dark [type=radio]:checked {
|
343
|
-
border-color: transparent;
|
344
|
-
background-color: currentColor;
|
345
|
-
background-size: 100% 100%;
|
346
|
-
background-position: center;
|
347
|
-
background-repeat: no-repeat;
|
348
|
-
}
|
349
|
-
[type=checkbox]:checked {
|
350
|
-
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
351
|
-
}
|
352
|
-
[type=radio]:checked {
|
353
|
-
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
|
354
|
-
}
|
355
|
-
[type=checkbox]:indeterminate {
|
356
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
|
357
|
-
border-color: transparent;
|
358
|
-
background-color: currentColor;
|
359
|
-
background-size: 100% 100%;
|
360
|
-
background-position: center;
|
361
|
-
background-repeat: no-repeat;
|
362
|
-
}
|
363
|
-
[type=checkbox]:indeterminate:hover,
|
364
|
-
[type=checkbox]:indeterminate:focus {
|
365
|
-
border-color: transparent;
|
366
|
-
background-color: currentColor;
|
367
|
-
}
|
368
|
-
[type=file] {
|
369
|
-
background: unset;
|
370
|
-
border-color: inherit;
|
371
|
-
border-width: 0;
|
372
|
-
border-radius: 0;
|
373
|
-
padding: 0;
|
374
|
-
font-size: unset;
|
375
|
-
line-height: inherit;
|
376
|
-
}
|
377
|
-
[type=file]:focus {
|
378
|
-
outline: 1px auto inherit;
|
379
|
-
}
|
380
|
-
input[type=file]::file-selector-button {
|
381
|
-
color: white;
|
382
|
-
background: #3D3D3D;
|
383
|
-
border: 0;
|
384
|
-
font-weight: 500;
|
385
|
-
font-size: 0.8125rem;
|
386
|
-
cursor: pointer;
|
387
|
-
padding-top: 0.625rem;
|
388
|
-
padding-bottom: 0.625rem;
|
389
|
-
padding-left: 2rem;
|
390
|
-
padding-right: 1rem;
|
391
|
-
margin-inline-start: -1rem;
|
392
|
-
margin-inline-end: 1rem;
|
393
|
-
}
|
394
|
-
input[type=file]::file-selector-button:hover {
|
395
|
-
background: #4F4F4F;
|
396
|
-
}
|
397
|
-
input[type=range]::-webkit-slider-thumb {
|
398
|
-
height: 1.25rem;
|
399
|
-
width: 1.25rem;
|
400
|
-
background: #1C64F2;
|
401
|
-
border-radius: 9999px;
|
402
|
-
border: 0;
|
403
|
-
appearance: none;
|
404
|
-
-moz-appearance: none;
|
405
|
-
-webkit-appearance: none;
|
406
|
-
cursor: pointer;
|
407
|
-
}
|
408
|
-
input[type=range]:disabled::-webkit-slider-thumb {
|
409
|
-
background: #BABABA;
|
410
|
-
}
|
411
|
-
input[type=range]:focus::-webkit-slider-thumb {
|
412
|
-
outline: 2px solid transparent;
|
413
|
-
outline-offset: 2px;
|
414
|
-
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
415
|
-
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
416
|
-
box-shadow:
|
417
|
-
var(--tw-ring-offset-shadow),
|
418
|
-
var(--tw-ring-shadow),
|
419
|
-
var(--tw-shadow, 0 0 #0000);
|
420
|
-
--tw-ring-opacity: 1px;
|
421
|
-
--tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity));
|
422
|
-
}
|
423
|
-
input[type=range]::-moz-range-thumb {
|
424
|
-
height: 1.25rem;
|
425
|
-
width: 1.25rem;
|
426
|
-
background: #1C64F2;
|
427
|
-
border-radius: 9999px;
|
428
|
-
border: 0;
|
429
|
-
appearance: none;
|
430
|
-
-moz-appearance: none;
|
431
|
-
-webkit-appearance: none;
|
432
|
-
cursor: pointer;
|
433
|
-
}
|
434
|
-
input[type=range]:disabled::-moz-range-thumb {
|
435
|
-
background: #BABABA;
|
436
|
-
}
|
437
|
-
input[type=range]::-moz-range-progress {
|
438
|
-
background: #3F83F8;
|
439
|
-
}
|
440
|
-
input[type=range]::-ms-fill-lower {
|
441
|
-
background: #3F83F8;
|
442
|
-
}
|
443
|
-
[data-popper-arrow],
|
444
|
-
[data-popper-arrow]:before {
|
445
|
-
position: absolute;
|
446
|
-
width: 8px;
|
447
|
-
height: 8px;
|
448
|
-
background: inherit;
|
449
|
-
}
|
450
|
-
[data-popper-arrow] {
|
451
|
-
visibility: hidden;
|
452
|
-
}
|
453
|
-
[data-popper-arrow]:before {
|
454
|
-
content: "";
|
455
|
-
visibility: visible;
|
456
|
-
transform: rotate(45deg);
|
457
|
-
}
|
458
|
-
[data-popper-arrow]:after {
|
459
|
-
content: "";
|
460
|
-
visibility: visible;
|
461
|
-
transform: rotate(45deg);
|
462
|
-
position: absolute;
|
463
|
-
width: 9px;
|
464
|
-
height: 9px;
|
465
|
-
background: inherit;
|
466
|
-
}
|
467
|
-
[role=tooltip] > [data-popper-arrow]:before {
|
468
|
-
border-style: solid;
|
469
|
-
border-color: #e5e7eb;
|
470
|
-
}
|
471
|
-
[role=tooltip] > [data-popper-arrow]:after {
|
472
|
-
border-style: solid;
|
473
|
-
border-color: #e5e7eb;
|
474
|
-
}
|
475
|
-
[data-popover][role=tooltip][data-popper-placement^=top] > [data-popper-arrow]:before {
|
476
|
-
border-bottom-width: 1px;
|
477
|
-
border-right-width: 1px;
|
478
|
-
}
|
479
|
-
[data-popover][role=tooltip][data-popper-placement^=top] > [data-popper-arrow]:after {
|
480
|
-
border-bottom-width: 1px;
|
481
|
-
border-right-width: 1px;
|
482
|
-
}
|
483
|
-
[data-popover][role=tooltip][data-popper-placement^=right] > [data-popper-arrow]:before {
|
484
|
-
border-bottom-width: 1px;
|
485
|
-
border-left-width: 1px;
|
486
|
-
}
|
487
|
-
[data-popover][role=tooltip][data-popper-placement^=right] > [data-popper-arrow]:after {
|
488
|
-
border-bottom-width: 1px;
|
489
|
-
border-left-width: 1px;
|
490
|
-
}
|
491
|
-
[data-popover][role=tooltip][data-popper-placement^=bottom] > [data-popper-arrow]:before {
|
492
|
-
border-top-width: 1px;
|
493
|
-
border-left-width: 1px;
|
494
|
-
}
|
495
|
-
[data-popover][role=tooltip][data-popper-placement^=bottom] > [data-popper-arrow]:after {
|
496
|
-
border-top-width: 1px;
|
497
|
-
border-left-width: 1px;
|
498
|
-
}
|
499
|
-
[data-popover][role=tooltip][data-popper-placement^=left] > [data-popper-arrow]:before {
|
500
|
-
border-top-width: 1px;
|
501
|
-
border-right-width: 1px;
|
502
|
-
}
|
503
|
-
[data-popover][role=tooltip][data-popper-placement^=left] > [data-popper-arrow]:after {
|
504
|
-
border-top-width: 1px;
|
505
|
-
border-right-width: 1px;
|
506
|
-
}
|
507
|
-
[data-popover][role=tooltip][data-popper-placement^=top] > [data-popper-arrow] {
|
508
|
-
bottom: -5px;
|
509
|
-
}
|
510
|
-
[data-popover][role=tooltip][data-popper-placement^=bottom] > [data-popper-arrow] {
|
511
|
-
top: -5px;
|
512
|
-
}
|
513
|
-
[data-popover][role=tooltip][data-popper-placement^=left] > [data-popper-arrow] {
|
514
|
-
right: -5px;
|
515
|
-
}
|
516
|
-
[data-popover][role=tooltip][data-popper-placement^=right] > [data-popper-arrow] {
|
517
|
-
left: -5px;
|
518
|
-
}
|
519
216
|
*,
|
520
217
|
::before,
|
521
218
|
::after {
|
@@ -540,7 +237,7 @@ input[type=range]::-ms-fill-lower {
|
|
540
237
|
--tw-ring-inset: ;
|
541
238
|
--tw-ring-offset-width: 0px;
|
542
239
|
--tw-ring-offset-color: #fff;
|
543
|
-
--tw-ring-color: rgb(
|
240
|
+
--tw-ring-color: rgb(59 130 246 / 0.5);
|
544
241
|
--tw-ring-offset-shadow: 0 0 #0000;
|
545
242
|
--tw-ring-shadow: 0 0 #0000;
|
546
243
|
--tw-shadow: 0 0 #0000;
|
@@ -586,7 +283,7 @@ input[type=range]::-ms-fill-lower {
|
|
586
283
|
--tw-ring-inset: ;
|
587
284
|
--tw-ring-offset-width: 0px;
|
588
285
|
--tw-ring-offset-color: #fff;
|
589
|
-
--tw-ring-color: rgb(
|
286
|
+
--tw-ring-color: rgb(59 130 246 / 0.5);
|
590
287
|
--tw-ring-offset-shadow: 0 0 #0000;
|
591
288
|
--tw-ring-shadow: 0 0 #0000;
|
592
289
|
--tw-shadow: 0 0 #0000;
|
@@ -719,10 +416,6 @@ input[type=range]::-ms-fill-lower {
|
|
719
416
|
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))) !important;
|
720
417
|
border-bottom-width: calc(1px * var(--tw-divide-y-reverse)) !important;
|
721
418
|
}
|
722
|
-
.divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
|
723
|
-
--tw-divide-opacity: 1 !important;
|
724
|
-
border-color: rgb(245 245 245 / var(--tw-divide-opacity)) !important;
|
725
|
-
}
|
726
419
|
.overflow-auto {
|
727
420
|
overflow: auto !important;
|
728
421
|
}
|
@@ -734,9 +427,6 @@ input[type=range]::-ms-fill-lower {
|
|
734
427
|
.rounded {
|
735
428
|
border-radius: 4px !important;
|
736
429
|
}
|
737
|
-
.rounded-lg {
|
738
|
-
border-radius: 0.5rem !important;
|
739
|
-
}
|
740
430
|
.border-0 {
|
741
431
|
border-width: 0px !important;
|
742
432
|
}
|
@@ -839,18 +529,14 @@ input[type=range]::-ms-fill-lower {
|
|
839
529
|
.opacity-25 {
|
840
530
|
opacity: 0.25 !important;
|
841
531
|
}
|
842
|
-
.shadow {
|
843
|
-
--tw-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 12px 4px rgba(0,0,0,0.12) !important;
|
844
|
-
--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 1px 12px 4px var(--tw-shadow-color) !important;
|
845
|
-
box-shadow:
|
846
|
-
var(--tw-ring-offset-shadow, 0 0 #0000),
|
847
|
-
var(--tw-ring-shadow, 0 0 #0000),
|
848
|
-
var(--tw-shadow) !important;
|
849
|
-
}
|
850
532
|
.outline-none {
|
851
533
|
outline: 2px solid transparent !important;
|
852
534
|
outline-offset: 2px !important;
|
853
535
|
}
|
536
|
+
.blur {
|
537
|
+
--tw-blur: blur(8px) !important;
|
538
|
+
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important;
|
539
|
+
}
|
854
540
|
.squiz-fte-form-group {
|
855
541
|
display: flex;
|
856
542
|
flex-direction: column;
|
@@ -919,6 +605,7 @@ input[type=range]::-ms-fill-lower {
|
|
919
605
|
font-family: "Open Sans" !important;
|
920
606
|
}
|
921
607
|
.formatted-text-editor.editor-wrapper {
|
608
|
+
margin: 0.5rem;
|
922
609
|
border-radius: 4px;
|
923
610
|
border-width: 2px;
|
924
611
|
border-style: solid;
|
@@ -1065,6 +752,7 @@ a {
|
|
1065
752
|
}
|
1066
753
|
.toolbar-dropdown__button {
|
1067
754
|
display: flex;
|
755
|
+
align-items: center;
|
1068
756
|
font-family:
|
1069
757
|
Open Sans,
|
1070
758
|
Arial,
|
@@ -1074,7 +762,6 @@ a {
|
|
1074
762
|
--tw-text-opacity: 1;
|
1075
763
|
color: rgb(112 112 112 / var(--tw-text-opacity));
|
1076
764
|
align-self: center;
|
1077
|
-
align-items: center;
|
1078
765
|
height: 2rem;
|
1079
766
|
padding-left: 0.5rem;
|
1080
767
|
}
|
@@ -1092,20 +779,35 @@ a {
|
|
1092
779
|
height: 1.5rem;
|
1093
780
|
}
|
1094
781
|
.toolbar-dropdown__menu {
|
1095
|
-
|
782
|
+
border-radius: 4px;
|
783
|
+
border-width: 1px;
|
784
|
+
--tw-border-opacity: 1;
|
785
|
+
border-color: rgb(224 224 224 / var(--tw-border-opacity));
|
786
|
+
--tw-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 4px 2px rgba(0,0,0,0.08);
|
787
|
+
--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 1px 4px 2px var(--tw-shadow-color);
|
788
|
+
box-shadow:
|
789
|
+
var(--tw-ring-offset-shadow, 0 0 #0000),
|
790
|
+
var(--tw-ring-shadow, 0 0 #0000),
|
791
|
+
var(--tw-shadow);
|
792
|
+
position: absolute;
|
793
|
+
margin-top: 0.75rem;
|
1096
794
|
}
|
1097
795
|
.dropdown-button {
|
1098
|
-
height: 40px;
|
1099
|
-
width: 100%;
|
1100
|
-
justify-content: space-between;
|
1101
|
-
align-items: center;
|
1102
|
-
display: flex;
|
1103
796
|
padding-left: 0.5rem;
|
1104
797
|
padding-right: 0.5rem;
|
1105
798
|
padding-top: 0.25rem;
|
1106
799
|
padding-bottom: 0.25rem;
|
1107
800
|
--tw-text-opacity: 1;
|
1108
801
|
color: rgb(112 112 112 / var(--tw-text-opacity));
|
802
|
+
height: 40px;
|
803
|
+
width: 100%;
|
804
|
+
justify-content: space-between;
|
805
|
+
align-items: center;
|
806
|
+
display: flex;
|
807
|
+
}
|
808
|
+
.dropdown-button:hover,
|
809
|
+
.dropdown-button:focus {
|
810
|
+
background-color: rgba(0, 0, 0, 0.04);
|
1109
811
|
}
|
1110
812
|
.squiz-fte-modal {
|
1111
813
|
display: flex;
|
@@ -1207,7 +909,3 @@ a {
|
|
1207
909
|
outline: 2px solid transparent !important;
|
1208
910
|
outline-offset: 2px !important;
|
1209
911
|
}
|
1210
|
-
.dark .dark\:bg-gray-700 {
|
1211
|
-
--tw-bg-opacity: 1 !important;
|
1212
|
-
background-color: rgb(79 79 79 / var(--tw-bg-opacity)) !important;
|
1213
|
-
}
|
@@ -1,12 +1,20 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { useState } from 'react';
|
2
2
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
3
|
-
import 'flowbite';
|
4
3
|
const ToolbarDropdown = ({ children, label }) => {
|
5
|
-
|
6
|
-
|
4
|
+
const [isOpen, setOpen] = useState(false);
|
5
|
+
const handleDropDown = () => {
|
6
|
+
setOpen(!isOpen);
|
7
|
+
};
|
8
|
+
const handleBlur = (event) => {
|
9
|
+
if (event.relatedTarget?.id !== 'dropdownMenuButton' && !event.target?.className.includes('is-active')) {
|
10
|
+
isOpen && handleDropDown();
|
11
|
+
}
|
12
|
+
};
|
13
|
+
return (React.createElement("div", { className: "toolbar-dropdown", onBlur: handleBlur },
|
14
|
+
React.createElement("button", { id: "dropdownHoverButton", className: "toolbar-dropdown__button", onClick: handleDropDown },
|
7
15
|
React.createElement("span", { className: "toolbar-dropdown__label" }, label),
|
8
16
|
React.createElement(ExpandMoreIcon, { className: "toolbar-dropdown__icon", "aria-hidden": "true" })),
|
9
|
-
React.createElement("div", { id: "dropdown", className:
|
10
|
-
React.createElement("ul", { "aria-labelledby": "dropdownHoverButton" }, children))));
|
17
|
+
React.createElement("div", { id: "dropdown", className: `toolbar-dropdown__menu z-10 ${isOpen ? 'block' : 'hidden'} bg-white divide-y w-169` },
|
18
|
+
React.createElement("ul", { "aria-labelledby": "dropdownHoverButton", onClick: handleDropDown }, children))));
|
11
19
|
};
|
12
20
|
export default ToolbarDropdown;
|
package/lib/ui/{DropdownButton/DropdownButton.js → ToolbarDropdownButton/ToolbarDropdownButton.js}
RENAMED
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import CheckIcon from '@mui/icons-material/Check';
|
3
3
|
const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label }) => {
|
4
|
-
return (React.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `btn dropdown-button ${isActive ? 'is-active' : ''}` },
|
4
|
+
return (React.createElement("button", { "aria-label": label, id: "dropdownMenuButton", title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `btn dropdown-button ${isActive ? 'is-active' : ''}` },
|
5
5
|
React.createElement("span", null, children || label),
|
6
6
|
isActive && React.createElement(CheckIcon, { className: "dropdown-button-icon" })));
|
7
7
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.12.0-alpha.
|
3
|
+
"version": "1.12.0-alpha.46",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -20,7 +20,6 @@
|
|
20
20
|
"@mui/icons-material": "5.11.0",
|
21
21
|
"@remirror/react": "2.0.25",
|
22
22
|
"clsx": "1.2.1",
|
23
|
-
"flowbite": "^1.6.3",
|
24
23
|
"postcss-prefix-selector": "1.16.0"
|
25
24
|
},
|
26
25
|
"devDependencies": {
|
@@ -65,5 +64,5 @@
|
|
65
64
|
"volta": {
|
66
65
|
"node": "16.19.0"
|
67
66
|
},
|
68
|
-
"gitHead": "
|
67
|
+
"gitHead": "047173c3a5a8d33be8d9becf6af6d5575d7ce914"
|
69
68
|
}
|
package/src/Editor/_editor.scss
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Center align button', () => {
|
7
|
+
it('Renders the center align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Align center' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting center align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const centerAlignButton = getByRole('button', { name: 'Align center' });
|
18
|
+
expect(centerAlignButton).toBeTruthy();
|
19
|
+
expect(centerAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(centerAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(centerAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply center alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="center"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const centerAlignButton = getByRole('button', { name: 'Align center' });
|
34
|
+
expect(centerAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(centerAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="center"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Justify align button', () => {
|
7
|
+
it('Renders the justify align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Justify' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting justify align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const justifyAlignButton = getByRole('button', { name: 'Justify' });
|
18
|
+
expect(justifyAlignButton).toBeTruthy();
|
19
|
+
expect(justifyAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(justifyAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(justifyAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply justify alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="justify"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const justifyAlignButton = getByRole('button', { name: 'Justify' });
|
34
|
+
expect(justifyAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(justifyAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="justify"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Left align button', () => {
|
7
|
+
it('Renders the left align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Align left' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting left align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const leftAlignButton = getByRole('button', { name: 'Align left' });
|
18
|
+
expect(leftAlignButton).toBeTruthy();
|
19
|
+
expect(leftAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(leftAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(leftAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply left alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const leftAlignButton = getByRole('button', { name: 'Align left' });
|
34
|
+
expect(leftAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(leftAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Right align button', () => {
|
7
|
+
it('Renders the right align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Align right' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting right align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const rightAlignButton = getByRole('button', { name: 'Align right' });
|
18
|
+
expect(rightAlignButton).toBeTruthy();
|
19
|
+
expect(rightAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(rightAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(rightAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply right alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="right"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const rightAlignButton = getByRole('button', { name: 'Align right' });
|
34
|
+
expect(rightAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(rightAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="right"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
const headings = [
|
7
|
+
['Heading 1', 'h1'],
|
8
|
+
['Heading 2', 'h2'],
|
9
|
+
['Heading 3', 'h3'],
|
10
|
+
['Heading 4', 'h4'],
|
11
|
+
['Heading 5', 'h5'],
|
12
|
+
['Heading 6', 'h6'],
|
13
|
+
];
|
14
|
+
|
15
|
+
describe('Heading button', () => {
|
16
|
+
it.each(headings)('Renders the "%s" heading button', (label) => {
|
17
|
+
const { baseElement, getByRole } = render(<Editor />);
|
18
|
+
expect(baseElement).toBeTruthy();
|
19
|
+
expect(getByRole('button', { name: label })).toBeTruthy();
|
20
|
+
});
|
21
|
+
|
22
|
+
it.each(headings)('Applies active status after selecting "%s" heading button', (label) => {
|
23
|
+
const { baseElement, getByRole } = render(<Editor />);
|
24
|
+
expect(baseElement).toBeTruthy();
|
25
|
+
|
26
|
+
const headingButton = getByRole('button', { name: label });
|
27
|
+
expect(headingButton).toBeTruthy();
|
28
|
+
expect(headingButton.className).not.toContain('is-active');
|
29
|
+
|
30
|
+
fireEvent.click(headingButton);
|
31
|
+
expect(headingButton.className).toContain('is-active');
|
32
|
+
});
|
33
|
+
|
34
|
+
it.each(headings)('Should render a check icon if button is active', (label) => {
|
35
|
+
const { baseElement, getByRole } = render(<Editor />);
|
36
|
+
expect(baseElement).toBeTruthy();
|
37
|
+
|
38
|
+
const headingButton = getByRole('button', { name: label });
|
39
|
+
expect(headingButton.querySelector('svg[data-testid="CheckIcon"]')).toBeFalsy();
|
40
|
+
|
41
|
+
fireEvent.click(headingButton);
|
42
|
+
expect(headingButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
43
|
+
});
|
44
|
+
|
45
|
+
it.each(headings)('Should apply "%s" heading tag to editor after clicking button', (label, tag) => {
|
46
|
+
const { baseElement } = render(<Editor />);
|
47
|
+
expect(baseElement).toBeTruthy();
|
48
|
+
expect(baseElement.querySelector('div.remirror-editor h1')).toBeFalsy();
|
49
|
+
|
50
|
+
const headingButton = baseElement.querySelector(`button[title="${label}"]`) as HTMLButtonElement;
|
51
|
+
expect(headingButton).toBeTruthy();
|
52
|
+
fireEvent.click(headingButton);
|
53
|
+
|
54
|
+
expect(baseElement.querySelector(`div.remirror-editor ${tag}`)).toBeTruthy();
|
55
|
+
});
|
56
|
+
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
3
|
import { HeadingExtension } from '@remirror/extension-heading';
|
4
|
-
import DropdownButton from '../../../../ui/
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
5
|
|
6
6
|
type HeadingButtonProps = {
|
7
7
|
level: number;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Paragraph button', () => {
|
7
|
+
it('Renders paragraph button', () => {
|
8
|
+
const { baseElement } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(baseElement.querySelector('button[title="Paragraph"]')).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Paragraph button active status should be set on as default', () => {
|
14
|
+
const { baseElement } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const paragraphButton = baseElement.querySelector('button[title="Paragraph"]') as HTMLButtonElement;
|
18
|
+
expect(paragraphButton).toBeTruthy();
|
19
|
+
expect(paragraphButton.className).toContain('is-active');
|
20
|
+
});
|
21
|
+
|
22
|
+
it('Should render a check icon if button is active', () => {
|
23
|
+
const { baseElement } = render(<Editor />);
|
24
|
+
expect(baseElement).toBeTruthy();
|
25
|
+
|
26
|
+
const paragraphButton = baseElement.querySelector('button[title="Paragraph"]') as HTMLButtonElement;
|
27
|
+
expect(paragraphButton).toBeTruthy();
|
28
|
+
expect(paragraphButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
29
|
+
});
|
30
|
+
});
|
@@ -1,14 +1,13 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
3
|
import { ParagraphExtension } from '@remirror/extension-paragraph';
|
4
|
-
import DropdownButton from '../../../../ui/
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
5
|
|
6
6
|
const ParagraphButton = () => {
|
7
7
|
const { convertParagraph } = useCommands<ParagraphExtension>();
|
8
8
|
const chain = useChainedCommands();
|
9
9
|
|
10
10
|
const active = useActive<ParagraphExtension>();
|
11
|
-
const enabled = convertParagraph.enabled();
|
12
11
|
|
13
12
|
const handleSelect = () => {
|
14
13
|
if (convertParagraph.enabled()) {
|
@@ -17,7 +16,7 @@ const ParagraphButton = () => {
|
|
17
16
|
};
|
18
17
|
|
19
18
|
return (
|
20
|
-
<DropdownButton handleOnClick={handleSelect} isDisabled={
|
19
|
+
<DropdownButton handleOnClick={handleSelect} isDisabled={false} isActive={active.paragraph()} label="Paragraph">
|
21
20
|
<p>Paragraph</p>
|
22
21
|
</DropdownButton>
|
23
22
|
);
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Preformatted button', () => {
|
7
|
+
it('Renders the preformatted button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Preformatted' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting preformatted button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
18
|
+
expect(preformattedButton).toBeTruthy();
|
19
|
+
expect(preformattedButton.className).not.toContain('is-active');
|
20
|
+
|
21
|
+
fireEvent.click(preformattedButton);
|
22
|
+
expect(preformattedButton.className).toContain('is-active');
|
23
|
+
});
|
24
|
+
|
25
|
+
it('Should render a check icon if button is active', () => {
|
26
|
+
const { baseElement, getByRole } = render(<Editor />);
|
27
|
+
expect(baseElement).toBeTruthy();
|
28
|
+
|
29
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
30
|
+
expect(preformattedButton.querySelector('svg[data-testid="CheckIcon"]')).toBeFalsy();
|
31
|
+
|
32
|
+
fireEvent.click(preformattedButton);
|
33
|
+
expect(preformattedButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
34
|
+
});
|
35
|
+
|
36
|
+
it('Should apply preformatted tag to editor after clicking button', () => {
|
37
|
+
const { baseElement, getByRole } = render(<Editor />);
|
38
|
+
expect(baseElement).toBeTruthy();
|
39
|
+
expect(baseElement.querySelector('div.remirror-editor pre')).toBeFalsy();
|
40
|
+
|
41
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
42
|
+
expect(preformattedButton).toBeTruthy();
|
43
|
+
fireEvent.click(preformattedButton);
|
44
|
+
|
45
|
+
expect(baseElement.querySelector(`div.remirror-editor pre`)).toBeTruthy();
|
46
|
+
});
|
47
|
+
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useActive } from '@remirror/react';
|
3
3
|
import { PreformattedExtension } from '../../../../Extensions/PreformattedExtension/PreformattedExtension';
|
4
|
-
import DropdownButton from '../../../../ui/
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
5
|
|
6
6
|
const PreformattedButton = () => {
|
7
7
|
const { togglePreformatted } = useCommands<PreformattedExtension>();
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
const dropdownButtons = [
|
7
|
+
'Paragraph',
|
8
|
+
'Heading 1',
|
9
|
+
'Heading 2',
|
10
|
+
'Heading 3',
|
11
|
+
'Heading 4',
|
12
|
+
'Heading 5',
|
13
|
+
'Heading 6',
|
14
|
+
'Preformatted',
|
15
|
+
];
|
16
|
+
|
17
|
+
const navigateKeys = ['Enter', 'Space'];
|
18
|
+
|
19
|
+
describe('Text type dropdown', () => {
|
20
|
+
it('should render the text type dropdown component', () => {
|
21
|
+
const { baseElement } = render(<Editor />);
|
22
|
+
expect(baseElement).toBeTruthy();
|
23
|
+
expect(baseElement.querySelector('button#dropdownHoverButton')).toBeTruthy();
|
24
|
+
});
|
25
|
+
|
26
|
+
it.each(dropdownButtons)('should render %s button in text type dropdown', (type) => {
|
27
|
+
const { baseElement } = render(<Editor />);
|
28
|
+
expect(baseElement).toBeTruthy();
|
29
|
+
|
30
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
31
|
+
expect(dropdownButton).toBeTruthy();
|
32
|
+
fireEvent.click(dropdownButton);
|
33
|
+
|
34
|
+
expect(baseElement.querySelector(`button[title="${type}"]`)).toBeTruthy();
|
35
|
+
});
|
36
|
+
|
37
|
+
it.each(navigateKeys)(`should display dropdown if '%s' is pressed while component is in focus`, async (key) => {
|
38
|
+
const { baseElement } = render(<Editor />);
|
39
|
+
expect(baseElement).toBeTruthy();
|
40
|
+
|
41
|
+
const dropdownMenu = baseElement.querySelector('div.toolbar-dropdown__menu') as HTMLDivElement;
|
42
|
+
expect(dropdownMenu.className).not.toContain('block'); // Hidden
|
43
|
+
|
44
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
45
|
+
fireEvent.keyDown(dropdownButton, { code: key });
|
46
|
+
|
47
|
+
setTimeout(() => {
|
48
|
+
expect(dropdownMenu.className).toContain('block'); // Visible
|
49
|
+
}, 50);
|
50
|
+
});
|
51
|
+
});
|
package/src/index.scss
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import ToolbarDropdown from './ToolbarDropdown';
|
5
|
+
|
6
|
+
describe('Toolbar dropdown', () => {
|
7
|
+
const ToolbarDropdownComponent = () => {
|
8
|
+
return (
|
9
|
+
<ToolbarDropdown label="Test dropdown">
|
10
|
+
<button>Test button</button>
|
11
|
+
<button>Test button 2</button>
|
12
|
+
</ToolbarDropdown>
|
13
|
+
);
|
14
|
+
};
|
15
|
+
|
16
|
+
it('Renders the dropdown button', () => {
|
17
|
+
render(<ToolbarDropdownComponent />);
|
18
|
+
// Check that the supplied label renders
|
19
|
+
const dropdownButton = screen.getByRole('button', { name: 'Test dropdown' });
|
20
|
+
expect(dropdownButton).toBeInTheDocument();
|
21
|
+
});
|
22
|
+
|
23
|
+
it('Renders the child elements for the dropdown', () => {
|
24
|
+
render(<ToolbarDropdownComponent />);
|
25
|
+
// Check that default value supplied renders
|
26
|
+
expect(screen.getByRole('button', { name: 'Test button' })).toBeInTheDocument();
|
27
|
+
expect(screen.getByRole('button', { name: 'Test button 2' })).toBeInTheDocument();
|
28
|
+
});
|
29
|
+
|
30
|
+
it('Renders the dropdown menu after clicking the dropdown button', () => {
|
31
|
+
const { baseElement } = render(<ToolbarDropdownComponent />);
|
32
|
+
expect(baseElement).toBeTruthy();
|
33
|
+
|
34
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
35
|
+
expect(dropdownButton).toBeTruthy();
|
36
|
+
fireEvent.click(dropdownButton);
|
37
|
+
|
38
|
+
const dropdownMenu = baseElement.querySelector('div.toolbar-dropdown__menu') as HTMLDivElement;
|
39
|
+
expect(dropdownMenu).toBeTruthy();
|
40
|
+
expect(dropdownMenu.classList).toContain('block');
|
41
|
+
});
|
42
|
+
|
43
|
+
it('Handles the onBlur call when focusing out of the menu', () => {
|
44
|
+
const { baseElement } = render(<ToolbarDropdownComponent />);
|
45
|
+
expect(baseElement).toBeTruthy();
|
46
|
+
|
47
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
48
|
+
expect(dropdownButton).toBeTruthy();
|
49
|
+
fireEvent.click(dropdownButton);
|
50
|
+
|
51
|
+
const dropdownMenu = baseElement.querySelector('div.toolbar-dropdown__menu') as HTMLDivElement;
|
52
|
+
expect(dropdownMenu).toBeTruthy();
|
53
|
+
expect(dropdownMenu.classList).toContain('block');
|
54
|
+
|
55
|
+
fireEvent.blur(dropdownMenu);
|
56
|
+
|
57
|
+
expect(dropdownMenu.classList).toContain('hidden');
|
58
|
+
});
|
59
|
+
|
60
|
+
it('Closes the dropdown menu after clicking a child element', () => {
|
61
|
+
const { baseElement } = render(<ToolbarDropdownComponent />);
|
62
|
+
expect(baseElement).toBeTruthy();
|
63
|
+
|
64
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
65
|
+
expect(dropdownButton).toBeTruthy();
|
66
|
+
fireEvent.click(dropdownButton);
|
67
|
+
|
68
|
+
const dropdownMenu = baseElement.querySelector('div.toolbar-dropdown__menu') as HTMLDivElement;
|
69
|
+
expect(dropdownMenu).toBeTruthy();
|
70
|
+
expect(dropdownMenu.classList).toContain('block');
|
71
|
+
|
72
|
+
const childElement = screen.getByRole('button', { name: 'Test button' });
|
73
|
+
expect(childElement).toBeTruthy();
|
74
|
+
fireEvent.click(childElement);
|
75
|
+
|
76
|
+
expect(dropdownMenu.classList).toContain('hidden');
|
77
|
+
});
|
78
|
+
});
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { FocusEventHandler, useState } from 'react';
|
2
2
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
3
|
-
import 'flowbite';
|
4
3
|
|
5
4
|
type ToolbarDropdownProps = {
|
6
5
|
children: JSX.Element | JSX.Element[];
|
@@ -8,24 +7,35 @@ type ToolbarDropdownProps = {
|
|
8
7
|
};
|
9
8
|
|
10
9
|
const ToolbarDropdown = ({ children, label }: ToolbarDropdownProps) => {
|
10
|
+
const [isOpen, setOpen] = useState<boolean>(false);
|
11
|
+
|
12
|
+
const handleDropDown = () => {
|
13
|
+
setOpen(!isOpen);
|
14
|
+
};
|
15
|
+
|
16
|
+
const handleBlur: FocusEventHandler<HTMLDivElement> = (event) => {
|
17
|
+
if (event.relatedTarget?.id !== 'dropdownMenuButton' && !event.target?.className.includes('is-active')) {
|
18
|
+
isOpen && handleDropDown();
|
19
|
+
}
|
20
|
+
};
|
21
|
+
|
11
22
|
return (
|
12
|
-
|
13
|
-
<button
|
14
|
-
id="dropdownHoverButton"
|
15
|
-
data-dropdown-toggle="dropdown"
|
16
|
-
className="toolbar-dropdown__button"
|
17
|
-
type="button"
|
18
|
-
>
|
23
|
+
<div className="toolbar-dropdown" onBlur={handleBlur}>
|
24
|
+
<button id="dropdownHoverButton" className="toolbar-dropdown__button" onClick={handleDropDown}>
|
19
25
|
<span className="toolbar-dropdown__label">{label}</span>
|
20
26
|
<ExpandMoreIcon className="toolbar-dropdown__icon" aria-hidden="true" />
|
21
27
|
</button>
|
28
|
+
|
22
29
|
<div
|
23
30
|
id="dropdown"
|
24
|
-
className=
|
31
|
+
className={`toolbar-dropdown__menu z-10 ${isOpen ? 'block' : 'hidden'} bg-white divide-y w-169`}
|
25
32
|
>
|
26
|
-
|
33
|
+
{/* eslint-disable-next-line */}
|
34
|
+
<ul aria-labelledby="dropdownHoverButton" onClick={handleDropDown}>
|
35
|
+
{children}
|
36
|
+
</ul>
|
27
37
|
</div>
|
28
|
-
|
38
|
+
</div>
|
29
39
|
);
|
30
40
|
};
|
31
41
|
|
@@ -1,28 +1,32 @@
|
|
1
|
-
.toolbar-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
.toolbar-dropdown {
|
2
|
+
&__button {
|
3
|
+
@apply flex items-center font-base text-md font-semibold text-gray-600;
|
4
|
+
align-self: center;
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
height: 2rem;
|
7
|
+
padding-left: 0.5rem;
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
&:active,
|
10
|
+
&:hover,
|
11
|
+
&:focus {
|
12
|
+
background-color: rgba(black, 0.04);
|
13
|
+
}
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
.toolbar-dropdown__label {
|
16
|
+
display: inline-flex;
|
17
|
+
width: 7rem;
|
18
|
+
}
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
.toolbar-dropdown__icon {
|
21
|
+
width: 1rem;
|
22
|
+
height: 1.5rem;
|
23
|
+
}
|
23
24
|
}
|
24
|
-
}
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
&__menu {
|
27
|
+
@apply rounded shadow-sm border border-gray-300;
|
28
|
+
|
29
|
+
position: absolute;
|
30
|
+
margin-top: 0.75rem;
|
31
|
+
}
|
28
32
|
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import ToolbarDropdownButton from './ToolbarDropdownButton';
|
5
|
+
|
6
|
+
describe('Toolbar dropdown', () => {
|
7
|
+
const handleSelect = jest.fn();
|
8
|
+
|
9
|
+
const ToolbarDropdownButtonComponent = () => {
|
10
|
+
return (
|
11
|
+
<ToolbarDropdownButton handleOnClick={handleSelect} isDisabled={false} isActive={true} label={'Test label'}>
|
12
|
+
<h1>Test heading 1</h1>
|
13
|
+
</ToolbarDropdownButton>
|
14
|
+
);
|
15
|
+
};
|
16
|
+
|
17
|
+
const EmptyToolbarDropdownButtonComponent = () => {
|
18
|
+
return (
|
19
|
+
<ToolbarDropdownButton handleOnClick={handleSelect} isDisabled={false} isActive={false} label={'Test label'} />
|
20
|
+
);
|
21
|
+
};
|
22
|
+
|
23
|
+
it('Renders the dropdown menu button', () => {
|
24
|
+
const { baseElement } = render(<ToolbarDropdownButtonComponent />);
|
25
|
+
expect(baseElement).toBeTruthy();
|
26
|
+
|
27
|
+
const buttonElement = baseElement.querySelector('button#dropdownMenuButton') as HTMLButtonElement;
|
28
|
+
expect(buttonElement).toBeTruthy();
|
29
|
+
expect(buttonElement.textContent).toBe('Test heading 1');
|
30
|
+
});
|
31
|
+
|
32
|
+
it('Renders the dropdown menu button active icon', () => {
|
33
|
+
const { baseElement } = render(<ToolbarDropdownButtonComponent />);
|
34
|
+
expect(baseElement).toBeTruthy();
|
35
|
+
|
36
|
+
const iconElement = baseElement.querySelector('svg.dropdown-button-icon') as SVGElement;
|
37
|
+
expect(iconElement).toBeTruthy();
|
38
|
+
});
|
39
|
+
|
40
|
+
it('Should render the label as button text if no children are provided', () => {
|
41
|
+
const { baseElement } = render(<EmptyToolbarDropdownButtonComponent />);
|
42
|
+
expect(baseElement).toBeTruthy();
|
43
|
+
|
44
|
+
const buttonElement = baseElement.querySelector('button#dropdownMenuButton') as HTMLButtonElement;
|
45
|
+
expect(buttonElement).toBeTruthy();
|
46
|
+
expect(buttonElement.textContent).toBe('Test label');
|
47
|
+
});
|
48
|
+
});
|
@@ -1,8 +1,14 @@
|
|
1
1
|
.dropdown-button {
|
2
|
+
@apply px-2 py-1 text-gray-600;
|
3
|
+
|
2
4
|
height: 40px;
|
3
5
|
width: 100%;
|
4
6
|
justify-content: space-between;
|
5
7
|
align-items: center;
|
6
8
|
display: flex;
|
7
|
-
|
9
|
+
|
10
|
+
&:hover,
|
11
|
+
&:focus {
|
12
|
+
background-color: rgba(black, 0.04);
|
13
|
+
}
|
8
14
|
}
|
package/tailwind.config.cjs
CHANGED
File without changes
|