@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
@@ -0,0 +1,40 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { cleanup, fireEvent, render } from '@testing-library/react';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { BaseToolbarButton } from './BaseToolbarButton';
6
+
7
+ const BUTTON_ID = 'base-toolbar-button';
8
+
9
+ describe('BaseToolbarButton', () => {
10
+ afterEach(() => {
11
+ cleanup();
12
+ });
13
+ it('should call onClick', async () => {
14
+ const onClickStub = vi.fn();
15
+ const { getByTestId } = render(<BaseToolbarButton onClick={onClickStub}>Button</BaseToolbarButton>);
16
+
17
+ await fireEvent.click(getByTestId(BUTTON_ID));
18
+
19
+ expect(onClickStub).toHaveBeenCalledOnce();
20
+ });
21
+
22
+ it('should apply cursor styles', async () => {
23
+ const { getByTestId } = render(<BaseToolbarButton cursor="grab">Button</BaseToolbarButton>);
24
+
25
+ expect(getByTestId(BUTTON_ID)).toHaveClass('!tw-cursor-grab');
26
+ });
27
+
28
+ it('should apply active styles', async () => {
29
+ const { getByTestId } = render(<BaseToolbarButton forceActiveStyle>Button</BaseToolbarButton>);
30
+
31
+ expect(getByTestId(BUTTON_ID)).toHaveClass('tw-text-box-neutral-inverse-pressed');
32
+ });
33
+
34
+ it('should forward other attributes to button', async () => {
35
+ const BUTTON_TYPE = 'base';
36
+ const { getByTestId } = render(<BaseToolbarButton data-button-type={BUTTON_TYPE}>Button</BaseToolbarButton>);
37
+
38
+ expect(getByTestId(BUTTON_ID).dataset.buttonType).toEqual(BUTTON_TYPE);
39
+ });
40
+ });
@@ -0,0 +1,37 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { type HTMLAttributes, type ReactNode, forwardRef } from 'react';
4
+ import { getToolbarButtonClassNames } from './helpers';
5
+
6
+ type BaseToolbarButtonProps = {
7
+ children: ReactNode;
8
+ forceActiveStyle?: boolean;
9
+ cursor?: 'pointer' | 'grab';
10
+ 'data-test-id'?: string;
11
+ } & HTMLAttributes<HTMLButtonElement>;
12
+
13
+ export const BaseToolbarButton = forwardRef<HTMLButtonElement, BaseToolbarButtonProps>(
14
+ (
15
+ {
16
+ onClick,
17
+ children,
18
+ forceActiveStyle,
19
+ cursor = 'pointer',
20
+ 'data-test-id': dataTestId = 'base-toolbar-button',
21
+ ...props
22
+ },
23
+ ref,
24
+ ) => (
25
+ <button
26
+ onClick={onClick}
27
+ className={getToolbarButtonClassNames(cursor, forceActiveStyle)}
28
+ data-test-id={dataTestId}
29
+ {...props}
30
+ ref={ref}
31
+ >
32
+ {children}
33
+ </button>
34
+ ),
35
+ );
36
+
37
+ BaseToolbarButton.displayName = 'BaseToolbarButton';
@@ -0,0 +1,89 @@
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 { DragHandleToolbarButton } from './DragHandleToolbarButton';
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('DragHandleToolbarButton', () => {
19
+ it('should show tooltip and activeStyles when item is in drag preview context', async () => {
20
+ const { getByTestId } = render(
21
+ <DragPreviewContextProvider isDragPreview>
22
+ <DragHandleToolbarButton
23
+ tooltip={TOOLTIP_CONTENT}
24
+ icon={<IconAdobeCreativeCloud />}
25
+ draggableProps={{}}
26
+ />
27
+ </DragPreviewContextProvider>,
28
+ );
29
+
30
+ expect(getByTestId(TOOLTIP_ID)).not.toHaveClass('tw-opacity-0');
31
+ expect(getByTestId(TOOLBAR_BUTTON_ID)).toHaveClass('tw-cursor-grabbing');
32
+ });
33
+
34
+ it('should show tooltip when item is focused', async () => {
35
+ const { getByTestId } = render(
36
+ <DragHandleToolbarButton tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} draggableProps={{}} />,
37
+ );
38
+
39
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
40
+ expect(getByTestId(TOOLTIP_ID)).toHaveTextContent(TOOLTIP_CONTENT);
41
+
42
+ getByTestId(TOOLBAR_BUTTON_ID).focus();
43
+
44
+ await waitFor(() => {
45
+ expect(getByTestId(TOOLTIP_ID)).not.toHaveClass('tw-opacity-0');
46
+ });
47
+ });
48
+
49
+ it('should forward draggableProps', async () => {
50
+ const onDragStub = vi.fn();
51
+
52
+ const { getByTestId } = render(
53
+ <DragHandleToolbarButton
54
+ tooltip={TOOLTIP_CONTENT}
55
+ icon={<IconAdobeCreativeCloud />}
56
+ draggableProps={{ onDrag: onDragStub }}
57
+ />,
58
+ );
59
+
60
+ await fireEvent.drag(getByTestId(TOOLBAR_BUTTON_ID));
61
+
62
+ expect(onDragStub).toHaveBeenCalledOnce();
63
+ });
64
+
65
+ it('should forward setActivatorNodeRef', async () => {
66
+ const setActivatorNodeRefStub = vi.fn();
67
+
68
+ render(
69
+ <DragHandleToolbarButton
70
+ tooltip={TOOLTIP_CONTENT}
71
+ icon={<IconAdobeCreativeCloud />}
72
+ draggableProps={{}}
73
+ setActivatorNodeRef={setActivatorNodeRefStub}
74
+ />,
75
+ );
76
+
77
+ expect(setActivatorNodeRefStub).toHaveBeenCalledOnce();
78
+ });
79
+
80
+ it('should display icon', async () => {
81
+ const { getByTestId } = render(
82
+ <DragHandleToolbarButton tooltip={TOOLTIP_CONTENT} icon={<IconAdobeCreativeCloud />} draggableProps={{}} />,
83
+ );
84
+
85
+ const icons = [...getByTestId(TOOLBAR_BUTTON_ID).querySelectorAll('svg')];
86
+ expect(icons).toHaveLength(1);
87
+ expect(icons[0].outerHTML).toMatch('IconAdobeCreativeCloud');
88
+ });
89
+ });
@@ -0,0 +1,39 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { DEFAULT_DRAGGING_TOOLTIP, DEFAULT_DRAG_TOOLTIP } from '../../constants';
4
+ import { useDragPreviewContext } from '../context/DragPreviewContext';
5
+ import { BaseToolbarButton } from '../BaseToolbarButton';
6
+ import { ToolbarButtonTooltip } from '../ToolbarButtonTooltip';
7
+
8
+ export type DragHandleToolbarButtonProps = {
9
+ icon: JSX.Element;
10
+ tooltip?: string;
11
+ draggableProps: Record<string, unknown>;
12
+ setActivatorNodeRef?: (node: HTMLElement | null) => void;
13
+ };
14
+
15
+ export const DragHandleToolbarButton = ({
16
+ tooltip,
17
+ icon,
18
+ setActivatorNodeRef,
19
+ draggableProps,
20
+ }: DragHandleToolbarButtonProps) => {
21
+ const isDragPreview = useDragPreviewContext();
22
+
23
+ return (
24
+ <ToolbarButtonTooltip
25
+ open={isDragPreview}
26
+ content={<div>{isDragPreview ? DEFAULT_DRAGGING_TOOLTIP : tooltip ?? DEFAULT_DRAG_TOOLTIP}</div>}
27
+ >
28
+ <BaseToolbarButton
29
+ ref={setActivatorNodeRef}
30
+ data-test-id="block-item-wrapper-toolbar-btn"
31
+ forceActiveStyle={isDragPreview}
32
+ cursor="grab"
33
+ {...draggableProps}
34
+ >
35
+ {icon}
36
+ </BaseToolbarButton>
37
+ </ToolbarButtonTooltip>
38
+ );
39
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './DragHandleToolbarButton';
@@ -0,0 +1,140 @@
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 { FlyoutToolbarButton } from './FlyoutToolbarButton';
6
+ import { MultiFlyoutContextProvider } from '../context/MultiFlyoutContext';
7
+ import { IconAdobeCreativeCloud } from '@frontify/fondue';
8
+ import { DragPreviewContextProvider } from '../context/DragPreviewContext';
9
+
10
+ const BUTTON_ID = 'block-item-wrapper-toolbar-flyout';
11
+ const TOOLTIP_ID = 'toolbar-button-tooltip';
12
+
13
+ const TEST_FLYOUT_ID = 'test';
14
+ const TEST_TOOLTIP = 'tooltip';
15
+ /**
16
+ * @vitest-environment happy-dom
17
+ */
18
+
19
+ describe('FlyoutToolbarButton', () => {
20
+ it('should log error if not inside a flyout provider when opening', async () => {
21
+ vi.spyOn(console, 'error');
22
+ const { getByTestId } = render(
23
+ <FlyoutToolbarButton
24
+ flyoutId={TEST_FLYOUT_ID}
25
+ icon={<IconAdobeCreativeCloud />}
26
+ tooltip={TEST_TOOLTIP}
27
+ content="screen"
28
+ />,
29
+ );
30
+
31
+ await fireEvent.click(getByTestId(BUTTON_ID));
32
+
33
+ expect(console.error).toBeCalled();
34
+ });
35
+
36
+ it('should use flyoutId in flyout context', async () => {
37
+ const setOpenFlyoutIdsStub = vi.fn();
38
+
39
+ const { getByTestId } = render(
40
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
41
+ <FlyoutToolbarButton
42
+ flyoutId={TEST_FLYOUT_ID}
43
+ icon={<IconAdobeCreativeCloud />}
44
+ tooltip={TEST_TOOLTIP}
45
+ content="children"
46
+ />
47
+ </MultiFlyoutContextProvider>,
48
+ );
49
+
50
+ await fireEvent.click(getByTestId(BUTTON_ID));
51
+
52
+ expect(setOpenFlyoutIdsStub).toHaveBeenCalled();
53
+ const dispatchedStateResult = setOpenFlyoutIdsStub.mock.lastCall[0]([]);
54
+ expect(dispatchedStateResult).toEqual([TEST_FLYOUT_ID]);
55
+ });
56
+
57
+ it('should display content', async () => {
58
+ const setOpenFlyoutIdsStub = vi.fn();
59
+
60
+ const { getByTestId } = render(
61
+ <MultiFlyoutContextProvider openFlyoutIds={[TEST_FLYOUT_ID]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
62
+ <FlyoutToolbarButton
63
+ flyoutId={TEST_FLYOUT_ID}
64
+ icon={<IconAdobeCreativeCloud />}
65
+ tooltip={TEST_TOOLTIP}
66
+ flyoutFooter={<div data-test-id="footer">Footer</div>}
67
+ flyoutHeader={<div data-test-id="header">Header</div>}
68
+ content={<div data-test-id="content">Content</div>}
69
+ />
70
+ </MultiFlyoutContextProvider>,
71
+ );
72
+
73
+ expect(getByTestId('content')).toBeVisible();
74
+ expect(getByTestId('header')).toBeVisible();
75
+ expect(getByTestId('footer')).toBeVisible();
76
+ });
77
+
78
+ it('should show tooltip content', async () => {
79
+ const setOpenFlyoutIdsStub = vi.fn();
80
+
81
+ const { getByTestId } = render(
82
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
83
+ <FlyoutToolbarButton
84
+ flyoutId={TEST_FLYOUT_ID}
85
+ icon={<IconAdobeCreativeCloud />}
86
+ tooltip={TEST_TOOLTIP}
87
+ content="children"
88
+ />
89
+ </MultiFlyoutContextProvider>,
90
+ );
91
+
92
+ getByTestId(BUTTON_ID).focus();
93
+
94
+ await waitFor(() => expect(getByTestId(TOOLTIP_ID)).not.toHaveClass('tw-opacity-0'));
95
+ expect(getByTestId(TOOLTIP_ID)).toHaveTextContent(TEST_TOOLTIP);
96
+ });
97
+
98
+ it('should use supplied icon', async () => {
99
+ const setOpenFlyoutIdsStub = vi.fn();
100
+
101
+ const { getByTestId } = render(
102
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
103
+ <FlyoutToolbarButton
104
+ flyoutId={TEST_FLYOUT_ID}
105
+ icon={<IconAdobeCreativeCloud />}
106
+ tooltip={TEST_TOOLTIP}
107
+ content="content"
108
+ />
109
+ </MultiFlyoutContextProvider>,
110
+ );
111
+
112
+ const icons = [...getByTestId(BUTTON_ID).querySelectorAll('svg')];
113
+ expect(icons).toHaveLength(1);
114
+ expect(icons[0].outerHTML).toMatch('IconAdobeCreativeCloud');
115
+ });
116
+
117
+ it('should disable tooltip and flyout when content is inside drag preview', async () => {
118
+ const { getByTestId, queryByTestId } = render(
119
+ <MultiFlyoutContextProvider openFlyoutIds={[TEST_FLYOUT_ID]} setOpenFlyoutIds={vi.fn()}>
120
+ <DragPreviewContextProvider isDragPreview>
121
+ <FlyoutToolbarButton
122
+ flyoutId={TEST_FLYOUT_ID}
123
+ icon={<IconAdobeCreativeCloud />}
124
+ tooltip={TEST_TOOLTIP}
125
+ content={<div data-test-id="content">Content</div>}
126
+ />
127
+ </DragPreviewContextProvider>
128
+ </MultiFlyoutContextProvider>,
129
+ );
130
+
131
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
132
+
133
+ getByTestId(TOOLTIP_ID).focus();
134
+
135
+ await waitFor(() => {
136
+ expect(getByTestId(TOOLTIP_ID)).toHaveClass('tw-opacity-0');
137
+ expect(queryByTestId('content')).toBeNull();
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,61 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { type MutableRefObject, type ReactNode } from 'react';
4
+ import { useDragPreviewContext } from '../context/DragPreviewContext';
5
+ import { ToolbarButtonTooltip } from '../ToolbarButtonTooltip';
6
+ import { Flyout, FlyoutPlacement, useMemoizedId } from '@frontify/fondue';
7
+ import { BaseToolbarButton } from '../BaseToolbarButton';
8
+ import { useMultiFlyoutState } from '../hooks/useMultiFlyoutState';
9
+
10
+ export type FlyoutToolbarButtonProps = {
11
+ content: ReactNode;
12
+ icon: ReactNode;
13
+ tooltip: ReactNode;
14
+ flyoutId: string;
15
+ flyoutFooter?: ReactNode;
16
+ flyoutHeader?: ReactNode;
17
+ };
18
+
19
+ export const FlyoutToolbarButton = ({
20
+ content,
21
+ icon,
22
+ tooltip,
23
+ flyoutId,
24
+ flyoutFooter,
25
+ flyoutHeader,
26
+ }: FlyoutToolbarButtonProps) => {
27
+ const id = useMemoizedId(flyoutId);
28
+
29
+ const { isOpen, onOpenChange } = useMultiFlyoutState(id);
30
+
31
+ const isDragPreview = useDragPreviewContext();
32
+
33
+ return (
34
+ <ToolbarButtonTooltip disabled={isDragPreview || isOpen} content={tooltip}>
35
+ <div className="tw-flex tw-flex-shrink-0 tw-flex-1 tw-h-6 tw-relative">
36
+ <Flyout
37
+ isOpen={isOpen && !isDragPreview}
38
+ legacyFooter={false}
39
+ fixedFooter={flyoutFooter}
40
+ fixedHeader={flyoutHeader}
41
+ fitContent
42
+ hug={false}
43
+ placement={FlyoutPlacement.BottomRight}
44
+ onOpenChange={onOpenChange}
45
+ trigger={(triggerProps, triggerRef) => (
46
+ <BaseToolbarButton
47
+ data-test-id="block-item-wrapper-toolbar-flyout"
48
+ forceActiveStyle={isOpen && !isDragPreview}
49
+ {...triggerProps}
50
+ ref={triggerRef as MutableRefObject<HTMLButtonElement>}
51
+ >
52
+ {icon}
53
+ </BaseToolbarButton>
54
+ )}
55
+ >
56
+ {content}
57
+ </Flyout>
58
+ </div>
59
+ </ToolbarButtonTooltip>
60
+ );
61
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './FlyoutToolbarButton';
@@ -0,0 +1,77 @@
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 { IconAdobeCreativeCloud } from '@frontify/fondue';
6
+ import { MenuToolbarButton } from '.';
7
+ import { MultiFlyoutContextProvider } from '../context/MultiFlyoutContext';
8
+
9
+ const BUTTON_ID = 'block-item-wrapper-toolbar-flyout';
10
+ const MENU_ITEM_ID = 'menu-item';
11
+ const TOOLTIP_ID = 'toolbar-button-tooltip';
12
+
13
+ const TEST_FLYOUT_ID = 'test';
14
+ const TEST_TOOLTIP = 'tooltip';
15
+ /**
16
+ * @vitest-environment happy-dom
17
+ */
18
+
19
+ describe('MenuToolbarButton', () => {
20
+ it('should log error if not inside a flyout provider when opening', async () => {
21
+ vi.spyOn(console, 'error');
22
+ const { getByTestId } = render(<MenuToolbarButton items={[]} />);
23
+
24
+ await fireEvent.click(getByTestId(BUTTON_ID));
25
+
26
+ expect(console.error).toBeCalled();
27
+ });
28
+
29
+ it('should use flyout Id in flyout context', async () => {
30
+ const setOpenFlyoutIdsStub = vi.fn();
31
+
32
+ const { getByTestId } = render(
33
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
34
+ <MenuToolbarButton items={[]} flyoutId={TEST_FLYOUT_ID} />
35
+ </MultiFlyoutContextProvider>,
36
+ );
37
+
38
+ await fireEvent.click(getByTestId(BUTTON_ID));
39
+
40
+ expect(setOpenFlyoutIdsStub).toHaveBeenCalled();
41
+ const dispatchedStateResult = setOpenFlyoutIdsStub.mock.lastCall[0]([]);
42
+ expect(dispatchedStateResult).toEqual([TEST_FLYOUT_ID]);
43
+ });
44
+
45
+ it('should display menu items', async () => {
46
+ const setOpenFlyoutIdsStub = vi.fn();
47
+
48
+ const { getAllByTestId } = render(
49
+ <MultiFlyoutContextProvider openFlyoutIds={[TEST_FLYOUT_ID]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
50
+ <MenuToolbarButton
51
+ items={[
52
+ [{ title: 'item-1', onClick: vi.fn(), icon: <IconAdobeCreativeCloud /> }],
53
+ [{ title: 'item-2', onClick: vi.fn(), icon: <IconAdobeCreativeCloud /> }],
54
+ ]}
55
+ flyoutId={TEST_FLYOUT_ID}
56
+ />
57
+ </MultiFlyoutContextProvider>,
58
+ );
59
+
60
+ expect(getAllByTestId(MENU_ITEM_ID)).toHaveLength(2);
61
+ });
62
+
63
+ it('should show tooltip content', async () => {
64
+ const setOpenFlyoutIdsStub = vi.fn();
65
+
66
+ const { getByTestId } = render(
67
+ <MultiFlyoutContextProvider openFlyoutIds={[]} setOpenFlyoutIds={setOpenFlyoutIdsStub}>
68
+ <MenuToolbarButton items={[]} tooltip={TEST_TOOLTIP} flyoutId={TEST_FLYOUT_ID} />
69
+ </MultiFlyoutContextProvider>,
70
+ );
71
+
72
+ getByTestId(BUTTON_ID).focus();
73
+
74
+ await waitFor(() => expect(getByTestId(TOOLTIP_ID)).not.toHaveClass('tw-opacity-0'));
75
+ expect(getByTestId(TOOLTIP_ID)).toHaveTextContent(TEST_TOOLTIP);
76
+ });
77
+ });
@@ -0,0 +1,30 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { IconDotsHorizontal16, useMemoizedId } from '@frontify/fondue';
4
+ import { ToolbarFlyoutMenu, ToolbarFlyoutMenuItem } from './ToolbarFlyoutMenu';
5
+ import { FlyoutToolbarButton } from '../FlyoutToolbarButton/FlyoutToolbarButton';
6
+
7
+ export const DEFAULT_MENU_BUTTON_ID = 'menu';
8
+
9
+ export type MenuToolbarButtonProps = {
10
+ items: ToolbarFlyoutMenuItem[][];
11
+ flyoutId?: string;
12
+ tooltip?: string;
13
+ };
14
+
15
+ export const MenuToolbarButton = ({
16
+ items,
17
+ flyoutId = DEFAULT_MENU_BUTTON_ID,
18
+ tooltip = 'Options',
19
+ }: MenuToolbarButtonProps) => {
20
+ const id = useMemoizedId(flyoutId);
21
+
22
+ return (
23
+ <FlyoutToolbarButton
24
+ icon={<IconDotsHorizontal16 />}
25
+ tooltip={tooltip}
26
+ flyoutId={id}
27
+ content={<ToolbarFlyoutMenu items={items} flyoutId={id} />}
28
+ />
29
+ );
30
+ };
@@ -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';