@squiz/formatted-text-editor 1.33.1-alpha.2 → 1.33.1-alpha.3

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 (36) hide show
  1. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +9 -14
  2. package/lib/EditorToolbar/Tools/Image/ImageModal.js +3 -2
  3. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +12 -20
  4. package/lib/EditorToolbar/Tools/Link/LinkModal.js +3 -2
  5. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +1 -1
  6. package/lib/Extensions/LinkExtension/LinkExtension.js +1 -1
  7. package/lib/index.css +77 -2
  8. package/lib/ui/Fields/Checkbox/Checkbox.d.ts +8 -0
  9. package/lib/ui/Fields/Checkbox/Checkbox.js +47 -0
  10. package/lib/ui/Modal/Modal.d.ts +1 -0
  11. package/lib/ui/Modal/Modal.js +3 -2
  12. package/lib/ui/Tabs/Tabs.d.ts +10 -0
  13. package/lib/ui/Tabs/Tabs.js +46 -0
  14. package/package.json +2 -2
  15. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +2 -2
  16. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +12 -24
  17. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +11 -10
  18. package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +1 -0
  19. package/src/EditorToolbar/Tools/Image/ImageModal.tsx +3 -2
  20. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +10 -11
  21. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +21 -37
  22. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +8 -8
  23. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +3 -2
  24. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +1 -1
  25. package/src/Extensions/LinkExtension/LinkExtension.ts +1 -1
  26. package/src/index.scss +1 -0
  27. package/src/ui/Fields/Checkbox/Checkbox.spec.tsx +50 -0
  28. package/src/ui/Fields/Checkbox/Checkbox.tsx +49 -0
  29. package/src/ui/Fields/Checkbox/_checkbox.scss +26 -0
  30. package/src/ui/Modal/FormModal.spec.tsx +2 -1
  31. package/src/ui/Modal/Modal.spec.tsx +15 -7
  32. package/src/ui/Modal/Modal.tsx +4 -2
  33. package/src/ui/Tabs/Tabs.spec.tsx +44 -0
  34. package/src/ui/Tabs/Tabs.tsx +41 -0
  35. package/lib/ui/Fields/Select/Select.d.ts +0 -12
  36. package/lib/ui/Fields/Select/Select.js +0 -53
@@ -35,18 +35,18 @@ const LinkOff_1 = __importDefault(require("@mui/icons-material/LinkOff"));
35
35
  const InsertLinkRounded_1 = __importDefault(require("@mui/icons-material/InsertLinkRounded"));
36
36
  const clsx_1 = __importDefault(require("clsx"));
37
37
  const Extensions_1 = require("../../../../Extensions/Extensions");
38
- const Select_1 = require("../../../../ui/Fields/Select/Select");
39
38
  const EditorContext_1 = require("../../../../Editor/EditorContext");
40
39
  const validation_1 = require("../../../../utils/validation");
40
+ const Tabs_1 = require("../../../../ui/Tabs/Tabs");
41
41
  const imageTypeOptions = {
42
- [Extensions_1.NodeName.Image]: { label: 'External image' },
43
- [Extensions_1.NodeName.AssetImage]: { label: 'Asset image' },
42
+ [Extensions_1.NodeName.AssetImage]: { label: 'From source' },
43
+ [Extensions_1.NodeName.Image]: { label: 'From URL' },
44
44
  };
45
45
  const ImageForm = ({ data, onSubmit }) => {
46
46
  const { register, handleSubmit, setValue, watch, formState: { errors }, } = (0, react_hook_form_1.useForm)({
47
47
  defaultValues: data,
48
48
  });
49
- const imageType = watch('imageType') || Extensions_1.NodeName.Image;
49
+ const imageType = watch('imageType') || Extensions_1.NodeName.AssetImage;
50
50
  const context = (0, react_1.useContext)(EditorContext_1.EditorContext);
51
51
  const [aspectRatioFromWidth, setAspectRatioFromWidth] = (0, react_1.useState)(9 / 16);
52
52
  const [aspectRatioFromHeight, setAspectRatioFromHeight] = (0, react_1.useState)(16 / 9);
@@ -88,8 +88,8 @@ const ImageForm = ({ data, onSubmit }) => {
88
88
  setAspectRatioLocked(!aspectRatioLocked);
89
89
  };
90
90
  return (react_1.default.createElement("form", { className: "squiz-fte-form", onSubmit: handleSubmit(onSubmit) },
91
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
92
- react_1.default.createElement(Select_1.Select, { name: "imageType", label: "Type", value: imageType, options: imageTypeOptions, onChange: (value) => setValue('imageType', value) })),
91
+ react_1.default.createElement("div", { className: "squiz-fte-form-group mb-4" },
92
+ react_1.default.createElement(Tabs_1.Tabs, { value: imageType, options: imageTypeOptions, onChange: (value) => setValue('imageType', value) })),
93
93
  imageType === Extensions_1.NodeName.Image && (react_1.default.createElement(react_1.default.Fragment, null,
94
94
  react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
95
95
  react_1.default.createElement(Input_1.Input, { label: "Source", required: true, error: errors?.image?.src?.message, ...register('image.src', {
@@ -107,13 +107,6 @@ const ImageForm = ({ data, onSubmit }) => {
107
107
  noEmptySpaces: validation_1.noEmptySpacesValidation,
108
108
  },
109
109
  }) })),
110
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
111
- react_1.default.createElement(Input_1.Input, { label: "Alternative description", required: true, error: errors?.image?.alt?.message, ...register('image.alt', {
112
- required: 'Alternative description is required',
113
- validate: {
114
- noEmptySpaces: validation_1.noEmptySpacesValidation,
115
- },
116
- }) })),
117
110
  react_1.default.createElement("div", { className: "flex flex-row" },
118
111
  react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
119
112
  react_1.default.createElement(Input_1.Input, { label: "Width", type: "number", required: true, error: errors?.image?.width?.message, ...register('image.width', {
@@ -140,7 +133,9 @@ const ImageForm = ({ data, onSubmit }) => {
140
133
  }
141
134
  },
142
135
  },
143
- }) }))))),
136
+ }) }))),
137
+ react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
138
+ react_1.default.createElement(Input_1.Input, { label: "Alternative description", error: errors?.image?.alt?.message, ...register('image.alt') })))),
144
139
  imageType === Extensions_1.NodeName.AssetImage && (react_1.default.createElement(react_1.default.Fragment, null,
145
140
  react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
146
141
  react_1.default.createElement(Input_1.Input, { label: "Asset ID", required: true, error: errors?.assetImage?.matrixAssetId?.message, ...register('assetImage.matrixAssetId', {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const ImageForm_1 = __importDefault(require("./Form/ImageForm"));
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const react_2 = require("@remirror/react");
9
+ const ImageRounded_1 = __importDefault(require("@mui/icons-material/ImageRounded"));
9
10
  const FormModal_1 = __importDefault(require("../../../ui/Modal/FormModal"));
10
11
  const Extensions_1 = require("../../../Extensions/Extensions");
11
12
  const ImageModal = ({ onCancel, onSubmit }) => {
@@ -13,11 +14,11 @@ const ImageModal = ({ onCancel, onSubmit }) => {
13
14
  const currentImage = selection?.node;
14
15
  const currentImageAttrs = { ...currentImage?.attrs };
15
16
  const formData = {
16
- imageType: currentImage?.type.name === Extensions_1.NodeName.AssetImage ? Extensions_1.NodeName.AssetImage : Extensions_1.NodeName.Image,
17
+ imageType: currentImage?.type.name === Extensions_1.NodeName.Image ? Extensions_1.NodeName.Image : Extensions_1.NodeName.AssetImage,
17
18
  image: currentImage?.type?.name === Extensions_1.NodeName.Image ? currentImageAttrs : {},
18
19
  assetImage: currentImage?.type?.name === Extensions_1.NodeName.AssetImage ? currentImageAttrs : {},
19
20
  };
20
- return (react_1.default.createElement(FormModal_1.default, { title: "Image", onCancel: onCancel },
21
+ return (react_1.default.createElement(FormModal_1.default, { title: "Image", icon: react_1.default.createElement(ImageRounded_1.default, null), onCancel: onCancel },
21
22
  react_1.default.createElement(ImageForm_1.default, { data: formData, onSubmit: onSubmit })));
22
23
  };
23
24
  exports.default = ImageModal;
@@ -31,28 +31,25 @@ const react_1 = __importStar(require("react"));
31
31
  const clsx_1 = __importDefault(require("clsx"));
32
32
  const react_hook_form_1 = require("react-hook-form");
33
33
  const Input_1 = require("../../../../ui/Fields/Input/Input");
34
- const Select_1 = require("../../../../ui/Fields/Select/Select");
34
+ const Checkbox_1 = require("../../../../ui/Fields/Checkbox/Checkbox");
35
35
  const common_1 = require("../../../../Extensions/LinkExtension/common");
36
36
  const EditorContext_1 = require("../../../../Editor/EditorContext");
37
37
  const Extensions_1 = require("../../../../Extensions/Extensions");
38
38
  const validation_1 = require("../../../../utils/validation");
39
+ const Tabs_1 = require("../../../../ui/Tabs/Tabs");
39
40
  const linkTypeOptions = {
40
- [Extensions_1.MarkName.Link]: { label: 'Link to URL' },
41
- [Extensions_1.MarkName.AssetLink]: { label: 'Link to asset' },
42
- };
43
- const targetOptions = {
44
- [common_1.LinkTarget.Self]: { label: 'Current window' },
45
- [common_1.LinkTarget.Blank]: { label: 'New window' },
41
+ [Extensions_1.MarkName.AssetLink]: { label: 'From source' },
42
+ [Extensions_1.MarkName.Link]: { label: 'From URL' },
46
43
  };
47
44
  const LinkForm = ({ data, onSubmit }) => {
48
45
  const context = (0, react_1.useContext)(EditorContext_1.EditorContext);
49
46
  const { register, handleSubmit, setValue, watch, formState: { errors }, } = (0, react_hook_form_1.useForm)({
50
47
  defaultValues: data,
51
48
  });
52
- const linkType = watch('linkType') || Extensions_1.MarkName.Link;
49
+ const linkType = watch('linkType') || Extensions_1.MarkName.AssetLink;
53
50
  return (react_1.default.createElement("form", { className: "squiz-fte-form", onSubmit: handleSubmit(onSubmit) },
54
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
55
- react_1.default.createElement(Select_1.Select, { name: "linkType", label: "Type", value: linkType, options: linkTypeOptions, onChange: (value) => setValue('linkType', value) })),
51
+ react_1.default.createElement("div", { className: "squiz-fte-form-group mb-4" },
52
+ react_1.default.createElement(Tabs_1.Tabs, { value: linkType, options: linkTypeOptions, onChange: (value) => setValue('linkType', value) })),
56
53
  linkType === Extensions_1.MarkName.Link && (react_1.default.createElement(react_1.default.Fragment, null,
57
54
  react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
58
55
  react_1.default.createElement(Input_1.Input, { label: "URL", required: true, error: errors?.link?.href?.message, ...register('link.href', {
@@ -69,14 +66,9 @@ const LinkForm = ({ data, onSubmit }) => {
69
66
  },
70
67
  }) })),
71
68
  react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
72
- react_1.default.createElement(Input_1.Input, { label: "Title", required: true, error: errors?.link?.title?.message, ...register('link.title', {
73
- required: 'Title is required',
74
- validate: {
75
- noEmptySpaces: validation_1.noEmptySpacesValidation,
76
- },
77
- }) })),
78
- react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-0') },
79
- react_1.default.createElement(Select_1.Select, { name: "link.target", label: "Target", value: data?.link?.target || '_self', options: targetOptions, onChange: (value) => setValue('link.target', value) })))),
69
+ react_1.default.createElement(Input_1.Input, { label: "Title", error: errors?.link?.title?.message, ...register('link.title') })),
70
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
71
+ react_1.default.createElement(Checkbox_1.Checkbox, { label: "Open link in new window", onChange: (value) => setValue('link.target', value), defaultChecked: data?.link?.target === common_1.LinkTarget.Blank, unchecked: common_1.LinkTarget.Self, checked: common_1.LinkTarget.Blank })))),
80
72
  linkType === Extensions_1.MarkName.AssetLink && (react_1.default.createElement(react_1.default.Fragment, null,
81
73
  react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
82
74
  react_1.default.createElement(Input_1.Input, { label: "Asset ID", required: true, error: errors?.assetLink?.matrixAssetId?.message, ...register('assetLink.matrixAssetId', {
@@ -97,7 +89,7 @@ const LinkForm = ({ data, onSubmit }) => {
97
89
  noEmptySpaces: validation_1.noEmptySpacesValidation,
98
90
  },
99
91
  }) })),
100
- react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-0') },
101
- react_1.default.createElement(Select_1.Select, { name: "assetLink.target", label: "Target", value: data?.assetLink?.target || '_self', options: targetOptions, onChange: (value) => setValue('assetLink.target', value) }))))));
92
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
93
+ react_1.default.createElement(Checkbox_1.Checkbox, { label: "Open link in new window", onChange: (value) => setValue('assetLink.target', value), defaultChecked: data?.assetLink?.target === '_blank', unchecked: '_self', checked: '_blank' }))))));
102
94
  };
103
95
  exports.LinkForm = LinkForm;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const LinkForm_1 = require("./Form/LinkForm");
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const react_2 = require("@remirror/react");
9
+ const InsertLinkRounded_1 = __importDefault(require("@mui/icons-material/InsertLinkRounded"));
9
10
  const FormModal_1 = __importDefault(require("../../../ui/Modal/FormModal"));
10
11
  const hooks_1 = require("../../../hooks");
11
12
  const Extensions_1 = require("../../../Extensions/Extensions");
@@ -14,13 +15,13 @@ const LinkModal = ({ onCancel, onSubmit }) => {
14
15
  const { selection, marks } = (0, hooks_1.useExpandedSelection)([Extensions_1.MarkName.Link, Extensions_1.MarkName.AssetLink]);
15
16
  const selectedText = helpers.getTextBetween(selection.from, selection.to, state.doc);
16
17
  const data = {
17
- linkType: marks[0]?.type?.name === Extensions_1.MarkName.AssetLink ? Extensions_1.MarkName.AssetLink : Extensions_1.MarkName.Link,
18
+ linkType: marks[0]?.type?.name === Extensions_1.MarkName.Link ? Extensions_1.MarkName.Link : Extensions_1.MarkName.AssetLink,
18
19
  text: selectedText,
19
20
  link: { ...marks.find((mark) => mark.type.name === 'link')?.attrs },
20
21
  assetLink: { ...marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink)?.attrs },
21
22
  range: { from: selection.from, to: selection.to },
22
23
  };
23
- return (react_1.default.createElement(FormModal_1.default, { title: "Link", onCancel: onCancel },
24
+ return (react_1.default.createElement(FormModal_1.default, { title: "Link", icon: react_1.default.createElement(InsertLinkRounded_1.default, null), onCancel: onCancel },
24
25
  react_1.default.createElement(LinkForm_1.LinkForm, { data: data, onSubmit: onSubmit })));
25
26
  };
26
27
  exports.default = LinkModal;
@@ -20,7 +20,7 @@ let AssetLinkExtension = class AssetLinkExtension extends core_1.MarkExtension {
20
20
  createMarkSpec(extra, override) {
21
21
  return {
22
22
  inclusive: false,
23
- excludes: Extensions_1.MarkName.Link,
23
+ excludes: [this.name, Extensions_1.MarkName.Link].join(' '),
24
24
  ...override,
25
25
  attrs: {
26
26
  ...extra.defaults(),
@@ -19,7 +19,7 @@ let LinkExtension = class LinkExtension extends core_1.MarkExtension {
19
19
  createMarkSpec(extra, override) {
20
20
  return {
21
21
  inclusive: false,
22
- excludes: Extensions_1.MarkName.AssetLink,
22
+ excludes: [this.name, Extensions_1.MarkName.AssetLink].join(' '),
23
23
  ...override,
24
24
  attrs: {
25
25
  ...extra.defaults(),
package/lib/index.css CHANGED
@@ -404,18 +404,33 @@
404
404
  .squiz-fte-scope .mb-2 {
405
405
  margin-bottom: 0.5rem !important;
406
406
  }
407
+ .squiz-fte-scope .mb-4 {
408
+ margin-bottom: 1rem !important;
409
+ }
407
410
  .squiz-fte-scope .ml-3 {
408
411
  margin-left: 0.75rem !important;
409
412
  }
410
413
  .squiz-fte-scope .ml-auto {
411
414
  margin-left: auto !important;
412
415
  }
416
+ .squiz-fte-scope .mr-1 {
417
+ margin-right: 0.25rem !important;
418
+ }
419
+ .squiz-fte-scope .mr-1\.5 {
420
+ margin-right: 0.375rem !important;
421
+ }
413
422
  .squiz-fte-scope .mr-2 {
414
423
  margin-right: 0.5rem !important;
415
424
  }
416
425
  .squiz-fte-scope .mt-1 {
417
426
  margin-top: 0.25rem !important;
418
427
  }
428
+ .squiz-fte-scope .mt-\[-1px\] {
429
+ margin-top: -1px !important;
430
+ }
431
+ .squiz-fte-scope .mt-\[7px\] {
432
+ margin-top: 7px !important;
433
+ }
419
434
  .squiz-fte-scope .block {
420
435
  display: block !important;
421
436
  }
@@ -425,9 +440,21 @@
425
440
  .squiz-fte-scope .flex {
426
441
  display: flex !important;
427
442
  }
443
+ .squiz-fte-scope .grid {
444
+ display: grid !important;
445
+ }
428
446
  .squiz-fte-scope .hidden {
429
447
  display: none !important;
430
448
  }
449
+ .squiz-fte-scope .h-10 {
450
+ height: 2.5rem !important;
451
+ }
452
+ .squiz-fte-scope .h-\[3px\] {
453
+ height: 3px !important;
454
+ }
455
+ .squiz-fte-scope .w-11\/12 {
456
+ width: 91.666667% !important;
457
+ }
431
458
  .squiz-fte-scope .w-169 {
432
459
  width: 169px !important;
433
460
  }
@@ -445,17 +472,29 @@
445
472
  -moz-user-select: none !important;
446
473
  user-select: none !important;
447
474
  }
475
+ .squiz-fte-scope .grid-flow-col {
476
+ grid-auto-flow: column !important;
477
+ }
448
478
  .squiz-fte-scope .flex-row {
449
479
  flex-direction: row !important;
450
480
  }
481
+ .squiz-fte-scope .flex-col {
482
+ flex-direction: column !important;
483
+ }
451
484
  .squiz-fte-scope .items-center {
452
485
  align-items: center !important;
453
486
  }
487
+ .squiz-fte-scope .justify-between {
488
+ justify-content: space-between !important;
489
+ }
454
490
  .squiz-fte-scope .divide-y > :not([hidden]) ~ :not([hidden]) {
455
491
  --tw-divide-y-reverse: 0 !important;
456
492
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))) !important;
457
493
  border-bottom-width: calc(1px * var(--tw-divide-y-reverse)) !important;
458
494
  }
495
+ .squiz-fte-scope .self-center {
496
+ align-self: center !important;
497
+ }
459
498
  .squiz-fte-scope .overflow-auto {
460
499
  overflow: auto !important;
461
500
  }
@@ -467,12 +506,19 @@
467
506
  .squiz-fte-scope .rounded {
468
507
  border-radius: 4px !important;
469
508
  }
509
+ .squiz-fte-scope .rounded-t-sm {
510
+ border-top-left-radius: 0.125rem !important;
511
+ border-top-right-radius: 0.125rem !important;
512
+ }
470
513
  .squiz-fte-scope .border-0 {
471
514
  border-width: 0px !important;
472
515
  }
473
516
  .squiz-fte-scope .border-2 {
474
517
  border-width: 2px !important;
475
518
  }
519
+ .squiz-fte-scope .border-b {
520
+ border-bottom-width: 1px !important;
521
+ }
476
522
  .squiz-fte-scope .border-gray-300 {
477
523
  --tw-border-opacity: 1 !important;
478
524
  border-color: rgb(224 224 224 / var(--tw-border-opacity)) !important;
@@ -493,6 +539,10 @@
493
539
  --tw-bg-opacity: 1 !important;
494
540
  background-color: rgb(237 237 237 / var(--tw-bg-opacity)) !important;
495
541
  }
542
+ .squiz-fte-scope .bg-gray-800 {
543
+ --tw-bg-opacity: 1 !important;
544
+ background-color: rgb(61 61 61 / var(--tw-bg-opacity)) !important;
545
+ }
496
546
  .squiz-fte-scope .bg-transparent {
497
547
  background-color: transparent !important;
498
548
  }
@@ -511,8 +561,8 @@
511
561
  padding-top: 0.5rem !important;
512
562
  padding-bottom: 0.5rem !important;
513
563
  }
514
- .squiz-fte-scope .pb-2 {
515
- padding-bottom: 0.5rem !important;
564
+ .squiz-fte-scope .pb-4 {
565
+ padding-bottom: 1rem !important;
516
566
  }
517
567
  .squiz-fte-scope .pl-3 {
518
568
  padding-left: 0.75rem !important;
@@ -533,6 +583,9 @@
533
583
  .squiz-fte-scope .text-md {
534
584
  font-size: 0.875rem !important;
535
585
  }
586
+ .squiz-fte-scope .font-bold {
587
+ font-weight: 700 !important;
588
+ }
536
589
  .squiz-fte-scope .font-semibold {
537
590
  font-weight: 600 !important;
538
591
  }
@@ -901,6 +954,28 @@
901
954
  .squiz-fte-scope .dropdown-button:focus {
902
955
  background-color: rgba(0, 0, 0, 0.04);
903
956
  }
957
+ .squiz-fte-scope .squiz-fte-checkbox {
958
+ --tw-text-opacity: 1;
959
+ color: rgb(61 61 61 / var(--tw-text-opacity));
960
+ font-size: 14px;
961
+ display: flex;
962
+ align-items: center;
963
+ margin-top: 0.75rem;
964
+ gap: 0.75rem;
965
+ }
966
+ .squiz-fte-scope .squiz-fte-checkbox .checkbox {
967
+ display: flex;
968
+ justify-content: center;
969
+ align-items: center;
970
+ width: 1.25rem;
971
+ height: 1.25rem;
972
+ background-color: #fff;
973
+ border: 2px solid #e0e0e0;
974
+ border-radius: 4px;
975
+ }
976
+ .squiz-fte-scope .squiz-fte-checkbox .checkbox svg {
977
+ width: 100%;
978
+ }
904
979
  .squiz-fte-scope .squiz-fte-modal {
905
980
  display: flex;
906
981
  width: 100%;
@@ -0,0 +1,8 @@
1
+ export type CheckboxProps<TChecked, TUnchecked> = {
2
+ label: string;
3
+ onChange: (value: TChecked | TUnchecked) => void;
4
+ defaultChecked?: boolean;
5
+ unchecked: TUnchecked;
6
+ checked: TChecked;
7
+ };
8
+ export declare const Checkbox: <TChecked, TUnchecked>({ label, onChange, defaultChecked, unchecked, checked, }: CheckboxProps<TChecked, TUnchecked>) => JSX.Element;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Checkbox = void 0;
30
+ const react_1 = __importStar(require("react"));
31
+ const CheckRounded_1 = __importDefault(require("@mui/icons-material/CheckRounded"));
32
+ const Checkbox = ({ label, onChange, defaultChecked = false, unchecked, checked, }) => {
33
+ const [toggled, setToggled] = (0, react_1.useState)(defaultChecked);
34
+ (0, react_1.useEffect)(() => {
35
+ if (toggled) {
36
+ onChange(checked);
37
+ }
38
+ else {
39
+ onChange(unchecked);
40
+ }
41
+ }, [toggled]);
42
+ const toggleCheckbox = () => setToggled(!toggled);
43
+ return (react_1.default.createElement("div", { className: "squiz-fte-checkbox" },
44
+ react_1.default.createElement("button", { type: "button", role: "checkbox", "aria-label": label, "aria-checked": toggled, className: "checkbox", onClick: toggleCheckbox }, toggled && react_1.default.createElement(CheckRounded_1.default, null)),
45
+ react_1.default.createElement("button", { type: "button", className: "label", onClick: toggleCheckbox }, label)));
46
+ };
47
+ exports.Checkbox = Checkbox;
@@ -1,6 +1,7 @@
1
1
  import React, { ReactElement } from 'react';
2
2
  export type ModalProps = {
3
3
  title: string;
4
+ icon: ReactElement;
4
5
  children: ReactElement;
5
6
  onCancel: () => void;
6
7
  onSubmit?: () => void;
@@ -31,7 +31,7 @@ const react_dom_1 = require("react-dom");
31
31
  const CloseRounded_1 = __importDefault(require("@mui/icons-material/CloseRounded"));
32
32
  const base_1 = require("@mui/base");
33
33
  const clsx_1 = __importDefault(require("clsx"));
34
- const Modal = ({ children, title, onCancel, onSubmit, className }, ref) => {
34
+ const Modal = ({ children, title, icon, onCancel, onSubmit, className }, ref) => {
35
35
  const content = (0, react_1.useRef)(null);
36
36
  const container = (0, react_1.useMemo)(() => {
37
37
  const element = document.createElement('div');
@@ -65,7 +65,8 @@ const Modal = ({ children, title, onCancel, onSubmit, className }, ref) => {
65
65
  react_1.default.createElement("div", { ref: ref, className: (0, clsx_1.default)('squiz-fte-modal-wrapper', className), tabIndex: -1 },
66
66
  react_1.default.createElement("div", { className: "w-modal-sm my-6 mx-auto" },
67
67
  react_1.default.createElement("div", { className: "squiz-fte-modal" },
68
- react_1.default.createElement("div", { className: "squiz-fte-modal-header p-6 pb-2" },
68
+ react_1.default.createElement("div", { className: "squiz-fte-modal-header p-6 pb-4" },
69
+ react_1.default.createElement("div", { className: "squiz-fte-modal-header-icon mr-1.5 mt-[-1px]" }, icon),
69
70
  react_1.default.createElement("h2", { className: "font-semibold text-gray-900 text-heading-2" }, title),
70
71
  react_1.default.createElement("button", { type: "button", className: "ml-auto -mr-3 -mt-3 bg-transparent border-0 text-gray-600 font-semibold outline-none focus:outline-none hover:text-color-gray-800", onClick: onCancel, "aria-label": "Close" },
71
72
  react_1.default.createElement(CloseRounded_1.default, null))),
@@ -0,0 +1,10 @@
1
+ export type TabOptions = Record<string, TabOption>;
2
+ export type TabOption = {
3
+ label: string;
4
+ };
5
+ export type TabsProps = {
6
+ value: string;
7
+ options: TabOptions;
8
+ onChange?: (value: string) => void;
9
+ };
10
+ export declare const Tabs: ({ value, options, onChange }: TabsProps) => JSX.Element;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Tabs = void 0;
30
+ const react_1 = require("@headlessui/react");
31
+ const react_2 = __importStar(require("react"));
32
+ const clsx_1 = __importDefault(require("clsx"));
33
+ const Tabs = ({ value, options, onChange }) => (react_2.default.createElement(react_1.Tab.Group
34
+ // Check what index the selected tab is, otherwise default to first tab
35
+ , {
36
+ // Check what index the selected tab is, otherwise default to first tab
37
+ defaultIndex: Object.keys(options).indexOf(value) || 0,
38
+ // Check what the selected tab key is and trigger onChange
39
+ onChange: (index) => {
40
+ const selectedTab = Object.keys(options)[index];
41
+ onChange?.(selectedTab);
42
+ } },
43
+ react_2.default.createElement(react_1.Tab.List, { className: "grid grid-flow-col h-10 border-b border-gray-300" }, Object.entries(options).map(([key, option]) => (react_2.default.createElement(react_1.Tab, { key: key, as: react_2.Fragment }, ({ selected }) => (react_2.default.createElement("div", { className: "flex flex-col justify-between", "data-testid": key, "aria-selected": selected },
44
+ react_2.default.createElement("button", { type: "button", className: (0, clsx_1.default)('mt-[7px] text-gray-800', selected && 'font-bold') }, option.label),
45
+ selected && react_2.default.createElement("span", { className: "h-[3px] bg-gray-800 w-11/12 self-center rounded-t-sm" })))))))));
46
+ exports.Tabs = Tabs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.33.1-alpha.2",
3
+ "version": "1.33.1-alpha.3",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -74,5 +74,5 @@
74
74
  "volta": {
75
75
  "node": "18.15.0"
76
76
  },
77
- "gitHead": "593039274a41b1b39826723fc8bd3bb05224def6"
77
+ "gitHead": "5189e21fcd9ad091397cacfa50765caf6138b034"
78
78
  }
@@ -19,7 +19,7 @@ describe('Image Form', () => {
19
19
  it('Renders the form with the relevant fields for arbitrary images', () => {
20
20
  render(<ImageForm data={data} onSubmit={handleSubmit} />);
21
21
 
22
- expect(screen.getByLabelText('Type')).toHaveTextContent('External image');
22
+ expect(document.querySelector('div[data-headlessui-state="selected"]')).toHaveTextContent('From URL');
23
23
  expect(screen.getByLabelText('Source')).toHaveValue('https://httpcats.com/302.jpg');
24
24
  expect(screen.getByLabelText('Alternative description')).toHaveValue('Cat with mouse in mouth');
25
25
  expect(screen.getByLabelText('Width')).toHaveValue(1600);
@@ -38,7 +38,7 @@ describe('Image Form', () => {
38
38
  />,
39
39
  );
40
40
 
41
- expect(screen.getByLabelText('Type')).toHaveTextContent('Asset image');
41
+ expect(document.querySelector('div[data-headlessui-state="selected"]')).toHaveTextContent('From source');
42
42
  expect(screen.getByLabelText('Asset ID')).toHaveValue('100');
43
43
  });
44
44
 
@@ -10,9 +10,9 @@ import clsx from 'clsx';
10
10
  import { NodeName } from '../../../../Extensions/Extensions';
11
11
  import { AssetImageAttributes } from '../../../../Extensions/ImageExtension/AssetImageExtension';
12
12
  import { DeepPartial } from '../../../../types';
13
- import { Select, SelectOptions } from '../../../../ui/Fields/Select/Select';
14
13
  import { EditorContext } from '../../../../Editor/EditorContext';
15
14
  import { noEmptySpacesValidation, regexDataURI } from '../../../../utils/validation';
15
+ import { TabOptions, Tabs } from '../../../../ui/Tabs/Tabs';
16
16
 
17
17
  export type ImageFormData = {
18
18
  imageType: NodeName;
@@ -20,9 +20,9 @@ export type ImageFormData = {
20
20
  assetImage: AssetImageAttributes;
21
21
  };
22
22
 
23
- const imageTypeOptions: SelectOptions = {
24
- [NodeName.Image]: { label: 'External image' },
25
- [NodeName.AssetImage]: { label: 'Asset image' },
23
+ const imageTypeOptions: TabOptions = {
24
+ [NodeName.AssetImage]: { label: 'From source' },
25
+ [NodeName.Image]: { label: 'From URL' },
26
26
  };
27
27
 
28
28
  export type FormProps = {
@@ -41,7 +41,7 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
41
41
  } = useForm<ImageFormData>({
42
42
  defaultValues: data,
43
43
  });
44
- const imageType = watch('imageType') || NodeName.Image;
44
+ const imageType = watch('imageType') || NodeName.AssetImage;
45
45
  const context = useContext(EditorContext);
46
46
  const [aspectRatioFromWidth, setAspectRatioFromWidth] = useState(9 / 16);
47
47
  const [aspectRatioFromHeight, setAspectRatioFromHeight] = useState(16 / 9);
@@ -88,10 +88,8 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
88
88
 
89
89
  return (
90
90
  <form className="squiz-fte-form" onSubmit={handleSubmit(onSubmit)}>
91
- <div className="squiz-fte-form-group mb-2">
92
- <Select
93
- name="imageType"
94
- label="Type"
91
+ <div className="squiz-fte-form-group mb-4">
92
+ <Tabs
95
93
  value={imageType}
96
94
  options={imageTypeOptions}
97
95
  onChange={(value) => setValue('imageType', value as NodeName)}
@@ -121,19 +119,6 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
121
119
  })}
122
120
  />
123
121
  </div>
124
- <div className="squiz-fte-form-group mb-2">
125
- <Input
126
- label="Alternative description"
127
- required
128
- error={errors?.image?.alt?.message}
129
- {...register('image.alt', {
130
- required: 'Alternative description is required',
131
- validate: {
132
- noEmptySpaces: noEmptySpacesValidation,
133
- },
134
- })}
135
- />
136
- </div>
137
122
  <div className="flex flex-row">
138
123
  <div className="squiz-fte-form-group mb-2">
139
124
  <Input
@@ -146,7 +131,7 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
146
131
  required: 'Width is required',
147
132
  validate: {
148
133
  isValidWidth: (value) => {
149
- if (value && !(value > 0)) {
134
+ if (value && !((value as number) > 0)) {
150
135
  return 'Must be higher than 0';
151
136
  }
152
137
  },
@@ -175,7 +160,7 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
175
160
  required: 'Height is required',
176
161
  validate: {
177
162
  isValidHeight: (value) => {
178
- if (value && !(value > 0)) {
163
+ if (value && !((value as number) > 0)) {
179
164
  return 'Must be higher than 0';
180
165
  }
181
166
  },
@@ -184,6 +169,9 @@ const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
184
169
  />
185
170
  </div>
186
171
  </div>
172
+ <div className="squiz-fte-form-group mb-2">
173
+ <Input label="Alternative description" error={errors?.image?.alt?.message} {...register('image.alt')} />
174
+ </div>
187
175
  </>
188
176
  )}
189
177
  {imageType === NodeName.AssetImage && (