From 119914ab39bb5de8ae4c864dbd73eef59c45aa46 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 18 Jan 2024 16:45:44 +0100 Subject: [PATCH 01/19] feat(react-draggable-dialog): create base logic for draggable dialogs --- package.json | 3 + packages/react-draggable-dialog/package.json | 11 +- .../DraggableDialog/DraggableDialog.styles.ts | 22 --- .../DraggableDialog/DraggableDialog.test.tsx | 6 +- .../DraggableDialog/DraggableDialog.tsx | 100 +++++++++- .../DraggableDialog/DraggableDialog.types.ts | 68 +++++++ .../DraggableDialog/useDraggableDialog.ts | 177 ++++++++++++++++++ .../src/components/DraggableDialog/utils.ts | 77 ++++++++ .../DraggableDialogHandle.styles.ts | 7 + .../DraggableDialogHandle.test.tsx | 13 ++ .../DraggableDialogHandle.tsx | 29 +++ .../DraggableDialogHandle.types.ts | 10 + .../components/DraggableDialogHandle/index.ts | 1 + .../DraggableDialogSurface.styles.ts | 7 + .../DraggableDialogSurface.test.tsx | 13 ++ .../DraggableDialogSurface.tsx | 80 ++++++++ .../DraggableDialogSurface.types.ts | 10 + .../DraggableDialogSurface/index.ts | 1 + .../src/contexts/DraggableDialogContext.ts | 31 +++ .../src/hooks/useDraggableDialog.ts | 5 + packages/react-draggable-dialog/src/index.ts | 3 +- .../DraggableDialog/Default.stories.tsx | 55 +++++- .../stories/DraggableDialog/index.stories.tsx | 1 - yarn.lock | 31 +++ 24 files changed, 726 insertions(+), 35 deletions(-) delete mode 100644 packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.styles.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.styles.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.test.tsx create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogHandle/index.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.styles.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.test.tsx create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts create mode 100644 packages/react-draggable-dialog/src/components/DraggableDialogSurface/index.ts create mode 100644 packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts create mode 100644 packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts diff --git a/package.json b/package.json index b211a9fd..f78ad72b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,9 @@ "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.5", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/utilities": "^3.2.2", "@fluentui/react-components": "^9.44.4", "@griffel/shadow-dom": "~0.1.0", "@nx/devkit": "16.1.4", diff --git a/packages/react-draggable-dialog/package.json b/packages/react-draggable-dialog/package.json index eaf2e430..f1802922 100644 --- a/packages/react-draggable-dialog/package.json +++ b/packages/react-draggable-dialog/package.json @@ -2,11 +2,16 @@ "name": "@fluentui-contrib/react-draggable-dialog", "version": "0.0.1", "private": true, + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/utilities": "^3.2.2", + "@dnd-kit/modifiers": "^7.0.0" + }, "peerDependencies": { "@fluentui/react-components": ">=9.35.1 <10.0.0", - "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0", - "react-dom": ">=16.8.0 <19.0.0" + "@types/react": ">=16.8.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.8.0 <19.0.0" } } diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.styles.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.styles.ts deleted file mode 100644 index b54388da..00000000 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.styles.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { makeStyles, shorthands, tokens } from '@fluentui/react-components'; - -export const useStyles = makeStyles({ - root: { - ...shorthands.padding(tokens.spacingHorizontalM), - ...shorthands.border( - tokens.strokeWidthThin, - 'solid', - tokens.colorNeutralStroke1 - ), - color: tokens.colorNeutralForeground1, - backgroundColor: tokens.colorNeutralBackground1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - maxWidth: '200px', - ':hover': { - backgroundColor: tokens.colorNeutralBackground1Hover, - color: tokens.colorNeutralForeground1Hover, - }, - }, -}); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.test.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.test.tsx index b87d205e..b0e6da5a 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.test.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.test.tsx @@ -4,6 +4,10 @@ import { DraggableDialog } from './DraggableDialog'; describe('DraggableDialog', () => { it('should render', () => { - render(); + render( + +
Content
+
+ ); }); }); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index e6db0039..889c518b 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -1,8 +1,98 @@ import * as React from 'react'; -import { mergeClasses } from '@fluentui/react-components'; -import { useStyles } from './DraggableDialog.styles'; +import { + DndContext, + useSensor, + MouseSensor, + TouchSensor, + KeyboardSensor, + useSensors, + DragEndEvent, + PointerSensor, +} from '@dnd-kit/core'; +import { Dialog, useId } from '@fluentui/react-components'; -export const DraggableDialog: React.FC = () => { - const styles = useStyles(); - return
Hello World!
; +import { DraggableDialogContextProvider } from '../../contexts/DraggableDialogContext'; +import { DraggableDialogProps } from './DraggableDialog.types'; +import { getParsedDraggableMargin, restrictToMarginModifier } from './utils'; + +export const DraggableDialog: React.FC = (props) => { + const { + margin, + keepInViewport, + id, + announcements, + position: defaultPosition, + } = { + id: useId('draggable-dialog-'), + keepInViewport: true, + ...props, + position: { x: 0, y: 0, ...props.position }, + margin: getParsedDraggableMargin(props.margin), + }; + + const [isDragging, setIsDragging] = React.useState(false); + const [hasBeenDragged, setHasBeenDragged] = React.useState(false); + const [position, setPosition] = React.useState(defaultPosition); + + const mouseSensor = useSensor(MouseSensor); + const pointerSensor = useSensor(PointerSensor); + const touchSensor = useSensor(TouchSensor); + const keyboardSensor = useSensor(KeyboardSensor); + const sensors = useSensors( + pointerSensor, + mouseSensor, + touchSensor, + keyboardSensor + ); + + const onDragEnd = React.useCallback((event: DragEndEvent) => { + setPosition(({ x, y }) => ({ + x: x + event.delta.x, + y: y + event.delta.y, + })); + setIsDragging(false); + }, []); + + const onDragStart = React.useCallback(() => { + setHasBeenDragged(true); + setIsDragging(true); + }, []); + + const restrictToMargin = React.useMemo(() => { + return restrictToMarginModifier({ margin, keepInViewport }); + }, [margin, keepInViewport]); + + const dndAnnouncements = React.useMemo(() => { + if (!announcements) { + return; + } + + return { + onDragStart: () => announcements.start, + onDragEnd: () => announcements.end, + }; + }, [announcements]); + + return ( + + + + + + ); }; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts new file mode 100644 index 00000000..ef30bc1c --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts @@ -0,0 +1,68 @@ +import { DialogProps } from '@fluentui/react-components'; + +export type DraggableMarginAxis = { + mainAxis?: number; + crossAxis?: number; +}; + +export type DraggableMarginViewport = { + top?: number; + end?: number; + bottom?: number; + start?: number; +}; + +export type DraggableMargin = + | number + | DraggableMarginAxis + | DraggableMarginViewport; + +export type DraggableDialogProps = DialogProps & { + /** + * Unique identifier for the draggable dialog. + */ + id?: string; + + /** + * Whether the element should remain in the viewport when dragged. + * @default true + */ + keepInViewport?: boolean; + + /** + * The margin from the viewport to keep the element in when dragged. Only used when keepInViewport is true. + * @default 0 + */ + margin?: DraggableMargin; + + /** + * The initial position of the draggable dialog. + * @default { x: 0, y: 0 } + */ + position?: { + /** + * A initial x coordinate of the draggable dialog. + */ + x?: number; + + /** + * A initial y coordinate of the draggable dialog. + */ + y?: number; + }; + + /** + * Text to be announced by screen readers when the dialog is dragged. + */ + announcements?: { + /** + * Announces the start of a drag action. + */ + start?: string; + + /** + * Announces the end of a drag action. + */ + end?: string; + }; +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts new file mode 100644 index 00000000..913724e0 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts @@ -0,0 +1,177 @@ +import * as React from 'react'; +import { useFluent, useEventCallback } from '@fluentui/react-components'; + +function roundByDPR(value: number, document?: Document) { + const win = document?.defaultView; + + if (!win) { + return value; + } + + const dpr = win.devicePixelRatio || 1; + + return Math.round(value * dpr) / dpr; +} + +type DraggableMarginAxis = { + mainAxis?: number; + crossAxis?: number; +}; +type DraggableMarginViewport = { + top?: number; + end?: number; + bottom?: number; + start?: number; +}; +type DraggableDialogOptions = { + keepInViewport?: boolean; + margin?: number | DraggableMarginAxis | DraggableMarginViewport; +}; + +const getDraggableMargin = ( + margin: DraggableDialogOptions['margin'] +): Required => { + const defaultMargin = { + top: 0, + end: 0, + bottom: 0, + start: 0, + }; + + if (!margin) { + return defaultMargin; + } + + if (typeof margin === 'number') { + return { + top: margin, + end: margin, + bottom: margin, + start: margin, + }; + } + + if ('mainAxis' in margin || 'crossAxis' in margin) { + const x = margin.mainAxis || 0; + const y = margin.crossAxis || 0; + + return { + top: y, + end: x, + bottom: y, + start: x, + }; + } + + return { + ...defaultMargin, + ...margin, + }; +}; + +export const useDraggableDialog = (_opts: DraggableDialogOptions) => { + const { targetDocument } = useFluent(); + const { margin, keepInViewport } = { + keepInViewport: true, + ..._opts, + margin: getDraggableMargin(_opts.margin), + }; + const [dragging, setDragging] = React.useState(false); + const ref = React.useRef(null); + const currentPosition = React.useRef({ x: 0, y: 0 }); + + const getInBoundsPosition = useEventCallback( + (event: React.MouseEvent) => { + const { + currentTarget: { offsetLeft, offsetTop }, + } = event; + + let { x, y } = { + x: event.clientX - currentPosition.current.x, + y: event.clientY - currentPosition.current.y, + }; + + if (!keepInViewport) { + return { x, y }; + } + + if (x - margin.start + offsetLeft < 0) { + x = -offsetLeft + margin.start; + } + + if (y - margin.top + offsetTop < 0) { + y = -offsetTop + margin.top; + } + + if (offsetLeft - margin.end - x < 0) { + x = offsetLeft - margin.end; + } + + if (offsetTop - margin.bottom - y < 0) { + y = offsetTop - margin.bottom; + } + + return { x, y }; + } + ); + + const applyPosition = useEventCallback( + (el: HTMLDivElement, x: number, y: number) => { + const transformX = roundByDPR(x, targetDocument); + const transformY = roundByDPR(y, targetDocument); + + Object.assign(el.style, { + transform: `translate3D(${transformX}px, ${transformY}px, 0px)`, + }); + } + ); + + const onMouseDown = useEventCallback( + (event: React.MouseEvent) => { + const { x, y } = currentPosition.current; + + currentPosition.current = { + x: event.clientX - x, + y: event.clientY - y, + }; + setDragging(true); + } + ); + + const onMouseUp = useEventCallback( + (event: React.MouseEvent) => { + const { x, y } = getInBoundsPosition(event); + + currentPosition.current = { x, y }; + applyPosition(event.currentTarget, x, y); + setDragging(false); + } + ); + + const onMouseMove = useEventCallback( + (event: React.MouseEvent) => { + const { x, y } = currentPosition.current; + + applyPosition(event.currentTarget, event.clientX - x, event.clientY - y); + } + ); + + const onMouseLeave = onMouseUp; + + return React.useMemo(() => { + if (!dragging) { + return { + ref, + onMouseDown, + }; + } + + return { + ref, + onMouseDown, + onMouseLeave, + onMouseUp, + onMouseMove, + }; + }, [dragging]); +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts new file mode 100644 index 00000000..eb5111e1 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts @@ -0,0 +1,77 @@ +import { Modifier } from '@dnd-kit/core'; +import { restrictToWindowEdges } from '@dnd-kit/modifiers'; +import { + DraggableDialogProps, + DraggableMargin, + DraggableMarginViewport, +} from './DraggableDialog.types'; + +type RestrictToMarginModifierOptions = { + margin: Required; +} & Pick; + +type RestrictToMarginModifier = ( + options: RestrictToMarginModifierOptions +) => Modifier; + +export const restrictToMarginModifier: RestrictToMarginModifier = + ({ margin, keepInViewport }) => + ({ windowRect, transform, ...modifier }) => { + if (!keepInViewport || !windowRect) { + return transform; + } + + return restrictToWindowEdges({ + ...modifier, + transform, + windowRect: { + width: windowRect.width - margin.start - margin.end, + height: windowRect.height - margin.top - margin.bottom, + top: windowRect.top + margin.top, + right: windowRect.right + margin.end, + bottom: windowRect.bottom + margin.bottom, + left: windowRect.left + margin.start, + }, + }); + }; + +export const getParsedDraggableMargin = ( + margin: DraggableMargin | undefined +): Required => { + const defaultMargin = { + top: 0, + end: 0, + bottom: 0, + start: 0, + }; + + if (!margin) { + return defaultMargin; + } + + if (typeof margin === 'number') { + return { + top: margin, + end: margin, + bottom: margin, + start: margin, + }; + } + + if ('mainAxis' in margin || 'crossAxis' in margin) { + const x = margin.mainAxis || 0; + const y = margin.crossAxis || 0; + + return { + top: y, + end: x, + bottom: y, + start: x, + }; + } + + return { + ...defaultMargin, + ...margin, + }; +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.styles.ts b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.styles.ts new file mode 100644 index 00000000..c8f90bdc --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.styles.ts @@ -0,0 +1,7 @@ +import { makeStyles } from '@fluentui/react-components'; + +export const useStyles = makeStyles({ + root: { + cursor: 'move', + }, +}); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.test.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.test.tsx new file mode 100644 index 00000000..0798309a --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.test.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { DraggableDialogHandle } from './DraggableDialogHandle'; + +describe('DraggableDialogHandle', () => { + it('should render', () => { + render( + +
Handle
+
+ ); + }); +}); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx new file mode 100644 index 00000000..7a51d130 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { useDraggable } from '@dnd-kit/core'; +import { mergeClasses } from '@fluentui/react-components'; + +import { useDraggableDialog } from '../../hooks/useDraggableDialog'; +import { DraggableDialogHandleProps } from './DraggableDialogHandle.types'; +import { useStyles } from './DraggableDialogHandle.styles'; + +export const DraggableDialogHandle: React.FC = ({ + children, + className, +}) => { + const styles = useStyles(); + const { id } = useDraggableDialog(); + const { setActivatorNodeRef, attributes, listeners } = useDraggable({ + id, + }); + + return React.cloneElement(children, { + ref: setActivatorNodeRef, + className: mergeClasses( + 'fui-DraggableDialogHandle', + styles.root, + className + ), + ...attributes, + ...listeners, + }); +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts new file mode 100644 index 00000000..97bd08fd --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts @@ -0,0 +1,10 @@ +import * as React from 'react'; + +export type DraggableDialogHandleProps = React.HTMLAttributes & { + /** + * The element that will act as the handle for dragging the dialog. + * Only one child is allowed. + * @required + */ + children: JSX.Element; +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/index.ts b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/index.ts new file mode 100644 index 00000000..f1fc07dc --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/index.ts @@ -0,0 +1 @@ +export * from './DraggableDialogHandle'; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.styles.ts b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.styles.ts new file mode 100644 index 00000000..8fdfbe25 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.styles.ts @@ -0,0 +1,7 @@ +import { makeStyles } from '@fluentui/react-components'; + +export const useStyles = makeStyles({ + root: { + width: 'max-content', + }, +}); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.test.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.test.tsx new file mode 100644 index 00000000..0df267e8 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.test.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { DraggableDialogSurface } from './DraggableDialogSurface'; + +describe('DraggableDialogSurface', () => { + it('should render', () => { + render( + +
Content
+
+ ); + }); +}); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx new file mode 100644 index 00000000..5f270366 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { useDraggable } from '@dnd-kit/core'; +import { CSS } from '@dnd-kit/utilities'; +import { + DialogSurface, + mergeClasses, + useMergedRefs, +} from '@fluentui/react-components'; + +import { DraggableDialogSurfaceProps } from './DraggableDialogSurface.types'; +import { useStyles } from './DraggableDialogSurface.styles'; +import { useDraggableDialog } from '../../hooks/useDraggableDialog'; + +export const DraggableDialogSurface: React.FC = ({ + children, + className, +}) => { + const styles = useStyles(); + const ref = React.useRef(null); + const { + id, + hasBeenDragged, + isDragging, + position: { x, y }, + } = useDraggableDialog(); + const { setNodeRef, transform } = useDraggable({ + id, + }); + const refs = useMergedRefs(setNodeRef as React.Ref, ref); + + const style = React.useMemo(() => { + if (!hasBeenDragged) { + return; + } + + if (ref.current) { + const baseStyles = { + top: `calc(50% + ${y}px)`, + left: `calc(50% + ${x}px)`, + margin: 0, + marginTop: -Math.ceil(ref.current.clientHeight / 2), + marginLeft: -Math.ceil(ref.current.clientWidth / 2), + transition: 'none', + }; + + if (isDragging) { + return { + ...baseStyles, + transform: CSS.Translate.toString(transform), + }; + } + + return baseStyles; + } + + return { + top: '50%', + left: '50%', + margin: 0, + marginTop: y, + marginLeft: x, + transform: `translate3D(-50%, -50%, 0)`, + transition: 'none', + }; + }, [transform, x, y, hasBeenDragged, isDragging]); + + return ( + + {children} + + ); +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts new file mode 100644 index 00000000..3b353df3 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts @@ -0,0 +1,10 @@ +import { DialogSurfaceProps } from '@fluentui/react-components'; + +export type DraggableDialogSurfaceProps = DialogSurfaceProps & { + /** + * The element that will be draggable. + * Only one child is allowed. + * @required + */ + children: JSX.Element; +}; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/index.ts b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/index.ts new file mode 100644 index 00000000..779ec473 --- /dev/null +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/index.ts @@ -0,0 +1 @@ +export * from './DraggableDialogSurface'; diff --git a/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts new file mode 100644 index 00000000..54871eec --- /dev/null +++ b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts @@ -0,0 +1,31 @@ +import * as React from 'react'; + +export type DraggableDialogContextValue = { + id: string; + isDragging: boolean; + hasBeenDragged: boolean; + position: { + x: number; + y: number; + }; +}; + +export const draggableDialogContextDefaultValue = { + id: '', + isDragging: false, + hasBeenDragged: false, + position: { + x: 0, + y: 0, + }, +}; + +const draggableDialogContext = React.createContext( + draggableDialogContextDefaultValue +); + +export const useDraggableDialogContext = () => + React.useContext(draggableDialogContext) ?? + draggableDialogContextDefaultValue; + +export const DraggableDialogContextProvider = draggableDialogContext.Provider; diff --git a/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts b/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts new file mode 100644 index 00000000..1a082537 --- /dev/null +++ b/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts @@ -0,0 +1,5 @@ +import { useDraggableDialogContext } from '../contexts/DraggableDialogContext'; + +export const useDraggableDialog = () => { + return useDraggableDialogContext(); +}; diff --git a/packages/react-draggable-dialog/src/index.ts b/packages/react-draggable-dialog/src/index.ts index 4cf6b7a2..93e39049 100644 --- a/packages/react-draggable-dialog/src/index.ts +++ b/packages/react-draggable-dialog/src/index.ts @@ -1,2 +1,3 @@ +export * from './components/DraggableDialogHandle'; +export * from './components/DraggableDialogSurface'; export * from './components/DraggableDialog'; -export {}; diff --git a/packages/react-draggable-dialog/stories/DraggableDialog/Default.stories.tsx b/packages/react-draggable-dialog/stories/DraggableDialog/Default.stories.tsx index 6e460fca..65d7aa58 100644 --- a/packages/react-draggable-dialog/stories/DraggableDialog/Default.stories.tsx +++ b/packages/react-draggable-dialog/stories/DraggableDialog/Default.stories.tsx @@ -1,4 +1,55 @@ import * as React from 'react'; -import { DraggableDialog } from '@fluentui-contrib/react-draggable-dialog'; +import { + makeStyles, + Button, + DialogContent, + Avatar, +} from '@fluentui/react-components'; -export const Default = () => ; +import { + DraggableDialog, + DraggableDialogSurface, + DraggableDialogHandle, +} from '@fluentui-contrib/react-draggable-dialog'; + +const useStyles = makeStyles({ + dialog: { + width: '215px', + height: '120px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + + handle: { + width: '100%', + height: '100%', + position: 'absolute', + top: 0, + left: 0, + zIndex: 1, + }, +}); + +export const Default = () => { + const styles = useStyles(); + const [open, setOpen] = React.useState(false); + + return ( + + + + + + +
+
+ + +
+
+
+ ); +}; diff --git a/packages/react-draggable-dialog/stories/DraggableDialog/index.stories.tsx b/packages/react-draggable-dialog/stories/DraggableDialog/index.stories.tsx index d9052860..3c05ce46 100644 --- a/packages/react-draggable-dialog/stories/DraggableDialog/index.stories.tsx +++ b/packages/react-draggable-dialog/stories/DraggableDialog/index.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { Meta } from '@storybook/react'; import { DraggableDialog } from '@fluentui-contrib/react-draggable-dialog'; export { Default } from './Default.stories'; diff --git a/yarn.lock b/yarn.lock index 79f3fd4f..93442a93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1152,6 +1152,37 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@dnd-kit/accessibility@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz#1054e19be276b5f1154ced7947fc0cb5d99192e0" + integrity sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.1.0.tgz#e81a3d10d9eca5d3b01cbf054171273a3fe01def" + integrity sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg== + dependencies: + "@dnd-kit/accessibility" "^3.1.0" + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/modifiers@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz#229666dd4e8b9487f348035117f993af755b3db9" + integrity sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg== + dependencies: + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" + integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== + dependencies: + tslib "^2.0.0" + "@emotion/hash@^0.9.0": version "0.9.1" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" From b9769eaef5378c818011e27fbc5a072bb6620705 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 18 Jan 2024 16:52:00 +0100 Subject: [PATCH 02/19] fix: improve style logic by grouping styles --- .../DraggableDialogSurface.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx index 5f270366..26123b36 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx @@ -33,34 +33,40 @@ export const DraggableDialogSurface: React.FC = ({ return; } + const resetStyles = { + margin: 0, + transition: 'none', + }; + if (ref.current) { const baseStyles = { + ...resetStyles, top: `calc(50% + ${y}px)`, left: `calc(50% + ${x}px)`, - margin: 0, marginTop: -Math.ceil(ref.current.clientHeight / 2), marginLeft: -Math.ceil(ref.current.clientWidth / 2), - transition: 'none', }; if (isDragging) { + /* Styles for when the dialog is being dragged */ return { ...baseStyles, transform: CSS.Translate.toString(transform), }; } + /* Styles for the final position of the dialog */ return baseStyles; } + /* Styles to restore a previously dragged dialog */ return { + ...resetStyles, top: '50%', left: '50%', - margin: 0, marginTop: y, marginLeft: x, transform: `translate3D(-50%, -50%, 0)`, - transition: 'none', }; }, [transform, x, y, hasBeenDragged, isDragging]); From ccb086699e4a506bfe8ca8d9634794f0a4745330 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 18 Jan 2024 16:52:32 +0100 Subject: [PATCH 03/19] docs: improve comments description --- .../DraggableDialogSurface/DraggableDialogSurface.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx index 26123b36..e62bbcfd 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx @@ -48,18 +48,18 @@ export const DraggableDialogSurface: React.FC = ({ }; if (isDragging) { - /* Styles for when the dialog is being dragged */ + /* When the dialog is being dragged */ return { ...baseStyles, transform: CSS.Translate.toString(transform), }; } - /* Styles for the final position of the dialog */ + /* The final position of the dialog */ return baseStyles; } - /* Styles to restore a previously dragged dialog */ + /* Restore a previously dragged dialog */ return { ...resetStyles, top: '50%', From 7616f31eebf8d6abedb1fde4b9ca43df8a985f91 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Tue, 30 Jan 2024 15:57:53 +0100 Subject: [PATCH 04/19] fix: make some code cleanups and remove unecessary exportsd --- .../DraggableDialog/DraggableDialog.tsx | 11 ++--------- .../DraggableDialog/DraggableDialog.types.ts | 16 ---------------- .../src/contexts/DraggableDialogContext.ts | 15 ++++----------- 3 files changed, 6 insertions(+), 36 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index 889c518b..f190c2fc 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -16,23 +16,16 @@ import { DraggableDialogProps } from './DraggableDialog.types'; import { getParsedDraggableMargin, restrictToMarginModifier } from './utils'; export const DraggableDialog: React.FC = (props) => { - const { - margin, - keepInViewport, - id, - announcements, - position: defaultPosition, - } = { + const { margin, keepInViewport, id, announcements } = { id: useId('draggable-dialog-'), keepInViewport: true, ...props, - position: { x: 0, y: 0, ...props.position }, margin: getParsedDraggableMargin(props.margin), }; const [isDragging, setIsDragging] = React.useState(false); const [hasBeenDragged, setHasBeenDragged] = React.useState(false); - const [position, setPosition] = React.useState(defaultPosition); + const [position, setPosition] = React.useState({ x: 0, y: 0 }); const mouseSensor = useSensor(MouseSensor); const pointerSensor = useSensor(PointerSensor); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts index ef30bc1c..6d1ff685 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.types.ts @@ -35,22 +35,6 @@ export type DraggableDialogProps = DialogProps & { */ margin?: DraggableMargin; - /** - * The initial position of the draggable dialog. - * @default { x: 0, y: 0 } - */ - position?: { - /** - * A initial x coordinate of the draggable dialog. - */ - x?: number; - - /** - * A initial y coordinate of the draggable dialog. - */ - y?: number; - }; - /** * Text to be announced by screen readers when the dialog is dragged. */ diff --git a/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts index 54871eec..0d72ba10 100644 --- a/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts +++ b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts @@ -1,16 +1,6 @@ import * as React from 'react'; -export type DraggableDialogContextValue = { - id: string; - isDragging: boolean; - hasBeenDragged: boolean; - position: { - x: number; - y: number; - }; -}; - -export const draggableDialogContextDefaultValue = { +const draggableDialogContextDefaultValue = { id: '', isDragging: false, hasBeenDragged: false, @@ -20,6 +10,9 @@ export const draggableDialogContextDefaultValue = { }, }; +export type DraggableDialogContextValue = + typeof draggableDialogContextDefaultValue; + const draggableDialogContext = React.createContext( draggableDialogContextDefaultValue ); From def9b7dbf2b616be9cf2495c92b3e7ebe2381620 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Tue, 30 Jan 2024 16:57:55 +0100 Subject: [PATCH 05/19] fix: explicit exports --- packages/react-draggable-dialog/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-draggable-dialog/src/index.ts b/packages/react-draggable-dialog/src/index.ts index 93e39049..60d94d61 100644 --- a/packages/react-draggable-dialog/src/index.ts +++ b/packages/react-draggable-dialog/src/index.ts @@ -1,3 +1,3 @@ -export * from './components/DraggableDialogHandle'; -export * from './components/DraggableDialogSurface'; -export * from './components/DraggableDialog'; +export { DraggableDialogHandle } from './components/DraggableDialogHandle'; +export { DraggableDialogSurface } from './components/DraggableDialogSurface'; +export { DraggableDialog } from './components/DraggableDialog'; From 41d129b3863d652a2f036baf540fbd8666080c46 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:11:55 +0100 Subject: [PATCH 06/19] Delete packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts --- .../DraggableDialog/useDraggableDialog.ts | 177 ------------------ 1 file changed, 177 deletions(-) delete mode 100644 packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts deleted file mode 100644 index 913724e0..00000000 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/useDraggableDialog.ts +++ /dev/null @@ -1,177 +0,0 @@ -import * as React from 'react'; -import { useFluent, useEventCallback } from '@fluentui/react-components'; - -function roundByDPR(value: number, document?: Document) { - const win = document?.defaultView; - - if (!win) { - return value; - } - - const dpr = win.devicePixelRatio || 1; - - return Math.round(value * dpr) / dpr; -} - -type DraggableMarginAxis = { - mainAxis?: number; - crossAxis?: number; -}; -type DraggableMarginViewport = { - top?: number; - end?: number; - bottom?: number; - start?: number; -}; -type DraggableDialogOptions = { - keepInViewport?: boolean; - margin?: number | DraggableMarginAxis | DraggableMarginViewport; -}; - -const getDraggableMargin = ( - margin: DraggableDialogOptions['margin'] -): Required => { - const defaultMargin = { - top: 0, - end: 0, - bottom: 0, - start: 0, - }; - - if (!margin) { - return defaultMargin; - } - - if (typeof margin === 'number') { - return { - top: margin, - end: margin, - bottom: margin, - start: margin, - }; - } - - if ('mainAxis' in margin || 'crossAxis' in margin) { - const x = margin.mainAxis || 0; - const y = margin.crossAxis || 0; - - return { - top: y, - end: x, - bottom: y, - start: x, - }; - } - - return { - ...defaultMargin, - ...margin, - }; -}; - -export const useDraggableDialog = (_opts: DraggableDialogOptions) => { - const { targetDocument } = useFluent(); - const { margin, keepInViewport } = { - keepInViewport: true, - ..._opts, - margin: getDraggableMargin(_opts.margin), - }; - const [dragging, setDragging] = React.useState(false); - const ref = React.useRef(null); - const currentPosition = React.useRef({ x: 0, y: 0 }); - - const getInBoundsPosition = useEventCallback( - (event: React.MouseEvent) => { - const { - currentTarget: { offsetLeft, offsetTop }, - } = event; - - let { x, y } = { - x: event.clientX - currentPosition.current.x, - y: event.clientY - currentPosition.current.y, - }; - - if (!keepInViewport) { - return { x, y }; - } - - if (x - margin.start + offsetLeft < 0) { - x = -offsetLeft + margin.start; - } - - if (y - margin.top + offsetTop < 0) { - y = -offsetTop + margin.top; - } - - if (offsetLeft - margin.end - x < 0) { - x = offsetLeft - margin.end; - } - - if (offsetTop - margin.bottom - y < 0) { - y = offsetTop - margin.bottom; - } - - return { x, y }; - } - ); - - const applyPosition = useEventCallback( - (el: HTMLDivElement, x: number, y: number) => { - const transformX = roundByDPR(x, targetDocument); - const transformY = roundByDPR(y, targetDocument); - - Object.assign(el.style, { - transform: `translate3D(${transformX}px, ${transformY}px, 0px)`, - }); - } - ); - - const onMouseDown = useEventCallback( - (event: React.MouseEvent) => { - const { x, y } = currentPosition.current; - - currentPosition.current = { - x: event.clientX - x, - y: event.clientY - y, - }; - setDragging(true); - } - ); - - const onMouseUp = useEventCallback( - (event: React.MouseEvent) => { - const { x, y } = getInBoundsPosition(event); - - currentPosition.current = { x, y }; - applyPosition(event.currentTarget, x, y); - setDragging(false); - } - ); - - const onMouseMove = useEventCallback( - (event: React.MouseEvent) => { - const { x, y } = currentPosition.current; - - applyPosition(event.currentTarget, event.clientX - x, event.clientY - y); - } - ); - - const onMouseLeave = onMouseUp; - - return React.useMemo(() => { - if (!dragging) { - return { - ref, - onMouseDown, - }; - } - - return { - ref, - onMouseDown, - onMouseLeave, - onMouseUp, - onMouseMove, - }; - }, [dragging]); -}; From d583361e3753c99abc0ce47d4affabec22d1972d Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:16:02 +0100 Subject: [PATCH 07/19] fix: improve property handling --- .../DraggableDialog/DraggableDialog.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index f190c2fc..ca33dc8f 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -51,28 +51,28 @@ export const DraggableDialog: React.FC = (props) => { setIsDragging(true); }, []); - const restrictToMargin = React.useMemo(() => { - return restrictToMarginModifier({ margin, keepInViewport }); + const modifiers = React.useMemo(() => { + return [restrictToMarginModifier({ margin, keepInViewport })]; }, [margin, keepInViewport]); - const dndAnnouncements = React.useMemo(() => { + const accessibilityProps = React.useMemo(() => { if (!announcements) { return; } return { - onDragStart: () => announcements.start, - onDragEnd: () => announcements.end, + announcements: { + onDragStart: () => announcements.start, + onDragEnd: () => announcements.end, + }, }; }, [announcements]); return ( From af40f6046ad3a12f1e17548557a0f54d8988cd93 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:18:52 +0100 Subject: [PATCH 08/19] fix: improve return value to be more explicit --- .../src/components/DraggableDialog/DraggableDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index ca33dc8f..36e9ae5a 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -57,7 +57,7 @@ export const DraggableDialog: React.FC = (props) => { const accessibilityProps = React.useMemo(() => { if (!announcements) { - return; + return undefined; } return { From efc4535782ccb5cc3d41df1dabca67225d893807 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:22:08 +0100 Subject: [PATCH 09/19] fix: undo reordering --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 88bca2b4..30950065 100644 --- a/package.json +++ b/package.json @@ -58,22 +58,22 @@ "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", "beachball": "^2.33.2", + "eslint": "~8.15.0", "eslint-config-prettier": "8.1.0", "eslint-plugin-import": "^2.27.5", - "eslint": "~8.15.0", - "jest-environment-jsdom": "^29.4.1", "jest": "^29.4.1", + "jest-environment-jsdom": "^29.4.1", "jsonc-eslint-parser": "^2.1.0", "nx": "16.1.4", "parsel-js": "^1.1.2", "patch-package": "^7.0.0", "prettier": "^2.6.2", + "react": "^18.2.0", "react-dom": "^18.2.0", "react-frame-component": "^5.2.6", "react-shadow": "^20.3.0", "react-virtualized-auto-sizer": "^1.0.20", "react-window": "^1.8.9", - "react": "^18.2.0", "semver": "^7.5.2", "stylelint": "^15.10.3", "syncpack": "^9.8.6", From 67397b0f15f8b9f9ddbdf73de9d7c17b30dbe031 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:23:02 +0100 Subject: [PATCH 10/19] fix: undo reordering --- packages/react-draggable-dialog/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-draggable-dialog/package.json b/packages/react-draggable-dialog/package.json index f1802922..df53d8fe 100644 --- a/packages/react-draggable-dialog/package.json +++ b/packages/react-draggable-dialog/package.json @@ -9,9 +9,9 @@ }, "peerDependencies": { "@fluentui/react-components": ">=9.35.1 <10.0.0", - "@types/react-dom": ">=16.8.0 <19.0.0", "@types/react": ">=16.8.0 <19.0.0", - "react-dom": ">=16.8.0 <19.0.0", + "@types/react-dom": ">=16.8.0 <19.0.0", "react": ">=16.8.0 <19.0.0" + "react-dom": ">=16.8.0 <19.0.0", } } From a76564a3b3e6a8fda0933986d317727acc314ea9 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:27:08 +0100 Subject: [PATCH 11/19] fix: expose types alongside with components --- packages/react-draggable-dialog/src/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/react-draggable-dialog/src/index.ts b/packages/react-draggable-dialog/src/index.ts index 60d94d61..3d4a0c1e 100644 --- a/packages/react-draggable-dialog/src/index.ts +++ b/packages/react-draggable-dialog/src/index.ts @@ -1,3 +1,12 @@ +export { DraggableDialog } from './components/DraggableDialog'; export { DraggableDialogHandle } from './components/DraggableDialogHandle'; export { DraggableDialogSurface } from './components/DraggableDialogSurface'; -export { DraggableDialog } from './components/DraggableDialog'; + +export type { + DraggableDialogProps, + DraggableMargin, + DraggableMarginAxis, + DraggableMarginViewport, +} from './components/DraggableDialog/DraggableDialog.types'; +export type { DraggableDialogHandleProps } from './components/DraggableDialogHandle/DraggableDialogHandle.types'; +export type { DraggableDialogSurfaceProps } from './components/DraggableDialogSurface/DraggableDialogSurface.types'; From 765c65e61566401f371da42abb0be324da56a54e Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:27:54 +0100 Subject: [PATCH 12/19] fix: remove unnecessary file and merge it with context --- .../DraggableDialogHandle/DraggableDialogHandle.tsx | 4 ++-- .../DraggableDialogSurface.tsx | 4 ++-- .../src/contexts/DraggableDialogContext.ts | 13 ++++++++++--- .../src/hooks/useDraggableDialog.ts | 5 ----- 4 files changed, 14 insertions(+), 12 deletions(-) delete mode 100644 packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx index 7a51d130..94855ee7 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.tsx @@ -2,16 +2,16 @@ import * as React from 'react'; import { useDraggable } from '@dnd-kit/core'; import { mergeClasses } from '@fluentui/react-components'; -import { useDraggableDialog } from '../../hooks/useDraggableDialog'; import { DraggableDialogHandleProps } from './DraggableDialogHandle.types'; import { useStyles } from './DraggableDialogHandle.styles'; +import { useDraggableDialogState } from '../../contexts/DraggableDialogContext'; export const DraggableDialogHandle: React.FC = ({ children, className, }) => { const styles = useStyles(); - const { id } = useDraggableDialog(); + const { id } = useDraggableDialogState(); const { setActivatorNodeRef, attributes, listeners } = useDraggable({ id, }); diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx index e62bbcfd..640b8a0b 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.tsx @@ -9,7 +9,7 @@ import { import { DraggableDialogSurfaceProps } from './DraggableDialogSurface.types'; import { useStyles } from './DraggableDialogSurface.styles'; -import { useDraggableDialog } from '../../hooks/useDraggableDialog'; +import { useDraggableDialogState } from '../../contexts/DraggableDialogContext'; export const DraggableDialogSurface: React.FC = ({ children, @@ -22,7 +22,7 @@ export const DraggableDialogSurface: React.FC = ({ hasBeenDragged, isDragging, position: { x, y }, - } = useDraggableDialog(); + } = useDraggableDialogState(); const { setNodeRef, transform } = useDraggable({ id, }); diff --git a/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts index 0d72ba10..23b113a2 100644 --- a/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts +++ b/packages/react-draggable-dialog/src/contexts/DraggableDialogContext.ts @@ -17,8 +17,15 @@ const draggableDialogContext = React.createContext( draggableDialogContextDefaultValue ); -export const useDraggableDialogContext = () => - React.useContext(draggableDialogContext) ?? - draggableDialogContextDefaultValue; +export const useDraggableDialogContext = () => { + return ( + React.useContext(draggableDialogContext) ?? + draggableDialogContextDefaultValue + ); +}; + +export const useDraggableDialogState = () => { + return useDraggableDialogContext(); +}; export const DraggableDialogContextProvider = draggableDialogContext.Provider; diff --git a/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts b/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts deleted file mode 100644 index 1a082537..00000000 --- a/packages/react-draggable-dialog/src/hooks/useDraggableDialog.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useDraggableDialogContext } from '../contexts/DraggableDialogContext'; - -export const useDraggableDialog = () => { - return useDraggableDialogContext(); -}; From ae92b6f296cc8cf8be00302144f32a2550f11e14 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:29:46 +0100 Subject: [PATCH 13/19] fix: move const outside of function call --- .../src/components/DraggableDialog/utils.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts index eb5111e1..f25b75e4 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts @@ -35,15 +35,16 @@ export const restrictToMarginModifier: RestrictToMarginModifier = }); }; +const defaultMargin = { + top: 0, + end: 0, + bottom: 0, + start: 0, +}; + export const getParsedDraggableMargin = ( margin: DraggableMargin | undefined ): Required => { - const defaultMargin = { - top: 0, - end: 0, - bottom: 0, - start: 0, - }; if (!margin) { return defaultMargin; From db4cd0f0d1b1537254428957074edea8b63d85b2 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:35:08 +0100 Subject: [PATCH 14/19] fix: use correct types for children element for React --- .../DraggableDialogHandle/DraggableDialogHandle.types.ts | 2 +- .../DraggableDialogSurface.types.ts | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts index 97bd08fd..bd7beed5 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts +++ b/packages/react-draggable-dialog/src/components/DraggableDialogHandle/DraggableDialogHandle.types.ts @@ -6,5 +6,5 @@ export type DraggableDialogHandleProps = React.HTMLAttributes & { * Only one child is allowed. * @required */ - children: JSX.Element; + children: React.ReactElement; }; diff --git a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts index 3b353df3..bd5cdf0b 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts +++ b/packages/react-draggable-dialog/src/components/DraggableDialogSurface/DraggableDialogSurface.types.ts @@ -1,10 +1,3 @@ import { DialogSurfaceProps } from '@fluentui/react-components'; -export type DraggableDialogSurfaceProps = DialogSurfaceProps & { - /** - * The element that will be draggable. - * Only one child is allowed. - * @required - */ - children: JSX.Element; -}; +export type DraggableDialogSurfaceProps = DialogSurfaceProps; From 12589f357542b110659b456b6f36f8617cc1c627 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:45:55 +0100 Subject: [PATCH 15/19] fix: make announcements props optional in case they are undefined --- .../src/components/DraggableDialog/DraggableDialog.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index 36e9ae5a..95766954 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -62,8 +62,12 @@ export const DraggableDialog: React.FC = (props) => { return { announcements: { - onDragStart: () => announcements.start, - onDragEnd: () => announcements.end, + ...(announcements.start && { + onDragStart: () => announcements.start, + }), + ...(announcements.end && { + onDragEnd: () => announcements.end, + }), }, }; }, [announcements]); From 79b917c802094fac1cc06deb7291e3bfe6d89c32 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:49:41 +0100 Subject: [PATCH 16/19] fix: make announcements props optional in case they are undefined --- .../DraggableDialog/DraggableDialog.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index 95766954..57e9a005 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -8,6 +8,7 @@ import { useSensors, DragEndEvent, PointerSensor, + Announcements, } from '@dnd-kit/core'; import { Dialog, useId } from '@fluentui/react-components'; @@ -60,15 +61,18 @@ export const DraggableDialog: React.FC = (props) => { return undefined; } + const announcementsProps: Partial = {} + + if (announcements.start) { + announcementsProps.onDragStart = () => announcements.start; + } + + if (announcements.end) { + announcementsProps.onDragEnd = () => announcements.end; + } + return { - announcements: { - ...(announcements.start && { - onDragStart: () => announcements.start, - }), - ...(announcements.end && { - onDragEnd: () => announcements.end, - }), - }, + announcements: announcementsProps, }; }, [announcements]); From f4ce87ae87b002485f192c340efcc016efb3ded5 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:50:41 +0100 Subject: [PATCH 17/19] fix: simplify logic --- .../components/DraggableDialog/DraggableDialog.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index 57e9a005..e37deaf1 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -57,18 +57,20 @@ export const DraggableDialog: React.FC = (props) => { }, [margin, keepInViewport]); const accessibilityProps = React.useMemo(() => { - if (!announcements) { + const { start, end } = announcements || {}; + + if (!announcements || (!start && !end)) { return undefined; } const announcementsProps: Partial = {} - if (announcements.start) { - announcementsProps.onDragStart = () => announcements.start; + if (start) { + announcementsProps.onDragStart = () => start; } - if (announcements.end) { - announcementsProps.onDragEnd = () => announcements.end; + if (end) { + announcementsProps.onDragEnd = () => end; } return { From 3a0d0ca1d4bda39fc2d96440ea5ff6d4e2b22c77 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:56:56 +0100 Subject: [PATCH 18/19] fix: code formatting --- packages/react-draggable-dialog/package.json | 4 ++-- .../src/components/DraggableDialog/utils.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-draggable-dialog/package.json b/packages/react-draggable-dialog/package.json index df53d8fe..fbc90c1e 100644 --- a/packages/react-draggable-dialog/package.json +++ b/packages/react-draggable-dialog/package.json @@ -11,7 +11,7 @@ "@fluentui/react-components": ">=9.35.1 <10.0.0", "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0" - "react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.8.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0" } } diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts index f25b75e4..46228c9a 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/utils.ts @@ -45,7 +45,6 @@ const defaultMargin = { export const getParsedDraggableMargin = ( margin: DraggableMargin | undefined ): Required => { - if (!margin) { return defaultMargin; } From 037e788673b810ec55b27f4b8ba61a13d4f90592 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 31 Jan 2024 14:57:13 +0100 Subject: [PATCH 19/19] fix: create memo to not recreate object every time --- .../DraggableDialog/DraggableDialog.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx index e37deaf1..bee1f8aa 100644 --- a/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx +++ b/packages/react-draggable-dialog/src/components/DraggableDialog/DraggableDialog.tsx @@ -39,6 +39,16 @@ export const DraggableDialog: React.FC = (props) => { keyboardSensor ); + const contextValue = React.useMemo( + () => ({ + isDragging, + hasBeenDragged, + position, + id, + }), + [isDragging, hasBeenDragged, position, id] + ); + const onDragEnd = React.useCallback((event: DragEndEvent) => { setPosition(({ x, y }) => ({ x: x + event.delta.x, @@ -63,7 +73,7 @@ export const DraggableDialog: React.FC = (props) => { return undefined; } - const announcementsProps: Partial = {} + const announcementsProps: Partial = {}; if (start) { announcementsProps.onDragStart = () => start; @@ -86,14 +96,7 @@ export const DraggableDialog: React.FC = (props) => { onDragStart={onDragStart} onDragEnd={onDragEnd} > - +