Skip to content

Commit

Permalink
Feat/floating UI toolbar dropdown (#2027)
Browse files Browse the repository at this point in the history
* toolbar opens where the button is

* udpated positioning of toolbar dropdown with flyout-ui

* fix: test

* Create good-carpets-drive.md

Co-authored-by: Ziad Beyens <[email protected]>
  • Loading branch information
bojangles-m and zbeyens authored Nov 29, 2022
1 parent 97babd6 commit 476466e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 48 deletions.
6 changes: 6 additions & 0 deletions .changeset/good-carpets-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@udecode/plate-ui-font": patch
"@udecode/plate-ui-toolbar": patch
---

Feat/floating UI toolbar dropdown
2 changes: 1 addition & 1 deletion examples/src/ToolbarButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import {
import { AlignToolbarButtons } from './align/AlignToolbarButtons';
import { BasicElementToolbarButtons } from './basic-elements/BasicElementToolbarButtons';
import { BasicMarkToolbarButtons } from './basic-marks/BasicMarkToolbarButtons';
import { ExcalidrawElementToolbarButton } from './excalidraw/ExcalidrawElementToolbarButton';
import { IndentToolbarButtons } from './indent/IndentToolbarButtons';
import { ListToolbarButtons } from './list/ListToolbarButtons';
import { TableToolbarButtons } from './table/TableToolbarButtons';
import { ExcalidrawElementToolbarButton } from './excalidraw/ExcalidrawElementToolbarButton'

export const ToolbarButtons = () => {
const colorTooltip: TippyProps = { content: 'Text color' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('ColorPickerToolbarDropdown', () => {
});

it('should open the color picker', () => {
expect(screen.getByTestId('ColorPicker')).not.toBeVisible();
expect(() => screen.getByTestId('ColorPicker')).toThrowError();

openToolbar();

Expand All @@ -125,8 +125,7 @@ describe('ColorPickerToolbarDropdown', () => {
},
]);

const value = await screen.findByTestId('ColorPicker');
expect(value).not.toBeVisible();
expect(() => screen.getByTestId('ColorPicker')).toThrowError();
});

it(`should clear selected ${target}`, async () => {
Expand All @@ -147,8 +146,7 @@ describe('ColorPickerToolbarDropdown', () => {
},
]);

const value = await screen.findByTestId('ColorPicker');
expect(value).not.toBeVisible();
expect(() => screen.getByTestId('ColorPicker')).toThrowError();
});
});
};
Expand Down
65 changes: 23 additions & 42 deletions packages/ui/toolbar/src/ToolbarDropdown/ToolbarDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { ReactNode, useEffect, useState } from 'react';
import React, { ReactNode } from 'react';
import { css } from 'styled-components';
import tw from 'twin.macro';
import { useDropdownControls } from './useDropdownControls';

type ToolbarDropdownProps = {
control: ReactNode;
Expand All @@ -17,53 +18,33 @@ export const ToolbarDropdown = ({
onOpen,
onClose,
}: ToolbarDropdownProps) => {
const [
referenceElement,
setReferenceElement,
] = useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null
);

useEffect(() => {
const listener = (ev: MouseEvent) => {
if (open) {
if (referenceElement && ev.composedPath().includes(referenceElement)) {
return;
}
if (popperElement && ev.composedPath().includes(popperElement)) {
return;
}

onClose?.(ev);
}
};
document.body.addEventListener('mousedown', listener);
return () => {
document.body.removeEventListener('mousedown', listener);
};
}, [onClose, open, popperElement, referenceElement]);
const { styles, reference, floating } = useDropdownControls({
open,
onClose,
});

return (
<>
<div ref={setReferenceElement} onMouseDown={onOpen}>
<div ref={reference} onMouseDown={onOpen}>
{control}
</div>

<div
ref={setPopperElement}
css={[
tw`absolute bg-white top-10`,
!open && tw`hidden`,
css`
border: 1px solid #ccc;
box-shadow: 0 1px 3px 0 #ccc;
z-index: 1;
`,
]}
>
{children}
</div>
{open && (
<div
ref={floating}
css={[
tw` bg-white`,
css`
border: 1px solid #ccc;
box-shadow: 0 1px 3px 0 #ccc;
z-index: 1;
`,
]}
style={styles}
>
{children}
</div>
)}
</>
);
};
65 changes: 65 additions & 0 deletions packages/ui/toolbar/src/ToolbarDropdown/useDropdownControls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect } from 'react';
import { ExtendedRefs, flip, useFloating } from '@udecode/plate-floating';

type useDropdownControlsProps = {
open: boolean;
onClose?: (ev: MouseEvent) => void;
};

const closeAllExceptSelectedOneListener = ({
open,
onClose,
refs,
}: useDropdownControlsProps & { refs: ExtendedRefs<HTMLElement> }) => (
ev: MouseEvent
) => {
if (open) {
const target = ev.target as HTMLElement;
if (refs.reference.current?.contains(target)) {
return;
}
if (refs.floating.current?.contains(target)) {
return;
}

onClose?.(ev);
}
};

export const useDropdownControls = ({
open,
onClose,
}: useDropdownControlsProps) => {
const {
x,
y,
reference,
floating,
strategy,
refs,
} = useFloating<HTMLElement>({
open,
strategy: 'fixed',
placement: 'bottom-start',
middleware: [flip()],
});

useEffect(() => {
const listener = closeAllExceptSelectedOneListener({ open, onClose, refs });
document.body.addEventListener('mousedown', listener);
return () => {
document.body.removeEventListener('mousedown', listener);
};
}, [onClose, open, refs]);

return {
styles: {
position: strategy,
top: y ?? 0,
left: x ?? 0,
width: 'max-content',
},
reference,
floating,
};
};

2 comments on commit 476466e

@vercel
Copy link

@vercel vercel bot commented on 476466e Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

plate – ./

plate-udecode.vercel.app
plate-git-main-udecode.vercel.app
plate.udecode.io
www.plate.udecode.io

@vercel
Copy link

@vercel vercel bot commented on 476466e Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

plate-examples – ./

plate-examples-git-main-udecode.vercel.app
plate-examples.vercel.app
plate-examples-udecode.vercel.app

Please sign in to comment.