@frontify/guideline-blocks-settings 0.27.0 → 0.28.0

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 (214) hide show
  1. package/.eslintrc.js +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +24 -5
  4. package/package.json +46 -10
  5. package/postcss.config.js +8 -0
  6. package/setupTests.ts +13 -0
  7. package/src/components/Attachments/AttachmentItem.tsx +257 -0
  8. package/src/components/Attachments/Attachments.spec.ct.tsx +151 -0
  9. package/src/components/Attachments/Attachments.tsx +221 -0
  10. package/src/components/Attachments/index.ts +4 -0
  11. package/src/components/Attachments/types.ts +30 -0
  12. package/src/components/BlockInjectButton/BlockInjectButton.spec.ct.tsx +48 -0
  13. package/src/components/BlockInjectButton/BlockInjectButton.tsx +212 -0
  14. package/src/components/BlockInjectButton/index.ts +4 -0
  15. package/src/components/BlockInjectButton/types.ts +18 -0
  16. package/src/components/BlockItemWrapper/BlockItemWrapper.spec.ct.tsx +146 -0
  17. package/src/components/BlockItemWrapper/BlockItemWrapper.tsx +76 -0
  18. package/src/components/BlockItemWrapper/Toolbar.tsx +128 -0
  19. package/src/components/BlockItemWrapper/constants.ts +4 -0
  20. package/src/components/BlockItemWrapper/index.ts +5 -0
  21. package/src/components/BlockItemWrapper/types.ts +46 -0
  22. package/src/components/DownloadButton/DownloadButton.spec.ct.tsx +20 -0
  23. package/src/components/DownloadButton/DownloadButton.tsx +36 -0
  24. package/src/components/DownloadButton/index.ts +3 -0
  25. package/src/components/DownloadButton/types.ts +5 -0
  26. package/src/components/RichTextEditor/RichTextEditor.spec.ct.tsx +204 -0
  27. package/src/components/RichTextEditor/RichTextEditor.tsx +62 -0
  28. package/src/components/RichTextEditor/SerializedText.tsx +25 -0
  29. package/src/components/RichTextEditor/constants.ts +3 -0
  30. package/src/components/RichTextEditor/index.ts +6 -0
  31. package/src/components/RichTextEditor/pluginPresets/defaultPluginsWithLinkChooser.tsx +53 -0
  32. package/src/components/RichTextEditor/pluginPresets/index.ts +3 -0
  33. package/src/components/RichTextEditor/plugins/ButtonPlugin/ButtonMarkupElement/ButtonMarkupElementNode.tsx +74 -0
  34. package/src/components/RichTextEditor/plugins/ButtonPlugin/ButtonMarkupElement/index.ts +11 -0
  35. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/ButtonButton.tsx +20 -0
  36. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/ButtonToolbarButton.tsx +56 -0
  37. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/CustomFloatingButton.tsx +19 -0
  38. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/EditButtonModal/EditModal.tsx +42 -0
  39. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButton.tsx +37 -0
  40. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButtonEditButton.tsx +22 -0
  41. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButtonUrlInput.tsx +30 -0
  42. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/InsertButtonModal.tsx +81 -0
  43. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/types.ts +13 -0
  44. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/useInsertModal.ts +143 -0
  45. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/UnlinkButton.tsx +31 -0
  46. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/floatingButtonStore.ts +46 -0
  47. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/index.ts +12 -0
  48. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEdit.ts +113 -0
  49. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEnter.ts +21 -0
  50. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEscape.ts +30 -0
  51. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonInsert.ts +71 -0
  52. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useVirtualFloatingButton.ts +22 -0
  53. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/index.ts +3 -0
  54. package/src/components/RichTextEditor/plugins/ButtonPlugin/createButtonPlugin.ts +116 -0
  55. package/src/components/RichTextEditor/plugins/ButtonPlugin/index.ts +7 -0
  56. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/index.ts +8 -0
  57. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/insertButton.ts +17 -0
  58. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/submitFloatingButton.ts +40 -0
  59. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/unwrapButton.ts +68 -0
  60. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/upsertButton.ts +198 -0
  61. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/upsertButtonText.ts +40 -0
  62. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/wrapButton.ts +30 -0
  63. package/src/components/RichTextEditor/plugins/ButtonPlugin/types.ts +13 -0
  64. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/createButtonNode.ts +28 -0
  65. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/getButtonStyle.ts +14 -0
  66. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/getUrl.ts +18 -0
  67. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/index.ts +8 -0
  68. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/styles.ts +77 -0
  69. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButton.ts +23 -0
  70. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButtonEdit.ts +30 -0
  71. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButtonInsert.ts +45 -0
  72. package/src/components/RichTextEditor/plugins/ButtonPlugin/withButton.ts +106 -0
  73. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/CustomFloatingLink.tsx +26 -0
  74. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/EditModal.tsx +43 -0
  75. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/index.ts +4 -0
  76. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/useFloatingLinkEdit.ts +113 -0
  77. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/FloatingLink.tsx +45 -0
  78. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/InsertLinkModal.tsx +5 -0
  79. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/InsertModal.tsx +105 -0
  80. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/index.ts +4 -0
  81. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/types.ts +16 -0
  82. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/useFloatingLinkInsert.ts +73 -0
  83. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/useInsertModal.ts +136 -0
  84. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkButton.tsx +38 -0
  85. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkMarkupElement/LinkMarkupElementNode.tsx +36 -0
  86. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkMarkupElement/index.ts +11 -0
  87. package/src/components/RichTextEditor/plugins/LinkPlugin/id.ts +3 -0
  88. package/src/components/RichTextEditor/plugins/LinkPlugin/index.ts +48 -0
  89. package/src/components/RichTextEditor/plugins/LinkPlugin/types.ts +12 -0
  90. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/getUrl.ts +30 -0
  91. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/index.ts +4 -0
  92. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/relativeUrlRegex.spec.ts +35 -0
  93. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/relativeUrlRegex.ts +3 -0
  94. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/url.spec.ts +75 -0
  95. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/url.ts +21 -0
  96. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom1Plugin.tsx +61 -0
  97. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom2Plugin.tsx +61 -0
  98. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom3Plugin.tsx +62 -0
  99. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading1Plugin.tsx +61 -0
  100. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading2Plugin.tsx +58 -0
  101. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading3Plugin.tsx +58 -0
  102. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading4Plugin.tsx +59 -0
  103. package/src/components/RichTextEditor/plugins/TextStylePlugins/helpers.tsx +44 -0
  104. package/src/components/RichTextEditor/plugins/TextStylePlugins/imageCaptionPlugin.tsx +61 -0
  105. package/src/components/RichTextEditor/plugins/TextStylePlugins/imageTitlePlugin.tsx +61 -0
  106. package/src/components/RichTextEditor/plugins/TextStylePlugins/index.ts +15 -0
  107. package/src/components/RichTextEditor/plugins/TextStylePlugins/paragraphPlugin.tsx +58 -0
  108. package/src/components/RichTextEditor/plugins/TextStylePlugins/quotePlugin.tsx +62 -0
  109. package/src/components/RichTextEditor/plugins/index.ts +6 -0
  110. package/src/components/RichTextEditor/plugins/shared/LinkSelector/DocumentLink.tsx +80 -0
  111. package/src/components/RichTextEditor/plugins/shared/LinkSelector/DocumentLinks.tsx +97 -0
  112. package/src/components/RichTextEditor/plugins/shared/LinkSelector/LinkSelector.spec.ct.tsx +138 -0
  113. package/src/components/RichTextEditor/plugins/shared/LinkSelector/LinkSelector.tsx +80 -0
  114. package/src/components/RichTextEditor/plugins/shared/LinkSelector/PageLink.tsx +83 -0
  115. package/src/components/RichTextEditor/plugins/shared/LinkSelector/PageLinks.tsx +68 -0
  116. package/src/components/RichTextEditor/plugins/shared/LinkSelector/SectionLink.tsx +37 -0
  117. package/src/components/RichTextEditor/plugins/shared/LinkSelector/index.ts +3 -0
  118. package/src/components/RichTextEditor/plugins/styles.ts +179 -0
  119. package/src/components/RichTextEditor/serializer/index.ts +3 -0
  120. package/src/components/RichTextEditor/serializer/nodes/button.ts +25 -0
  121. package/src/components/RichTextEditor/serializer/nodes/checkItemNode.ts +29 -0
  122. package/src/components/RichTextEditor/serializer/nodes/default.ts +52 -0
  123. package/src/components/RichTextEditor/serializer/nodes/link.ts +25 -0
  124. package/src/components/RichTextEditor/serializer/nodes/mentionHtmlNode.ts +17 -0
  125. package/src/components/RichTextEditor/serializer/serializeNodesToHtmlRecursive.ts +134 -0
  126. package/src/components/RichTextEditor/serializer/serializeToHtml.ts +49 -0
  127. package/src/components/RichTextEditor/serializer/utlis/reactCssPropsToCss.ts +21 -0
  128. package/src/components/RichTextEditor/serializer/utlis/serializeLeafToHtml.ts +32 -0
  129. package/src/components/RichTextEditor/types.ts +23 -0
  130. package/src/components/index.ts +7 -0
  131. package/src/helpers/addHttps.spec.ts +42 -0
  132. package/src/helpers/addHttps.ts +15 -0
  133. package/src/helpers/convertToRichTextValue.spec.ts +32 -0
  134. package/src/helpers/convertToRichTextValue.ts +6 -0
  135. package/src/helpers/customCoordinatesGetterFactory.spec.ts +69 -0
  136. package/src/helpers/customCoordinatesGetterFactory.ts +39 -0
  137. package/src/helpers/hasRichTextValue.spec.ts +63 -0
  138. package/src/helpers/hasRichTextValue.ts +29 -0
  139. package/src/helpers/index.ts +8 -0
  140. package/src/helpers/isDownloadable.spec.ts +47 -0
  141. package/src/helpers/isDownloadable.ts +7 -0
  142. package/src/helpers/mapColorPalettes.spec.ts +146 -0
  143. package/src/helpers/mapColorPalettes.ts +22 -0
  144. package/src/hooks/index.ts +4 -0
  145. package/src/hooks/useAttachments.spec.ts +79 -0
  146. package/src/hooks/useAttachments.ts +46 -0
  147. package/src/hooks/useDndSensors.spec.ts +40 -0
  148. package/src/hooks/useDndSensors.ts +23 -0
  149. package/src/index.ts +8 -0
  150. package/src/settings/background.spec.ts +173 -0
  151. package/src/settings/background.ts +49 -0
  152. package/src/settings/border.spec.ts +76 -0
  153. package/src/settings/border.ts +90 -0
  154. package/src/settings/borderRadius.spec.ts +30 -0
  155. package/src/settings/borderRadius.ts +73 -0
  156. package/src/settings/borderRadiusExtended.spec.ts +52 -0
  157. package/src/settings/borderRadiusExtended.ts +84 -0
  158. package/src/settings/defaultValues.ts +21 -0
  159. package/src/settings/gutter.spec.ts +60 -0
  160. package/src/settings/gutter.ts +75 -0
  161. package/src/settings/index.ts +14 -0
  162. package/src/settings/margin.spec.ts +42 -0
  163. package/src/settings/margin.ts +72 -0
  164. package/src/settings/marginExtended.spec.ts +45 -0
  165. package/src/settings/marginExtended.ts +91 -0
  166. package/src/settings/padding.spec.ts +42 -0
  167. package/src/settings/padding.ts +73 -0
  168. package/src/settings/paddingExtended.spec.ts +45 -0
  169. package/src/settings/paddingExtended.ts +91 -0
  170. package/src/settings/security.spec.ts +87 -0
  171. package/src/settings/security.ts +61 -0
  172. package/src/settings/securityDownloadable.spec.ts +46 -0
  173. package/src/settings/securityDownloadable.ts +33 -0
  174. package/src/settings/securityGlobalControl.ts +42 -0
  175. package/src/settings/types.ts +128 -0
  176. package/src/utilities/color/getReadableColor.spec.ts +32 -0
  177. package/src/utilities/color/getReadableColor.ts +34 -0
  178. package/src/utilities/color/index.ts +10 -0
  179. package/src/utilities/color/isDark.spec.ts +33 -0
  180. package/src/utilities/color/isDark.ts +29 -0
  181. package/src/utilities/color/setAlpha.spec.ts +28 -0
  182. package/src/utilities/color/setAlpha.ts +14 -0
  183. package/src/utilities/color/toColorObject.spec.ts +19 -0
  184. package/src/utilities/color/toColorObject.ts +16 -0
  185. package/src/utilities/color/toHex8String.spec.ts +17 -0
  186. package/src/utilities/color/toHex8String.ts +14 -0
  187. package/src/utilities/color/toHexString.spec.ts +17 -0
  188. package/src/utilities/color/toHexString.ts +10 -0
  189. package/src/utilities/color/toRgbaString.spec.ts +12 -0
  190. package/src/utilities/color/toRgbaString.ts +14 -0
  191. package/src/utilities/color/toShortRgba.spec.ts +16 -0
  192. package/src/utilities/color/toShortRgba.ts +35 -0
  193. package/src/utilities/index.ts +5 -0
  194. package/src/utilities/moveItemInArray.spec.ts +17 -0
  195. package/src/utilities/moveItemInArray.ts +21 -0
  196. package/src/utilities/react/getBackgroundColorStyles.spec.ts +18 -0
  197. package/src/utilities/react/getBackgroundColorStyles.ts +11 -0
  198. package/src/utilities/react/getBorderStyles.spec.ts +39 -0
  199. package/src/utilities/react/getBorderStyles.ts +21 -0
  200. package/src/utilities/react/getRadiusStyles.spec.ts +25 -0
  201. package/src/utilities/react/getRadiusStyles.ts +8 -0
  202. package/src/utilities/react/index.ts +6 -0
  203. package/src/utilities/react/joinClassNames.spec.ts +18 -0
  204. package/src/utilities/react/joinClassNames.ts +10 -0
  205. package/tailwind.config.js +27 -0
  206. package/tsconfig.json +3 -1
  207. package/vite.config.ts +11 -1
  208. package/dist/index.cjs.js +0 -2
  209. package/dist/index.cjs.js.map +0 -1
  210. package/dist/index.d.ts +0 -147
  211. package/dist/index.es.js +0 -9
  212. package/dist/index.es.js.map +0 -1
  213. package/dist/index.umd.js +0 -2
  214. package/dist/index.umd.js.map +0 -1
@@ -0,0 +1,146 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { IconMagnifier16 } from '@frontify/fondue';
4
+ import { mount } from 'cypress/react18';
5
+ import { BlockItemWrapper } from './BlockItemWrapper';
6
+
7
+ const BlockItemWrapperSelector = '[data-test-id="block-item-wrapper"]';
8
+ const ToolbarSelector = '[data-test-id="block-item-wrapper-toolbar"]';
9
+ const FlyoutSelector = '[data-test-id="block-item-wrapper-toolbar-flyout"]';
10
+ const MenuItemSelector = '[data-test-id="menu-item"]';
11
+ const ToolbarButtonSelector = '[data-test-id="block-item-wrapper-toolbar-btn"]';
12
+ const ChildSelector = '[data-test-id="block-item-wrapper-child"]';
13
+
14
+ describe('Block Item Wrapper', () => {
15
+ it('should render the wrapper and the children', () => {
16
+ mount(
17
+ <BlockItemWrapper toolbarFlyoutItems={[]} toolbarItems={[]}>
18
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
19
+ </BlockItemWrapper>
20
+ );
21
+ cy.get(BlockItemWrapperSelector).should('exist');
22
+ cy.get(ChildSelector).should('exist');
23
+ });
24
+
25
+ it('should render the outline class', () => {
26
+ mount(
27
+ <BlockItemWrapper toolbarFlyoutItems={[]} toolbarItems={[]}>
28
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
29
+ </BlockItemWrapper>
30
+ );
31
+ cy.get(BlockItemWrapperSelector).should('have.class', 'hover:tw-outline');
32
+ });
33
+
34
+ it('should not render the outline class if the hide prop is set', () => {
35
+ mount(
36
+ <BlockItemWrapper toolbarFlyoutItems={[]} toolbarItems={[]} shouldHideWrapper>
37
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
38
+ </BlockItemWrapper>
39
+ );
40
+ cy.get(BlockItemWrapperSelector).should('not.exist');
41
+ });
42
+
43
+ it('should render the right amount of toolbar items', () => {
44
+ mount(
45
+ <BlockItemWrapper
46
+ toolbarFlyoutItems={[]}
47
+ toolbarItems={[
48
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
49
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
50
+ ]}
51
+ >
52
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
53
+ </BlockItemWrapper>
54
+ );
55
+ cy.get(ToolbarButtonSelector).should('have.length', 2);
56
+ });
57
+
58
+ it('should render the flyout button with the right amount of menu items', () => {
59
+ mount(
60
+ <BlockItemWrapper
61
+ toolbarFlyoutItems={[
62
+ [
63
+ {
64
+ icon: <IconMagnifier16 />,
65
+ onClick: cy.stub(),
66
+ title: 'Test title',
67
+ },
68
+ ],
69
+ [
70
+ {
71
+ icon: <IconMagnifier16 />,
72
+ onClick: cy.stub(),
73
+ title: 'Test title',
74
+ },
75
+ {
76
+ icon: <IconMagnifier16 />,
77
+ onClick: cy.stub(),
78
+ title: 'Test title',
79
+ },
80
+ ],
81
+ ]}
82
+ toolbarItems={[
83
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
84
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
85
+ ]}
86
+ >
87
+ <div data-test-id="block-item-wrapper-child" className="tw-mt-8 tw-w-8 tw-h-8 tw-bg-red-50" />
88
+ </BlockItemWrapper>
89
+ );
90
+ cy.get(ToolbarButtonSelector).eq(0).focus();
91
+ cy.get(FlyoutSelector).should('exist');
92
+ cy.get(FlyoutSelector).click({ force: true });
93
+ cy.get(MenuItemSelector).should('have.length', 3);
94
+ });
95
+
96
+ it('should render the outline if a toolbar button is focused', () => {
97
+ mount(
98
+ <BlockItemWrapper
99
+ toolbarFlyoutItems={[]}
100
+ toolbarItems={[
101
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
102
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
103
+ ]}
104
+ >
105
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
106
+ </BlockItemWrapper>
107
+ );
108
+ cy.get(ToolbarButtonSelector).eq(0).focus();
109
+ cy.get(BlockItemWrapperSelector).should('have.css', 'outline-style', 'solid');
110
+ });
111
+
112
+ it('should render the toolbar if a button is focused', () => {
113
+ mount(
114
+ <BlockItemWrapper
115
+ toolbarFlyoutItems={[]}
116
+ shouldHideComponent={false}
117
+ toolbarItems={[
118
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
119
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
120
+ ]}
121
+ >
122
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
123
+ </BlockItemWrapper>
124
+ );
125
+ cy.get(ToolbarButtonSelector).eq(0).focus();
126
+ cy.get(ToolbarSelector).should('be.visible');
127
+ });
128
+
129
+ it('should render the outline and the toolbar if enabled', () => {
130
+ mount(
131
+ <BlockItemWrapper
132
+ toolbarFlyoutItems={[]}
133
+ toolbarItems={[
134
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
135
+ { icon: <IconMagnifier16 />, onClick: cy.stub(), tooltip: 'Test tooltip' },
136
+ ]}
137
+ shouldBeShown
138
+ >
139
+ <div data-test-id="block-item-wrapper-child" className="tw-w-8 tw-h-8 tw-bg-red-50" />
140
+ </BlockItemWrapper>
141
+ );
142
+ cy.get(ChildSelector).should('exist');
143
+ cy.get(BlockItemWrapperSelector).should('have.css', 'outline-style', 'solid');
144
+ cy.get(ToolbarSelector).should('be.visible');
145
+ });
146
+ });
@@ -0,0 +1,76 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { PropsWithChildren, useEffect, useRef, useState } from 'react';
4
+ import { joinClassNames } from '../../utilities';
5
+ import { Toolbar } from './Toolbar';
6
+ import { BlockItemWrapperProps, ToolbarItem } from './types';
7
+
8
+ export const BlockItemWrapper = ({
9
+ children,
10
+ toolbarFlyoutItems,
11
+ toolbarItems,
12
+ shouldHideWrapper,
13
+ shouldHideComponent = false,
14
+ isDragging,
15
+ shouldFillContainer,
16
+ outlineOffset = 2,
17
+ shouldBeShown = false,
18
+ }: PropsWithChildren<BlockItemWrapperProps>) => {
19
+ const [isFlyoutOpen, setIsFlyoutOpen] = useState(shouldBeShown);
20
+ const [isFlyoutDisabled, setIsFlyoutDisabled] = useState(false);
21
+ const wrapperRef = useRef<HTMLDivElement>(null);
22
+
23
+ useEffect(() => {
24
+ if (!isFlyoutOpen) {
25
+ // This prevents automatic refocusing of the trigger element
26
+ setIsFlyoutDisabled(true);
27
+ }
28
+ }, [isFlyoutOpen]);
29
+
30
+ if (shouldHideWrapper) {
31
+ return children;
32
+ }
33
+
34
+ const items = toolbarItems?.filter((item): item is ToolbarItem => item !== undefined);
35
+
36
+ return (
37
+ <div
38
+ ref={wrapperRef}
39
+ onFocus={() => setIsFlyoutDisabled(false)}
40
+ onPointerEnter={() => setIsFlyoutDisabled(false)}
41
+ data-test-id="block-item-wrapper"
42
+ style={{
43
+ outlineOffset,
44
+ }}
45
+ className={joinClassNames([
46
+ 'tw-relative tw-group tw-outline-1 tw-outline-box-selected-inverse',
47
+ shouldFillContainer && 'tw-flex-1 tw-h-full tw-w-full',
48
+ 'hover:tw-outline focus-within:tw-outline',
49
+ (isFlyoutOpen || shouldBeShown) && 'tw-outline',
50
+ shouldHideComponent && 'tw-opacity-0',
51
+ ])}
52
+ >
53
+ <div
54
+ style={{
55
+ right: -1 - outlineOffset,
56
+ bottom: `calc(100% - ${2 + outlineOffset}px)`,
57
+ }}
58
+ className={joinClassNames([
59
+ 'tw-pointer-events-none tw-absolute tw-bottom-[calc(100%-4px)] tw-right-[-3px] tw-w-full tw-opacity-0 tw-z-10',
60
+ 'group-hover:tw-opacity-100 group-focus:tw-opacity-100 focus-within:tw-opacity-100',
61
+ (isFlyoutOpen || shouldBeShown) && 'tw-opacity-100',
62
+ ])}
63
+ >
64
+ <Toolbar
65
+ isFlyoutOpen={isFlyoutOpen}
66
+ isFlyoutDisabled={isFlyoutDisabled}
67
+ setIsFlyoutOpen={setIsFlyoutOpen}
68
+ flyoutItems={toolbarFlyoutItems}
69
+ items={items}
70
+ isDragging={isDragging}
71
+ />
72
+ </div>
73
+ {children}
74
+ </div>
75
+ );
76
+ };
@@ -0,0 +1,128 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import {
4
+ ActionMenu,
5
+ Flyout,
6
+ IconDotsHorizontal16,
7
+ MenuItemContentSize,
8
+ Tooltip,
9
+ TooltipPosition,
10
+ } from '@frontify/fondue';
11
+ import { ToolbarProps } from './types';
12
+ import { joinClassNames } from '../../utilities';
13
+ import { DEFAULT_DRAGGING_TOOLTIP, DEFAULT_DRAG_TOOLTIP } from './constants';
14
+
15
+ export const Toolbar = ({
16
+ items,
17
+ flyoutItems,
18
+ isFlyoutOpen,
19
+ setIsFlyoutOpen,
20
+ isDragging,
21
+ isFlyoutDisabled,
22
+ }: ToolbarProps) => {
23
+ return (
24
+ <div data-test-id="block-item-wrapper-toolbar" className="tw-flex tw-justify-end">
25
+ <div className="tw-bg-white tw-text-box-selected-inverse tw-pointer-events-auto tw-flex tw-flex-shrink-0 tw-gap-[2px] tw-px-[1px] tw-spacing tw-items-center tw-h-7 tw-self-start tw-border tw-border-box-selected-inverse tw-rounded">
26
+ {items.map((item, i) =>
27
+ 'draggableProps' in item ? (
28
+ <Tooltip
29
+ key={i}
30
+ withArrow
31
+ hoverDelay={0}
32
+ enterDelay={300}
33
+ open={isDragging}
34
+ position={TooltipPosition.Top}
35
+ content={
36
+ <div>
37
+ {isDragging ? DEFAULT_DRAGGING_TOOLTIP : item.tooltip ?? DEFAULT_DRAG_TOOLTIP}
38
+ </div>
39
+ }
40
+ triggerElement={
41
+ <button
42
+ ref={item.setActivatorNodeRef}
43
+ data-test-id="block-item-wrapper-toolbar-btn"
44
+ {...item.draggableProps}
45
+ className={joinClassNames([
46
+ 'tw-bg-base tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-rounded-sm',
47
+ isDragging
48
+ ? 'tw-cursor-grabbing tw-bg-box-selected-pressed'
49
+ : 'tw-cursor-grab hover:tw-bg-box-selected-hover',
50
+ ])}
51
+ >
52
+ {item.icon}
53
+ </button>
54
+ }
55
+ />
56
+ ) : (
57
+ <Tooltip
58
+ key={i}
59
+ withArrow
60
+ enterDelay={300}
61
+ hoverDelay={0}
62
+ disabled={isDragging}
63
+ position={TooltipPosition.Top}
64
+ content={<div>{item.tooltip ?? ''}</div>}
65
+ triggerElement={
66
+ <button
67
+ data-test-id="block-item-wrapper-toolbar-btn"
68
+ onClick={item.onClick}
69
+ className="tw-bg-base hover:tw-bg-box-selected-hover active:tw-bg-box-selected-pressed tw-cursor-pointer tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-rounded-sm"
70
+ >
71
+ {item.icon}
72
+ </button>
73
+ }
74
+ />
75
+ )
76
+ )}
77
+ {flyoutItems.length > 0 && (
78
+ <div className="tw-flex tw-flex-shrink-0 tw-flex-1 tw-h-6">
79
+ <Flyout
80
+ isOpen={isFlyoutOpen && !isDragging}
81
+ isTriggerDisabled={isFlyoutDisabled}
82
+ legacyFooter={false}
83
+ fitContent
84
+ hug={false}
85
+ onOpenChange={setIsFlyoutOpen}
86
+ trigger={
87
+ <Tooltip
88
+ withArrow
89
+ hoverDelay={0}
90
+ enterDelay={300}
91
+ disabled={isDragging}
92
+ position={TooltipPosition.Top}
93
+ content={<div>Options</div>}
94
+ triggerElement={
95
+ <div
96
+ data-test-id="block-item-wrapper-toolbar-flyout"
97
+ className="tw-bg-base hover:tw-bg-box-selected-hover active:tw-bg-box-selected-pressed tw-cursor-pointer tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-rounded-sm"
98
+ >
99
+ <IconDotsHorizontal16 />
100
+ </div>
101
+ }
102
+ />
103
+ }
104
+ >
105
+ <ActionMenu
106
+ menuBlocks={flyoutItems.map((block, blockIndex) => ({
107
+ id: blockIndex.toString(),
108
+ menuItems: block.map((item, itemIndex) => ({
109
+ id: blockIndex.toString() + itemIndex.toString(),
110
+ size: MenuItemContentSize.XSmall,
111
+ title: item.title,
112
+ style: item.style,
113
+ onClick: () => {
114
+ setIsFlyoutOpen(false);
115
+ item.onClick();
116
+ },
117
+ initialValue: true,
118
+ decorator: <div className="tw-mr-2">{item.icon}</div>,
119
+ })),
120
+ }))}
121
+ />
122
+ </Flyout>
123
+ </div>
124
+ )}
125
+ </div>
126
+ </div>
127
+ );
128
+ };
@@ -0,0 +1,4 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export const DEFAULT_DRAG_TOOLTIP = 'Drag or press ↵ to move';
4
+ export const DEFAULT_DRAGGING_TOOLTIP = 'Move with ↑↓←→ and confirm with ↵';
@@ -0,0 +1,5 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './BlockItemWrapper';
4
+ export * from './types';
5
+ export * from './constants';
@@ -0,0 +1,46 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { MenuItemStyle } from '@frontify/fondue';
4
+
5
+ export type BlockItemWrapperProps = {
6
+ shouldHideWrapper?: boolean;
7
+ shouldHideComponent?: boolean;
8
+ toolbarItems: (ToolbarItem | undefined)[];
9
+ toolbarFlyoutItems: FlyoutToolbarItem[][];
10
+ isDragging?: boolean;
11
+ shouldFillContainer?: boolean;
12
+ outlineOffset?: number;
13
+ shouldBeShown?: boolean;
14
+ };
15
+
16
+ export type ToolbarProps = {
17
+ items: ToolbarItem[];
18
+ flyoutItems: FlyoutToolbarItem[][];
19
+ isFlyoutOpen: boolean;
20
+ setIsFlyoutOpen: (isOpen: boolean) => void;
21
+ isDragging?: boolean;
22
+ isFlyoutDisabled?: boolean;
23
+ };
24
+
25
+ type BaseToolbarItem = {
26
+ icon: JSX.Element;
27
+ tooltip?: string;
28
+ };
29
+
30
+ type DraghandleToolbarItem = BaseToolbarItem & {
31
+ draggableProps: Record<string, unknown>;
32
+ setActivatorNodeRef?: (node: HTMLElement | null) => void;
33
+ };
34
+
35
+ type ButtonToolbarItem = BaseToolbarItem & {
36
+ onClick: () => void;
37
+ };
38
+
39
+ export type ToolbarItem = DraghandleToolbarItem | ButtonToolbarItem;
40
+
41
+ type FlyoutToolbarItem = {
42
+ title: string;
43
+ onClick: () => void;
44
+ icon: JSX.Element;
45
+ style?: MenuItemStyle;
46
+ };
@@ -0,0 +1,20 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { mount } from 'cypress/react18';
4
+ import { DownloadButton } from '.';
5
+
6
+ const DownloadButtonSelector = '[data-test-id="download-button"]';
7
+
8
+ describe('DownloadButton', () => {
9
+ it('renders component', () => {
10
+ mount(<DownloadButton onDownload={cy.stub()} />);
11
+ cy.get(DownloadButtonSelector).should('exist');
12
+ });
13
+
14
+ it('calls onDownload on click', () => {
15
+ const onDownloadStub = cy.stub().as('downloadStub');
16
+ mount(<DownloadButton onDownload={onDownloadStub} />);
17
+ cy.get(DownloadButtonSelector).click();
18
+ cy.get('@downloadStub').should('have.been.called');
19
+ });
20
+ });
@@ -0,0 +1,36 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { DownloadButtonProps } from './types';
4
+ import { useFocusRing } from '@react-aria/focus';
5
+ import { FOCUS_STYLE, IconArrowCircleDown16, Tooltip, TooltipPosition } from '@frontify/fondue';
6
+ import { joinClassNames } from '../../utilities';
7
+
8
+ export const DownloadButton = ({ onDownload }: DownloadButtonProps) => {
9
+ const { isFocused, focusProps } = useFocusRing();
10
+
11
+ return (
12
+ <Tooltip
13
+ withArrow
14
+ position={TooltipPosition.Top}
15
+ content="Download"
16
+ enterDelay={500}
17
+ triggerElement={
18
+ <button
19
+ tabIndex={0}
20
+ aria-label="Download"
21
+ {...focusProps}
22
+ className={joinClassNames(['tw-outline-none tw-rounded', isFocused && FOCUS_STYLE])}
23
+ onClick={onDownload}
24
+ onPointerDown={(e) => e.preventDefault()}
25
+ >
26
+ <span
27
+ data-test-id="download-button"
28
+ className="tw-flex tw-text-xs tw-font-body tw-items-center tw-gap-1 tw-rounded-full tw-bg-box-neutral-strong-inverse hover:tw-bg-box-neutral-strong-inverse-hover active:tw-bg-box-neutral-strong-inverse-pressed tw-text-box-neutral-strong tw-outline tw-outline-1 tw-outline-offset-1 tw-p-1.5 tw-outline-line"
29
+ >
30
+ <IconArrowCircleDown16 />
31
+ </span>
32
+ </button>
33
+ }
34
+ />
35
+ );
36
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './DownloadButton';
@@ -0,0 +1,5 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export type DownloadButtonProps = {
4
+ onDownload: () => void;
5
+ };