@frontify/guideline-blocks-settings 0.32.0 → 0.32.2

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 (96) hide show
  1. package/CHANGELOG.md +46 -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 +37 -39
  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 +13 -110
  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 +57 -23
  43. package/dist/index.es.js +195 -185
  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 +3 -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.tsx +47 -47
  56. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.spec.tsx +96 -0
  57. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButton.tsx +42 -0
  58. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.spec.tsx +44 -0
  59. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/AttachmentsToolbarButtonTrigger.tsx +24 -0
  60. package/src/components/BlockItemWrapper/Toolbar/AttachmentsToolbarButton/index.ts +3 -0
  61. package/src/components/BlockItemWrapper/Toolbar/BaseToolbarButton.spec.tsx +40 -0
  62. package/src/components/BlockItemWrapper/Toolbar/BaseToolbarButton.tsx +37 -0
  63. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.spec.tsx +89 -0
  64. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/DragHandleToolbarButton.tsx +39 -0
  65. package/src/components/BlockItemWrapper/Toolbar/DragHandleToolbarButton/index.ts +3 -0
  66. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.spec.tsx +140 -0
  67. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/FlyoutToolbarButton.tsx +61 -0
  68. package/src/components/BlockItemWrapper/Toolbar/FlyoutToolbarButton/index.ts +3 -0
  69. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.spec.tsx +77 -0
  70. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/MenuToolbarButton.tsx +30 -0
  71. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.spec.tsx +63 -0
  72. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/ToolbarFlyoutMenu.tsx +40 -0
  73. package/src/components/BlockItemWrapper/Toolbar/MenuToolbarButton/index.ts +3 -0
  74. package/src/components/BlockItemWrapper/Toolbar/Toolbar.spec.tsx +50 -53
  75. package/src/components/BlockItemWrapper/Toolbar/Toolbar.tsx +24 -126
  76. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.spec.tsx +70 -0
  77. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/ToolbarButton.tsx +19 -0
  78. package/src/components/BlockItemWrapper/Toolbar/ToolbarButton/index.ts +3 -0
  79. package/src/components/BlockItemWrapper/Toolbar/ToolbarButtonTooltip.tsx +25 -0
  80. package/src/components/BlockItemWrapper/Toolbar/context/DragPreviewContext.tsx +15 -0
  81. package/src/components/BlockItemWrapper/Toolbar/context/MultiFlyoutContext.tsx +25 -0
  82. package/src/components/BlockItemWrapper/Toolbar/helpers.ts +1 -1
  83. package/src/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.spec.tsx +59 -0
  84. package/src/components/BlockItemWrapper/Toolbar/hooks/useMultiFlyoutState.ts +24 -0
  85. package/src/components/BlockItemWrapper/Toolbar/index.ts +4 -0
  86. package/src/components/BlockItemWrapper/Toolbar/types.ts +8 -27
  87. package/src/helpers/mapColorPalettes.spec.ts +14 -113
  88. package/src/helpers/mapColorPalettes.ts +51 -8
  89. package/src/hooks/useAttachments.spec.tsx +1 -0
  90. package/tsconfig.json +1 -1
  91. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js +0 -27
  92. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js.map +0 -1
  93. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js +0 -12
  94. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js.map +0 -1
  95. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachments.tsx +0 -29
  96. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.tsx +0 -14
@@ -7,6 +7,9 @@ import { beforeAll, describe, expect, it, vi } from 'vitest';
7
7
  import { AttachmentsProvider } from '../../../hooks/useAttachments';
8
8
 
9
9
  import { Toolbar } from './Toolbar';
10
+ import { MultiFlyoutContextProvider } from './context/MultiFlyoutContext';
11
+ import { DEFAULT_ATTACHMENTS_BUTTON_ID, DEFAULT_MENU_BUTTON_ID } from '.';
12
+ import { DragPreviewContextProvider } from './context/DragPreviewContext';
10
13
 
11
14
  /**
12
15
  * @vitest-environment happy-dom
@@ -31,25 +34,13 @@ describe('Toolbar', () => {
31
34
 
32
35
  it('should not throw error if toolbar does not have attachments enabled', () => {
33
36
  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
- ),
37
+ render(<Toolbar items={[]} flyoutMenu={{ items: [] }} attachments={{ isEnabled: false }} />),
41
38
  ).not.toThrowError();
42
39
  });
43
40
 
44
41
  it('should throw error if toolbar does have attachments enabled without provider', () => {
45
42
  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
- ),
43
+ render(<Toolbar items={[]} flyoutMenu={{ items: [] }} attachments={{ isEnabled: true }} />),
53
44
  ).toThrowError();
54
45
  });
55
46
 
@@ -61,26 +52,28 @@ describe('Toolbar', () => {
61
52
  });
62
53
 
63
54
  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
- },
55
+ <MultiFlyoutContextProvider
56
+ openFlyoutIds={[DEFAULT_ATTACHMENTS_BUTTON_ID, DEFAULT_MENU_BUTTON_ID]}
57
+ setOpenFlyoutIds={vi.fn()}
58
+ >
59
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
60
+ <Toolbar
61
+ items={[]}
62
+ flyoutMenu={{
63
+ items: [
64
+ [
65
+ {
66
+ title: 'Replace with upload',
67
+ icon: <div></div>,
68
+ onClick: vi.fn(),
69
+ },
70
+ ],
75
71
  ],
76
- ],
77
- isOpen: true,
78
- onOpenChange: vi.fn(),
79
- }}
80
- attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
81
- isDragging={false}
82
- />
83
- </AttachmentsProvider>
72
+ }}
73
+ attachments={{ isEnabled: true }}
74
+ />
75
+ </AttachmentsProvider>
76
+ </MultiFlyoutContextProvider>
84
77
  );
85
78
 
86
79
  const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
@@ -98,26 +91,30 @@ describe('Toolbar', () => {
98
91
  });
99
92
 
100
93
  const ToolbarWithAttachments = () => (
101
- <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
102
- <Toolbar
103
- items={[]}
104
- flyoutMenu={{
105
- items: [
106
- [
107
- {
108
- title: 'Replace with upload',
109
- icon: <div></div>,
110
- onClick: vi.fn(),
111
- },
112
- ],
113
- ],
114
- isOpen: true,
115
- onOpenChange: vi.fn(),
116
- }}
117
- attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
118
- isDragging
119
- />
120
- </AttachmentsProvider>
94
+ <MultiFlyoutContextProvider
95
+ openFlyoutIds={[DEFAULT_ATTACHMENTS_BUTTON_ID, DEFAULT_MENU_BUTTON_ID]}
96
+ setOpenFlyoutIds={vi.fn()}
97
+ >
98
+ <DragPreviewContextProvider isDragPreview>
99
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
100
+ <Toolbar
101
+ items={[]}
102
+ flyoutMenu={{
103
+ items: [
104
+ [
105
+ {
106
+ title: 'Replace with upload',
107
+ icon: <div></div>,
108
+ onClick: vi.fn(),
109
+ },
110
+ ],
111
+ ],
112
+ }}
113
+ attachments={{ isEnabled: true }}
114
+ />
115
+ </AttachmentsProvider>
116
+ </DragPreviewContextProvider>
117
+ </MultiFlyoutContextProvider>
121
118
  );
122
119
 
123
120
  const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
@@ -1,133 +1,31 @@
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 { MenuToolbarButton } from './MenuToolbarButton';
18
9
 
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
- )}
10
+ export const Toolbar = ({ items, flyoutMenu, attachments }: ToolbarProps) => (
11
+ <div
12
+ data-test-id="block-item-wrapper-toolbar"
13
+ 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"
14
+ >
15
+ {attachments.isEnabled && (
33
16
  <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
- )}
17
+ <AttachmentsToolbarButton />
130
18
  </ToolbarSegment>
131
- </div>
132
- );
133
- };
19
+ )}
20
+ <ToolbarSegment>
21
+ {items.map((item, i) =>
22
+ 'draggableProps' in item ? (
23
+ <DragHandleToolbarButton key={i} {...item} />
24
+ ) : (
25
+ <ToolbarButton key={i} {...item} />
26
+ ),
27
+ )}
28
+ {flyoutMenu.items.length > 0 && <MenuToolbarButton {...flyoutMenu} />}
29
+ </ToolbarSegment>
30
+ </div>
31
+ );
@@ -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';
@@ -0,0 +1,25 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { LegacyTooltip as Tooltip, TooltipPosition } from '@frontify/fondue';
4
+ import { ReactElement, ReactNode } from 'react';
5
+
6
+ type ToolbarButtonTooltipProps = {
7
+ content: ReactNode;
8
+ children: ReactElement;
9
+ open?: boolean;
10
+ disabled?: boolean;
11
+ };
12
+
13
+ export const ToolbarButtonTooltip = ({ open, content, children, disabled }: ToolbarButtonTooltipProps) => (
14
+ <Tooltip
15
+ withArrow
16
+ hoverDelay={0}
17
+ enterDelay={300}
18
+ open={open}
19
+ disabled={disabled}
20
+ position={TooltipPosition.Top}
21
+ content={<div>{content}</div>}
22
+ triggerElement={children}
23
+ data-test-id="toolbar-button-tooltip"
24
+ />
25
+ );
@@ -0,0 +1,15 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { type ReactNode, createContext, useContext } from 'react';
4
+
5
+ const DragPreviewContext = createContext(false);
6
+
7
+ export const DragPreviewContextProvider = ({
8
+ children,
9
+ isDragPreview,
10
+ }: {
11
+ children: ReactNode;
12
+ isDragPreview: boolean;
13
+ }) => <DragPreviewContext.Provider value={isDragPreview}>{children}</DragPreviewContext.Provider>;
14
+
15
+ export const useDragPreviewContext = () => useContext(DragPreviewContext);
@@ -0,0 +1,25 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { Dispatch, ReactNode, SetStateAction, createContext, useContext, useMemo } from 'react';
4
+
5
+ export type MultiFlyoutContextType = {
6
+ openFlyoutIds: string[];
7
+ setOpenFlyoutIds: Dispatch<SetStateAction<string[]>>;
8
+ };
9
+
10
+ const MultiFlyoutContext = createContext<MultiFlyoutContextType>({
11
+ openFlyoutIds: [],
12
+ setOpenFlyoutIds: () => console.error('No MultiFlyoutContext Provider found'),
13
+ });
14
+
15
+ export const MultiFlyoutContextProvider = ({
16
+ children,
17
+ openFlyoutIds,
18
+ setOpenFlyoutIds,
19
+ }: { children: ReactNode } & MultiFlyoutContextType) => {
20
+ const memoizedContext = useMemo(() => ({ openFlyoutIds, setOpenFlyoutIds }), [openFlyoutIds, setOpenFlyoutIds]);
21
+
22
+ return <MultiFlyoutContext.Provider value={memoizedContext}>{children}</MultiFlyoutContext.Provider>;
23
+ };
24
+
25
+ export const useMultiFlyoutContext = () => useContext(MultiFlyoutContext);
@@ -25,7 +25,7 @@ export const getToolbarButtonClassNames = (cursor: 'grab' | 'pointer', forceActi
25
25
  classNames.push(
26
26
  'hover:tw-bg-box-neutral-hover active:tw-bg-box-neutral-pressed',
27
27
  'tw-text-text-weak hover:tw-text-box-neutral-inverse-hover active:tw-text-box-neutral-inverse-pressed',
28
- cursor === 'grab' ? 'tw-cursor-grab active:tw-cursor-grabbing' : 'tw-cursor-pointer',
28
+ cursor === 'grab' ? '!tw-cursor-grab active:tw-cursor-grabbing' : 'tw-cursor-pointer',
29
29
  );
30
30
  }
31
31
 
@@ -0,0 +1,59 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { renderHook } from '@testing-library/react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { useMultiFlyoutState } from './useMultiFlyoutState';
6
+ import { MultiFlyoutContextProvider, MultiFlyoutContextType } from '../context/MultiFlyoutContext';
7
+
8
+ const FLYOUT_ID = 'flyout';
9
+
10
+ const renderMultiFlyoutState = ({ openFlyoutIds, setOpenFlyoutIds }: MultiFlyoutContextType) =>
11
+ renderHook(() => useMultiFlyoutState(FLYOUT_ID), {
12
+ wrapper: ({ children }) => (
13
+ <MultiFlyoutContextProvider openFlyoutIds={openFlyoutIds} setOpenFlyoutIds={setOpenFlyoutIds}>
14
+ {children}
15
+ </MultiFlyoutContextProvider>
16
+ ),
17
+ });
18
+
19
+ describe('useMultiFlyoutState', () => {
20
+ it('should be open when flyout id in context array', () => {
21
+ const { result } = renderMultiFlyoutState({ openFlyoutIds: [FLYOUT_ID], setOpenFlyoutIds: vi.fn() });
22
+ expect(result.current.isOpen).toEqual(true);
23
+ });
24
+
25
+ it('should be closed when flyout id is not in context array', () => {
26
+ const { result } = renderMultiFlyoutState({ openFlyoutIds: [], setOpenFlyoutIds: vi.fn() });
27
+ expect(result.current.isOpen).toEqual(false);
28
+ });
29
+
30
+ it('should add id to array when opening', () => {
31
+ const setOpenFlyoutIdsStub = vi.fn();
32
+ const { result } = renderMultiFlyoutState({ openFlyoutIds: [], setOpenFlyoutIds: setOpenFlyoutIdsStub });
33
+
34
+ result.current.onOpenChange(true);
35
+ const dispatchedResult = setOpenFlyoutIdsStub.mock.lastCall[0]([]);
36
+
37
+ expect(dispatchedResult).toEqual([FLYOUT_ID]);
38
+ });
39
+
40
+ it('should remove id from array when closing', () => {
41
+ const setOpenFlyoutIdsStub = vi.fn();
42
+ const { result } = renderMultiFlyoutState({ openFlyoutIds: [], setOpenFlyoutIds: setOpenFlyoutIdsStub });
43
+
44
+ result.current.onOpenChange(false);
45
+ const dispatchedResult = setOpenFlyoutIdsStub.mock.lastCall[0]([FLYOUT_ID]);
46
+
47
+ expect(dispatchedResult).toEqual([]);
48
+ });
49
+
50
+ it('should remove duplicates from array', () => {
51
+ const setOpenFlyoutIdsStub = vi.fn();
52
+ const { result } = renderMultiFlyoutState({ openFlyoutIds: [], setOpenFlyoutIds: setOpenFlyoutIdsStub });
53
+
54
+ result.current.onOpenChange(true);
55
+ const dispatchedResult = setOpenFlyoutIdsStub.mock.lastCall[0]([FLYOUT_ID, FLYOUT_ID, FLYOUT_ID]);
56
+
57
+ expect(dispatchedResult).toEqual([FLYOUT_ID]);
58
+ });
59
+ });
@@ -0,0 +1,24 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { useCallback } from 'react';
4
+ import { useMultiFlyoutContext } from '../context/MultiFlyoutContext';
5
+
6
+ export const useMultiFlyoutState = (flyoutId: string) => {
7
+ const { openFlyoutIds, setOpenFlyoutIds } = useMultiFlyoutContext();
8
+
9
+ const onOpenChange = useCallback(
10
+ (isFlyoutOpen: boolean) => {
11
+ setOpenFlyoutIds((currentIds) => {
12
+ const filteredIds = currentIds.filter((id) => id !== flyoutId);
13
+ if (!isFlyoutOpen) {
14
+ return filteredIds;
15
+ } else {
16
+ return [...filteredIds, flyoutId];
17
+ }
18
+ });
19
+ },
20
+ [flyoutId, setOpenFlyoutIds],
21
+ );
22
+
23
+ return { isOpen: openFlyoutIds.includes(flyoutId), onOpenChange };
24
+ };
@@ -1,4 +1,8 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
3
  export * from './Toolbar';
4
+ export * from './AttachmentsToolbarButton';
5
+ export * from './DragHandleToolbarButton';
6
+ export * from './FlyoutToolbarButton';
7
+ export * from './MenuToolbarButton';
4
8
  export * from './types';
@@ -1,38 +1,19 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
- import { type MenuItemStyle } from '@frontify/fondue';
3
+ import { type ToolbarButtonProps } from './ToolbarButton';
4
+ import { type ToolbarFlyoutMenuItem } from './MenuToolbarButton/ToolbarFlyoutMenu';
5
+ import { DragHandleToolbarButtonProps } from '.';
4
6
 
5
7
  export type ToolbarProps = {
6
8
  items: ToolbarItem[];
7
- flyoutMenu: ToolbarFlyoutState & { items: FlyoutToolbarItem[][] };
8
- attachments: ToolbarFlyoutState & { isEnabled: boolean };
9
- isDragging?: boolean;
9
+ flyoutMenu: { items: ToolbarFlyoutMenuItem[][] };
10
+ attachments: { isEnabled: boolean };
10
11
  };
11
12
 
12
- export type ToolbarFlyoutState = {
13
- isOpen: boolean;
14
- onOpenChange: (isOpen: boolean) => void;
15
- };
13
+ export type DraghandleToolbarItem = DragHandleToolbarButtonProps;
16
14
 
17
- type BaseToolbarItem = {
18
- icon: JSX.Element;
19
- tooltip?: string;
20
- };
15
+ export type ButtonToolbarItem = ToolbarButtonProps;
21
16
 
22
- type DraghandleToolbarItem = BaseToolbarItem & {
23
- draggableProps: Record<string, unknown>;
24
- setActivatorNodeRef?: (node: HTMLElement | null) => void;
25
- };
26
-
27
- type ButtonToolbarItem = BaseToolbarItem & {
28
- onClick: () => void;
29
- };
17
+ export type FlyoutToolbarItem = ToolbarFlyoutMenuItem;
30
18
 
31
19
  export type ToolbarItem = DraghandleToolbarItem | ButtonToolbarItem;
32
-
33
- export type FlyoutToolbarItem = {
34
- title: string;
35
- onClick: () => void;
36
- icon: JSX.Element;
37
- style?: MenuItemStyle;
38
- };