@frontify/guideline-blocks-settings 0.32.1 → 0.33.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 (100) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/components/Attachments/AttachmentItem.es.js +128 -109
  3. package/dist/components/Attachments/AttachmentItem.es.js.map +1 -1
  4. package/dist/components/Attachments/Attachments.es.js +9 -1
  5. package/dist/components/Attachments/Attachments.es.js.map +1 -1
  6. package/dist/components/Attachments/AttachmentsButtonTrigger.es.js +17 -9
  7. package/dist/components/Attachments/AttachmentsButtonTrigger.es.js.map +1 -1
  8. package/dist/components/BlockItemWrapper/BlockItemWrapper.es.js +38 -44
  9. package/dist/components/BlockItemWrapper/BlockItemWrapper.es.js.map +1 -1
  10. package/dist/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.es.js +33 -0
  11. package/dist/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.es.js.map +1 -0
  12. package/dist/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.es.js +26 -0
  13. package/dist/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.es.js.map +1 -0
  14. package/dist/components/BlockItemWrapper/Toolbar/BaseToolbarButton.es.js +28 -0
  15. package/dist/components/BlockItemWrapper/Toolbar/BaseToolbarButton.es.js.map +1 -0
  16. package/dist/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.es.js +35 -0
  17. package/dist/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.es.js.map +1 -0
  18. package/dist/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.es.js +44 -0
  19. package/dist/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.es.js.map +1 -0
  20. package/dist/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.es.js +25 -0
  21. package/dist/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.es.js.map +1 -0
  22. package/dist/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.es.js +29 -0
  23. package/dist/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.es.js.map +1 -0
  24. package/dist/components/BlockItemWrapper/Toolbar/Toolbar.es.js +11 -112
  25. package/dist/components/BlockItemWrapper/Toolbar/Toolbar.es.js.map +1 -1
  26. package/dist/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.es.js +12 -0
  27. package/dist/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.es.js.map +1 -0
  28. package/dist/components/BlockItemWrapper/Toolbar/ToolbarButtonTooltip.es.js +20 -0
  29. package/dist/components/BlockItemWrapper/Toolbar/ToolbarButtonTooltip.es.js.map +1 -0
  30. package/dist/components/BlockItemWrapper/Toolbar/context/DragPreviewContext.es.js +11 -0
  31. package/dist/components/BlockItemWrapper/Toolbar/context/DragPreviewContext.es.js.map +1 -0
  32. package/dist/components/BlockItemWrapper/Toolbar/context/MultiFlyoutContext.es.js +18 -0
  33. package/dist/components/BlockItemWrapper/Toolbar/context/MultiFlyoutContext.es.js.map +1 -0
  34. package/dist/components/BlockItemWrapper/Toolbar/helpers.es.js +1 -1
  35. package/dist/components/BlockItemWrapper/Toolbar/helpers.es.js.map +1 -1
  36. package/dist/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.es.js +18 -0
  37. package/dist/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.es.js.map +1 -0
  38. package/dist/helpers/mapColorPalettes.es.js +20 -8
  39. package/dist/helpers/mapColorPalettes.es.js.map +1 -1
  40. package/dist/index.cjs.js +3 -3
  41. package/dist/index.cjs.js.map +1 -1
  42. package/dist/index.d.ts +92 -27
  43. package/dist/index.es.js +205 -187
  44. package/dist/index.es.js.map +1 -1
  45. package/dist/index.umd.js +3 -3
  46. package/dist/index.umd.js.map +1 -1
  47. package/dist/styles.css +1 -1
  48. package/package.json +4 -2
  49. package/setupTests.ts +7 -2
  50. package/src/components/Attachments/AttachmentItem.tsx +21 -0
  51. package/src/components/Attachments/Attachments.spec.ct.tsx +20 -1
  52. package/src/components/Attachments/Attachments.tsx +8 -4
  53. package/src/components/Attachments/AttachmentsButtonTrigger.tsx +11 -3
  54. package/src/components/Attachments/types.ts +4 -2
  55. package/src/components/BlockItemWrapper/BlockItemWrapper.spec.ct.tsx +37 -38
  56. package/src/components/BlockItemWrapper/BlockItemWrapper.tsx +44 -48
  57. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.spec.tsx +96 -0
  58. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.tsx +42 -0
  59. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.spec.tsx +44 -0
  60. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.tsx +24 -0
  61. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/index.ts +3 -0
  62. package/src/components/BlockItemWrapper/Toolbar/BaseToolbarButton.spec.tsx +40 -0
  63. package/src/components/BlockItemWrapper/Toolbar/BaseToolbarButton.tsx +37 -0
  64. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.spec.tsx +89 -0
  65. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.tsx +40 -0
  66. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/index.ts +3 -0
  67. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.spec.tsx +140 -0
  68. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.tsx +61 -0
  69. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/index.ts +3 -0
  70. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.spec.tsx +77 -0
  71. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.tsx +30 -0
  72. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.spec.tsx +63 -0
  73. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.tsx +40 -0
  74. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/index.ts +3 -0
  75. package/src/components/BlockItemWrapper/Toolbar/Toolbar.spec.tsx +189 -57
  76. package/src/components/BlockItemWrapper/Toolbar/Toolbar.tsx +29 -126
  77. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.spec.tsx +70 -0
  78. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.tsx +19 -0
  79. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/index.ts +3 -0
  80. package/src/components/BlockItemWrapper/Toolbar/ToolbarButtonTooltip.tsx +25 -0
  81. package/src/components/BlockItemWrapper/Toolbar/context/DragPreviewContext.tsx +15 -0
  82. package/src/components/BlockItemWrapper/Toolbar/context/MultiFlyoutContext.tsx +25 -0
  83. package/src/components/BlockItemWrapper/Toolbar/context/index.ts +4 -0
  84. package/src/components/BlockItemWrapper/Toolbar/helpers.ts +1 -1
  85. package/src/components/BlockItemWrapper/Toolbar/hooks/index.ts +3 -0
  86. package/src/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.spec.tsx +59 -0
  87. package/src/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.ts +24 -0
  88. package/src/components/BlockItemWrapper/Toolbar/index.ts +6 -0
  89. package/src/components/BlockItemWrapper/Toolbar/types.ts +10 -30
  90. package/src/components/BlockItemWrapper/types.ts +1 -2
  91. package/src/helpers/mapColorPalettes.spec.ts +14 -113
  92. package/src/helpers/mapColorPalettes.ts +51 -8
  93. package/src/hooks/useAttachments.spec.tsx +1 -0
  94. package/tsconfig.json +1 -1
  95. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js +0 -27
  96. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js.map +0 -1
  97. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js +0 -12
  98. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js.map +0 -1
  99. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachments.tsx +0 -29
  100. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.tsx +0 -14
@@ -0,0 +1,63 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { fireEvent, render } from '@testing-library/react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { IconAdobeCreativeCloud } from '@frontify/fondue';
6
+ import { ToolbarFlyoutMenu } from './ToolbarFlyoutMenu';
7
+ import { MultiFlyoutContextProvider } from '../context/MultiFlyoutContext';
8
+
9
+ const MENU_ITEM_ID = 'menu-item';
10
+
11
+ const TEST_FLYOUT_ID = 'test';
12
+ /**
13
+ * @vitest-environment happy-dom
14
+ */
15
+
16
+ describe('ToolbarFlyoutMenu', () => {
17
+ it('should display menu items', async () => {
18
+ const setOpenFlyoutIdsStub = vi.fn();
19
+
20
+ const { getAllByTestId } = render(
21
+ <MultiFlyoutContextProvider openFlyoutIds={[TEST_FLYOUT_ID]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
22
+ <ToolbarFlyoutMenu
23
+ items={[
24
+ [{ title: 'item-1', onClick: vi.fn(), icon: <IconAdobeCreativeCloud /> }],
25
+ [{ title: 'item-2', onClick: vi.fn(), icon: <IconAdobeCreativeCloud /> }],
26
+ ]}
27
+ flyoutId={TEST_FLYOUT_ID}
28
+ />
29
+ </MultiFlyoutContextProvider>,
30
+ );
31
+
32
+ expect(getAllByTestId(MENU_ITEM_ID)).toHaveLength(2);
33
+ });
34
+
35
+ it('should close flyout onClick', async () => {
36
+ const setOpenFlyoutIdsStub = vi.fn();
37
+ const onClickStub = vi.fn();
38
+
39
+ const { getByTestId } = render(
40
+ <MultiFlyoutContextProvider openFlyoutIds={[TEST_FLYOUT_ID]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
41
+ <ToolbarFlyoutMenu
42
+ items={[
43
+ [
44
+ {
45
+ title: 'item-1',
46
+ onClick: onClickStub,
47
+ icon: <IconAdobeCreativeCloud />,
48
+ },
49
+ ],
50
+ ]}
51
+ flyoutId={TEST_FLYOUT_ID}
52
+ />
53
+ </MultiFlyoutContextProvider>,
54
+ );
55
+
56
+ expect(getByTestId(MENU_ITEM_ID)).toBeVisible();
57
+
58
+ await fireEvent.pointerUp(getByTestId(MENU_ITEM_ID));
59
+
60
+ expect(onClickStub).toHaveBeenCalledOnce();
61
+ expect(setOpenFlyoutIdsStub).toHaveBeenCalledOnce();
62
+ });
63
+ });
@@ -0,0 +1,40 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { ActionMenu, MenuItemContentSize, MenuItemStyle } from '@frontify/fondue';
4
+ import { useMultiFlyoutState } from '../hooks/useMultiFlyoutState';
5
+
6
+ export type ToolbarFlyoutMenuItem = {
7
+ title: string;
8
+ onClick: () => void;
9
+ icon: JSX.Element;
10
+ style?: MenuItemStyle;
11
+ };
12
+
13
+ export type ToolbarFlyoutMenuProps = {
14
+ items: ToolbarFlyoutMenuItem[][];
15
+ flyoutId: string;
16
+ };
17
+
18
+ export const ToolbarFlyoutMenu = ({ items, flyoutId }: ToolbarFlyoutMenuProps) => {
19
+ const { onOpenChange } = useMultiFlyoutState(flyoutId);
20
+
21
+ return (
22
+ <ActionMenu
23
+ menuBlocks={items.map((block, blockIndex) => ({
24
+ id: blockIndex.toString(),
25
+ menuItems: block.map((item, itemIndex) => ({
26
+ id: blockIndex.toString() + itemIndex.toString(),
27
+ size: MenuItemContentSize.XSmall,
28
+ title: item.title,
29
+ style: item.style,
30
+ onClick: () => {
31
+ onOpenChange(false);
32
+ item.onClick();
33
+ },
34
+ initialValue: true,
35
+ decorator: <div className="tw-mr-2">{item.icon}</div>,
36
+ })),
37
+ }))}
38
+ />
39
+ );
40
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './MenuToolbarButton';
@@ -1,12 +1,19 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
3
  import { getAppBridgeBlockStub } from '@frontify/app-bridge';
4
- import { render } from '@testing-library/react';
4
+ import { IconArrowMove16, IconMoveTo, IconTrashBin } from '@frontify/fondue';
5
+ import { fireEvent, render } from '@testing-library/react';
6
+ import userEvent from '@testing-library/user-event';
5
7
  import { beforeAll, describe, expect, it, vi } from 'vitest';
6
8
 
7
9
  import { AttachmentsProvider } from '../../../hooks/useAttachments';
8
10
 
9
11
  import { Toolbar } from './Toolbar';
12
+ import { MultiFlyoutContextProvider } from './context/MultiFlyoutContext';
13
+ import { DragPreviewContextProvider } from './context/DragPreviewContext';
14
+ import { DEFAULT_ATTACHMENTS_BUTTON_ID } from './AttachmentsToolbarButton';
15
+ import { DEFAULT_MENU_BUTTON_ID } from './MenuToolbarButton';
16
+ import { ToolbarItem } from './types';
10
17
 
11
18
  /**
12
19
  * @vitest-environment happy-dom
@@ -18,6 +25,54 @@ const MENU_FLYOUT_ID = 'menu-item';
18
25
  const MOCK_ASSET_FIELD_ID = 'attachment';
19
26
 
20
27
  describe('Toolbar', () => {
28
+ const stubs = vi.hoisted(() => ({
29
+ setOpenFlyoutIds: vi.fn(),
30
+ onClick: vi.fn(),
31
+ onDrag: vi.fn(),
32
+ onKeyDown: vi.fn(),
33
+ }));
34
+
35
+ const STUB_WITH_NO_ASSETS = getAppBridgeBlockStub({
36
+ blockId: 1,
37
+ blockAssets: { [MOCK_ASSET_FIELD_ID]: [] },
38
+ editorState: true,
39
+ });
40
+
41
+ const FULL_ITEMS: ToolbarItem[] = [
42
+ {
43
+ type: 'dragHandle',
44
+ setActivatorNodeRef: vi.fn(),
45
+ icon: <IconMoveTo />,
46
+ draggableProps: { onDrag: stubs.onDrag, onKeyDown: stubs.onKeyDown },
47
+ },
48
+ {
49
+ type: 'button',
50
+ onClick: stubs.onClick,
51
+ icon: <IconTrashBin />,
52
+ },
53
+ {
54
+ type: 'flyout',
55
+ icon: <IconArrowMove16 />,
56
+ tooltip: 'Move To',
57
+ content: <div>Content</div>,
58
+ flyoutHeader: <div>Fixed Header</div>,
59
+ flyoutFooter: <div>Fixed Footer</div>,
60
+ flyoutId: 'move',
61
+ },
62
+ {
63
+ type: 'menu',
64
+ items: [
65
+ [
66
+ {
67
+ title: 'Replace with upload',
68
+ icon: <div></div>,
69
+ onClick: stubs.onClick,
70
+ },
71
+ ],
72
+ ],
73
+ },
74
+ ];
75
+
21
76
  beforeAll(() => {
22
77
  vi.stubGlobal(
23
78
  'Worker',
@@ -30,27 +85,94 @@ describe('Toolbar', () => {
30
85
  });
31
86
 
32
87
  it('should not throw error if toolbar does not have attachments enabled', () => {
33
- expect(() =>
34
- render(
35
- <Toolbar
36
- items={[]}
37
- flyoutMenu={{ items: [], isOpen: false, onOpenChange: vi.fn() }}
38
- attachments={{ isEnabled: false, isOpen: false, onOpenChange: vi.fn() }}
39
- />,
40
- ),
41
- ).not.toThrowError();
88
+ expect(() => render(<Toolbar items={[]} attachments={{ isEnabled: false }} />)).not.toThrowError();
42
89
  });
43
90
 
44
91
  it('should throw error if toolbar does have attachments enabled without provider', () => {
45
- expect(() =>
46
- render(
47
- <Toolbar
48
- items={[]}
49
- flyoutMenu={{ items: [], isOpen: false, onOpenChange: vi.fn() }}
50
- attachments={{ isEnabled: true, isOpen: false, onOpenChange: vi.fn() }}
51
- />,
52
- ),
53
- ).toThrowError();
92
+ expect(() => render(<Toolbar items={[]} attachments={{ isEnabled: true }} />)).toThrowError();
93
+ });
94
+
95
+ it('should have every item type accessible by mouse', async () => {
96
+ const ToolbarWithAttachments = () => (
97
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={stubs.setOpenFlyoutIds}>
98
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
99
+ <Toolbar items={FULL_ITEMS} attachments={{ isEnabled: true }} />
100
+ </AttachmentsProvider>
101
+ </MultiFlyoutContextProvider>
102
+ );
103
+
104
+ const { getAllByRole } = render(<ToolbarWithAttachments />);
105
+
106
+ const buttons = getAllByRole('button');
107
+ expect(buttons).toHaveLength(5);
108
+
109
+ const [attachmentBtn, dragBtn, btn, flyoutBtn, menuBtn] = buttons;
110
+
111
+ // Click Interactions
112
+
113
+ await fireEvent.click(attachmentBtn);
114
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(1);
115
+
116
+ await fireEvent.drag(dragBtn);
117
+ expect(stubs.onDrag).toHaveBeenCalledTimes(1);
118
+
119
+ await fireEvent.click(btn);
120
+ expect(stubs.onClick).toHaveBeenCalledTimes(1);
121
+
122
+ await fireEvent.click(flyoutBtn);
123
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(2);
124
+
125
+ await fireEvent.click(menuBtn);
126
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(3);
127
+ });
128
+
129
+ it('should have every item type accessible by keyboard', async () => {
130
+ const ToolbarWithAttachments = () => (
131
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={stubs.setOpenFlyoutIds}>
132
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
133
+ <Toolbar items={FULL_ITEMS} attachments={{ isEnabled: true }} />
134
+ </AttachmentsProvider>
135
+ </MultiFlyoutContextProvider>
136
+ );
137
+
138
+ const user = userEvent.setup();
139
+
140
+ const { getAllByRole } = render(<ToolbarWithAttachments />);
141
+
142
+ const buttons = getAllByRole('button');
143
+ expect(buttons).toHaveLength(5);
144
+
145
+ const [attachmentBtn, dragBtn, btn, flyoutBtn, menuBtn] = buttons;
146
+
147
+ await user.keyboard('{Tab}');
148
+
149
+ expect(attachmentBtn).toHaveFocus();
150
+ await user.keyboard('{Enter}');
151
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(1);
152
+
153
+ await user.keyboard('{Tab}');
154
+
155
+ expect(dragBtn).toHaveFocus();
156
+ await user.keyboard('{Enter}');
157
+ expect(stubs.onKeyDown).toHaveBeenCalledTimes(1);
158
+
159
+ await user.keyboard('{Tab}');
160
+
161
+ expect(btn).toHaveFocus();
162
+ await user.keyboard('{Enter}');
163
+ expect(stubs.onClick).toHaveBeenCalledTimes(1);
164
+
165
+ await user.keyboard('{Tab}');
166
+
167
+ expect(flyoutBtn).toHaveFocus();
168
+ await user.keyboard('{Enter}');
169
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(2);
170
+
171
+ await user.keyboard('{Tab}');
172
+
173
+ expect(menuBtn).toHaveFocus();
174
+ await user.keyboard('{Enter}');
175
+ expect(stubs.setOpenFlyoutIds).toHaveBeenCalledTimes(3);
54
176
  });
55
177
 
56
178
  it('should open flyouts if not dragging', async () => {
@@ -61,26 +183,30 @@ describe('Toolbar', () => {
61
183
  });
62
184
 
63
185
  const ToolbarWithAttachments = () => (
64
- <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
65
- <Toolbar
66
- items={[]}
67
- flyoutMenu={{
68
- items: [
69
- [
70
- {
71
- title: 'Replace with upload',
72
- icon: <div></div>,
73
- onClick: vi.fn(),
74
- },
75
- ],
76
- ],
77
- isOpen: true,
78
- onOpenChange: vi.fn(),
79
- }}
80
- attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
81
- isDragging={false}
82
- />
83
- </AttachmentsProvider>
186
+ <MultiFlyoutContextProvider
187
+ openFlyoutIds={[DEFAULT_ATTACHMENTS_BUTTON_ID, DEFAULT_MENU_BUTTON_ID]}
188
+ setOpenFlyoutIds={vi.fn()}
189
+ >
190
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
191
+ <Toolbar
192
+ items={[
193
+ {
194
+ type: 'menu',
195
+ items: [
196
+ [
197
+ {
198
+ title: 'Replace with upload',
199
+ icon: <div></div>,
200
+ onClick: vi.fn(),
201
+ },
202
+ ],
203
+ ],
204
+ },
205
+ ]}
206
+ attachments={{ isEnabled: true }}
207
+ />
208
+ </AttachmentsProvider>
209
+ </MultiFlyoutContextProvider>
84
210
  );
85
211
 
86
212
  const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
@@ -98,26 +224,32 @@ describe('Toolbar', () => {
98
224
  });
99
225
 
100
226
  const ToolbarWithAttachments = () => (
101
- <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
102
- <Toolbar
103
- items={[]}
104
- flyoutMenu={{
105
- items: [
106
- [
227
+ <MultiFlyoutContextProvider
228
+ openFlyoutIds={[DEFAULT_ATTACHMENTS_BUTTON_ID, DEFAULT_MENU_BUTTON_ID]}
229
+ setOpenFlyoutIds={vi.fn()}
230
+ >
231
+ <DragPreviewContextProvider isDragPreview>
232
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
233
+ <Toolbar
234
+ items={[
107
235
  {
108
- title: 'Replace with upload',
109
- icon: <div></div>,
110
- onClick: vi.fn(),
236
+ type: 'menu',
237
+ items: [
238
+ [
239
+ {
240
+ title: 'Replace with upload',
241
+ icon: <div></div>,
242
+ onClick: vi.fn(),
243
+ },
244
+ ],
245
+ ],
111
246
  },
112
- ],
113
- ],
114
- isOpen: true,
115
- onOpenChange: vi.fn(),
116
- }}
117
- attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
118
- isDragging
119
- />
120
- </AttachmentsProvider>
247
+ ]}
248
+ attachments={{ isEnabled: true }}
249
+ />
250
+ </AttachmentsProvider>
251
+ </DragPreviewContextProvider>
252
+ </MultiFlyoutContextProvider>
121
253
  );
122
254
 
123
255
  const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
@@ -1,133 +1,36 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
- import {
4
- ActionMenu,
5
- Flyout,
6
- IconDotsHorizontal16,
7
- MenuItemContentSize,
8
- LegacyTooltip as Tooltip,
9
- TooltipPosition,
10
- } from '@frontify/fondue';
11
-
12
- import { DEFAULT_DRAGGING_TOOLTIP, DEFAULT_DRAG_TOOLTIP } from '../constants';
13
-
14
3
  import { ToolbarSegment } from './ToolbarSegment';
15
- import { ToolbarAttachments } from './ToolbarAttachments';
16
- import { getToolbarButtonClassNames } from './helpers';
4
+ import { AttachmentsToolbarButton } from './AttachmentsToolbarButton';
17
5
  import { type ToolbarProps } from './types';
6
+ import { ToolbarButton } from './ToolbarButton';
7
+ import { DragHandleToolbarButton } from './DragHandleToolbarButton';
8
+ import { FlyoutToolbarButton } from './FlyoutToolbarButton';
9
+ import { MenuToolbarButton } from './MenuToolbarButton';
18
10
 
19
- export const Toolbar = ({ items, flyoutMenu, attachments, isDragging }: ToolbarProps) => {
20
- return (
21
- <div
22
- data-test-id="block-item-wrapper-toolbar"
23
- className="tw-rounded-md tw-bg-base tw-border tw-border-line-strong tw-divide-x tw-divide-line-strong tw-shadow-lg tw-flex tw-flex-none tw-items-center tw-isolate"
24
- >
25
- {attachments.isEnabled && (
26
- <ToolbarSegment>
27
- <ToolbarAttachments
28
- isOpen={attachments.isOpen && !isDragging}
29
- onOpenChange={attachments.onOpenChange}
30
- />
31
- </ToolbarSegment>
32
- )}
11
+ export const Toolbar = ({ items, attachments }: ToolbarProps) => (
12
+ <div
13
+ data-test-id="block-item-wrapper-toolbar"
14
+ className="tw-rounded-md tw-bg-base tw-border tw-border-line-strong tw-divide-x tw-divide-line-strong tw-shadow-lg tw-flex tw-flex-none tw-items-center tw-isolate"
15
+ >
16
+ {attachments.isEnabled && (
33
17
  <ToolbarSegment>
34
- {items.map((item, i) =>
35
- 'draggableProps' in item ? (
36
- <Tooltip
37
- key={i}
38
- withArrow
39
- hoverDelay={0}
40
- enterDelay={300}
41
- open={isDragging}
42
- position={TooltipPosition.Top}
43
- content={
44
- <div>
45
- {isDragging ? DEFAULT_DRAGGING_TOOLTIP : item.tooltip ?? DEFAULT_DRAG_TOOLTIP}
46
- </div>
47
- }
48
- triggerElement={
49
- <button
50
- ref={item.setActivatorNodeRef}
51
- data-test-id="block-item-wrapper-toolbar-btn"
52
- {...item.draggableProps}
53
- className={getToolbarButtonClassNames('grab', isDragging)}
54
- >
55
- {item.icon}
56
- </button>
57
- }
58
- />
59
- ) : (
60
- <Tooltip
61
- key={i}
62
- withArrow
63
- enterDelay={300}
64
- hoverDelay={0}
65
- disabled={isDragging}
66
- position={TooltipPosition.Top}
67
- content={<div>{item.tooltip ?? ''}</div>}
68
- triggerElement={
69
- <button
70
- data-test-id="block-item-wrapper-toolbar-btn"
71
- onClick={item.onClick}
72
- className={getToolbarButtonClassNames('pointer')}
73
- >
74
- {item.icon}
75
- </button>
76
- }
77
- />
78
- ),
79
- )}
80
- {flyoutMenu.items.length > 0 && (
81
- <Tooltip
82
- withArrow
83
- hoverDelay={0}
84
- enterDelay={300}
85
- disabled={isDragging || flyoutMenu.isOpen}
86
- position={TooltipPosition.Top}
87
- content={<div>Options</div>}
88
- triggerElement={
89
- <div className="tw-flex tw-flex-shrink-0 tw-flex-1 tw-h-6 tw-relative">
90
- <Flyout
91
- isOpen={flyoutMenu.isOpen && !isDragging}
92
- legacyFooter={false}
93
- fitContent
94
- hug={false}
95
- onOpenChange={flyoutMenu.onOpenChange}
96
- trigger={
97
- <div
98
- data-test-id="block-item-wrapper-toolbar-flyout"
99
- className={getToolbarButtonClassNames(
100
- 'pointer',
101
- flyoutMenu.isOpen && !isDragging,
102
- )}
103
- >
104
- <IconDotsHorizontal16 />
105
- </div>
106
- }
107
- >
108
- <ActionMenu
109
- menuBlocks={flyoutMenu.items.map((block, blockIndex) => ({
110
- id: blockIndex.toString(),
111
- menuItems: block.map((item, itemIndex) => ({
112
- id: blockIndex.toString() + itemIndex.toString(),
113
- size: MenuItemContentSize.XSmall,
114
- title: item.title,
115
- style: item.style,
116
- onClick: () => {
117
- flyoutMenu.onOpenChange(false);
118
- item.onClick();
119
- },
120
- initialValue: true,
121
- decorator: <div className="tw-mr-2">{item.icon}</div>,
122
- })),
123
- }))}
124
- />
125
- </Flyout>
126
- </div>
127
- }
128
- />
129
- )}
18
+ <AttachmentsToolbarButton />
130
19
  </ToolbarSegment>
131
- </div>
132
- );
133
- };
20
+ )}
21
+ <ToolbarSegment>
22
+ {items.map((item) => {
23
+ if (item.type === 'dragHandle') {
24
+ return <DragHandleToolbarButton key={item.tooltip + item.type} {...item} />;
25
+ }
26
+ if (item.type === 'menu') {
27
+ return <MenuToolbarButton key={item.tooltip + item.type} {...item} />;
28
+ }
29
+ if (item.type === 'flyout') {
30
+ return <FlyoutToolbarButton key={item.tooltip + item.type} {...item} />;
31
+ }
32
+ return <ToolbarButton key={item.tooltip + item.type} {...item} />;
33
+ })}
34
+ </ToolbarSegment>
35
+ </div>
36
+ );
@@ -0,0 +1,70 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { fireEvent, render, waitFor } from '@testing-library/react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { ToolbarButton } from './ToolbarButton';
6
+ import { IconAdobeCreativeCloud } from '@frontify/fondue';
7
+ import { DragPreviewContextProvider } from '../context/DragPreviewContext';
8
+
9
+ const TOOLBAR_BUTTON_ID = 'block-item-wrapper-toolbar-btn';
10
+ const TOOLTIP_ID = 'toolbar-button-tooltip';
11
+
12
+ const TOOLTIP_CONTENT = 'content';
13
+
14
+ /**
15
+ * @vitest-environment happy-dom
16
+ */
17
+
18
+ describe('ToolbarButton', () => {
19
+ it('should disable tooltip when item is in drag preview context', async () => {
20
+ const { getByTestId } = render(
21
+ <DragPreviewContextProvider isDragPreview>
22
+ <ToolbarButton onClick={vi.fn()} tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} />
23
+ </DragPreviewContextProvider>,
24
+ );
25
+
26
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
27
+
28
+ getByTestId(TOOLTIP_ID).focus();
29
+
30
+ await waitFor(() => {
31
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
32
+ });
33
+ });
34
+
35
+ it('should show tooltip when item is focused', async () => {
36
+ const { getByTestId } = render(
37
+ <ToolbarButton onClick={vi.fn()} tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} />,
38
+ );
39
+
40
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
41
+ expect(getByTestId(TOOLTIP_ID)).toHaveTextContent(TOOLTIP_CONTENT);
42
+
43
+ getByTestId(TOOLTIP_ID).focus();
44
+
45
+ await waitFor(() => {
46
+ expect(getByTestId(TOOLTIP_ID)).not.toHaveClass('tw-opacity-0');
47
+ });
48
+ });
49
+
50
+ it('should trigger onClick', async () => {
51
+ const onClickStub = vi.fn();
52
+ const { getByTestId } = render(
53
+ <ToolbarButton onClick={onClickStub} tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} />,
54
+ );
55
+
56
+ await fireEvent.click(getByTestId(TOOLBAR_BUTTON_ID));
57
+
58
+ expect(onClickStub).toHaveBeenCalledOnce();
59
+ });
60
+
61
+ it('should display icon', async () => {
62
+ const { getByTestId } = render(
63
+ <ToolbarButton onClick={vi.fn()} tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} />,
64
+ );
65
+
66
+ const icons = [...getByTestId(TOOLBAR_BUTTON_ID).querySelectorAll('svg')];
67
+ expect(icons).toHaveLength(1);
68
+ expect(icons[0].outerHTML).toMatch('IconAdobeCreativeCloud');
69
+ });
70
+ });
@@ -0,0 +1,19 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { useDragPreviewContext } from '../context/DragPreviewContext';
4
+ import { BaseToolbarButton } from '../BaseToolbarButton';
5
+ import { ToolbarButtonTooltip } from '../ToolbarButtonTooltip';
6
+
7
+ export type ToolbarButtonProps = { icon: JSX.Element; tooltip?: string; onClick: () => void };
8
+
9
+ export const ToolbarButton = ({ tooltip, icon, onClick }: ToolbarButtonProps) => {
10
+ const isDragPreview = useDragPreviewContext();
11
+
12
+ return (
13
+ <ToolbarButtonTooltip disabled={isDragPreview} content={tooltip ?? ''}>
14
+ <BaseToolbarButton data-test-id="block-item-wrapper-toolbar-btn" onClick={onClick}>
15
+ {icon}
16
+ </BaseToolbarButton>
17
+ </ToolbarButtonTooltip>
18
+ );
19
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './ToolbarButton';