@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.
Files changed (29) hide show
  1. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.js +1 -1
  2. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.js +2 -3
  3. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +1 -1
  4. package/lib/index.css +29 -331
  5. package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +0 -1
  6. package/lib/ui/ToolbarDropdown/ToolbarDropdown.js +14 -6
  7. package/lib/ui/{DropdownButton/DropdownButton.js → ToolbarDropdownButton/ToolbarDropdownButton.js} +1 -1
  8. package/package.json +2 -3
  9. package/src/Editor/_editor.scss +1 -1
  10. package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.spec.tsx +39 -0
  11. package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.spec.tsx +39 -0
  12. package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.spec.tsx +39 -0
  13. package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.spec.tsx +39 -0
  14. package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.spec.tsx +56 -0
  15. package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.tsx +1 -1
  16. package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.spec.tsx +30 -0
  17. package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.tsx +2 -3
  18. package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.spec.tsx +47 -0
  19. package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +1 -1
  20. package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.spec.tsx +51 -0
  21. package/src/index.scss +1 -1
  22. package/src/ui/ToolbarDropdown/ToolbarDropdown.spec.tsx +78 -0
  23. package/src/ui/ToolbarDropdown/ToolbarDropdown.tsx +22 -12
  24. package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +25 -21
  25. package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.spec.tsx +48 -0
  26. package/src/ui/{DropdownButton/DropdownButton.tsx → ToolbarDropdownButton/ToolbarDropdownButton.tsx} +1 -0
  27. package/src/ui/{DropdownButton/_dropdown-button.scss → ToolbarDropdownButton/_toolbar-dropdown-button.scss} +7 -1
  28. package/tailwind.config.cjs +1 -1
  29. /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/DropdownButton/DropdownButton';
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/DropdownButton/DropdownButton';
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: !enabled, isActive: active.paragraph(), label: "Paragraph" },
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/DropdownButton/DropdownButton';
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(63 131 248 / 0.5);
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(63 131 248 / 0.5);
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
- inset: 0rem auto auto 1rem !important;
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,4 +1,3 @@
1
- import 'flowbite';
2
1
  type ToolbarDropdownProps = {
3
2
  children: JSX.Element | JSX.Element[];
4
3
  label: string;
@@ -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
- return (React.createElement(React.Fragment, null,
6
- React.createElement("button", { id: "dropdownHoverButton", "data-dropdown-toggle": "dropdown", className: "toolbar-dropdown__button", type: "button" },
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: "toolbar-dropdown__menu z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-169 dark:bg-gray-700" },
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;
@@ -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.45",
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": "d89e680e3a4435dc0befaa442ffb1a4eb8a1e4b1"
67
+ "gitHead": "047173c3a5a8d33be8d9becf6af6d5575d7ce914"
69
68
  }
@@ -2,7 +2,7 @@
2
2
  font-family: 'Open Sans' !important;
3
3
 
4
4
  &.editor-wrapper {
5
- @apply bg-white rounded border-gray-300 border-2 border-solid;
5
+ @apply bg-white rounded border-gray-300 border-2 border-solid m-2;
6
6
  }
7
7
 
8
8
  .remirror-editor-wrapper {
@@ -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/DropdownButton/DropdownButton';
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/DropdownButton/DropdownButton';
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={!enabled} isActive={active.paragraph()} label="Paragraph">
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/DropdownButton/DropdownButton';
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
@@ -13,6 +13,6 @@
13
13
 
14
14
  @import './ui/ToolbarButton/toolbar-button';
15
15
  @import './ui/ToolbarDropdown/toolbar-dropdown';
16
- @import './ui/DropdownButton/dropdown-button';
16
+ @import './ui/ToolbarDropdownButton/toolbar-dropdown-button';
17
17
 
18
18
  @import './ui/Modal/modal';
@@ -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="toolbar-dropdown__menu z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-169 dark:bg-gray-700"
31
+ className={`toolbar-dropdown__menu z-10 ${isOpen ? 'block' : 'hidden'} bg-white divide-y w-169`}
25
32
  >
26
- <ul aria-labelledby="dropdownHoverButton">{children}</ul>
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-dropdown__button {
2
- @apply flex font-base text-md font-semibold text-gray-600;
3
- align-self: center;
4
- align-items: center;
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
- height: 2rem;
7
- padding-left: 0.5rem;
6
+ height: 2rem;
7
+ padding-left: 0.5rem;
8
8
 
9
- &:active,
10
- &:hover,
11
- &:focus {
12
- background-color: rgba(black, 0.04);
13
- }
9
+ &:active,
10
+ &:hover,
11
+ &:focus {
12
+ background-color: rgba(black, 0.04);
13
+ }
14
14
 
15
- .toolbar-dropdown__label {
16
- display: inline-flex;
17
- width: 7rem;
18
- }
15
+ .toolbar-dropdown__label {
16
+ display: inline-flex;
17
+ width: 7rem;
18
+ }
19
19
 
20
- .toolbar-dropdown__icon {
21
- width: 1rem;
22
- height: 1.5rem;
20
+ .toolbar-dropdown__icon {
21
+ width: 1rem;
22
+ height: 1.5rem;
23
+ }
23
24
  }
24
- }
25
25
 
26
- .toolbar-dropdown__menu {
27
- inset: 0rem auto auto 1rem !important;
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
+ });
@@ -13,6 +13,7 @@ const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label }
13
13
  return (
14
14
  <button
15
15
  aria-label={label}
16
+ id="dropdownMenuButton"
16
17
  title={label}
17
18
  type="button"
18
19
  onClick={handleOnClick}
@@ -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
- @apply px-2 py-1 text-gray-600;
9
+
10
+ &:hover,
11
+ &:focus {
12
+ background-color: rgba(black, 0.04);
13
+ }
8
14
  }
@@ -78,6 +78,6 @@ module.exports = {
78
78
  },
79
79
  },
80
80
  },
81
- plugins: [require('flowbite/plugin')],
81
+ plugins: [],
82
82
  important: true,
83
83
  };