Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-draggable-dialog): create draggable dialog with handle #78

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
119914a
feat(react-draggable-dialog): create base logic for draggable dialogs
marcosmoura Jan 18, 2024
b9769ea
fix: improve style logic by grouping styles
marcosmoura Jan 18, 2024
ccb0866
docs: improve comments description
marcosmoura Jan 18, 2024
f4cde2a
Merge branch 'main' into feat/react-draggable-dialog/create-base-comp…
marcosmoura Jan 18, 2024
0b5be5e
Merge branch 'main' into feat/react-draggable-dialog/create-base-comp…
marcosmoura Jan 19, 2024
7616f31
fix: make some code cleanups and remove unecessary exportsd
marcosmoura Jan 30, 2024
cc33e33
Merge branch 'main' into feat/react-draggable-dialog/create-base-comp…
marcosmoura Jan 30, 2024
def9b7d
fix: explicit exports
marcosmoura Jan 30, 2024
41d129b
Delete packages/react-draggable-dialog/src/components/DraggableDialog…
marcosmoura Jan 31, 2024
d583361
fix: improve property handling
marcosmoura Jan 31, 2024
af40f60
fix: improve return value to be more explicit
marcosmoura Jan 31, 2024
efc4535
fix: undo reordering
marcosmoura Jan 31, 2024
67397b0
fix: undo reordering
marcosmoura Jan 31, 2024
a76564a
fix: expose types alongside with components
marcosmoura Jan 31, 2024
765c65e
fix: remove unnecessary file and merge it with context
marcosmoura Jan 31, 2024
ae92b6f
fix: move const outside of function call
marcosmoura Jan 31, 2024
225a6bb
Merge branch 'main' into feat/react-draggable-dialog/create-base-comp…
marcosmoura Jan 31, 2024
db4cd0f
fix: use correct types for children element for React
marcosmoura Jan 31, 2024
12589f3
fix: make announcements props optional in case they are undefined
marcosmoura Jan 31, 2024
79b917c
fix: make announcements props optional in case they are undefined
marcosmoura Jan 31, 2024
f4ce87a
fix: simplify logic
marcosmoura Jan 31, 2024
3a0d0ca
fix: code formatting
marcosmoura Jan 31, 2024
037e788
fix: create memo to not recreate object every time
marcosmoura Jan 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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",
Expand Down
11 changes: 8 additions & 3 deletions packages/react-draggable-dialog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
"name": "@fluentui-contrib/react-draggable-dialog",
"version": "0.0.1",
"private": true,
"dependencies": {
"@dnd-kit/core": "^6.1.0",
Hotell marked this conversation as resolved.
Show resolved Hide resolved
"@dnd-kit/utilities": "^3.2.2",
"@dnd-kit/modifiers": "^7.0.0"
layershifter marked this conversation as resolved.
Show resolved Hide resolved
},
"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"
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { DraggableDialog } from './DraggableDialog';

describe('DraggableDialog', () => {
it('should render', () => {
render(<DraggableDialog />);
render(
Copy link
Contributor

Choose a reason for hiding this comment

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

wondering what is actual purpose of this test besides not throwing any runtime error ? which is not probably the best test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not supposed to be a full test, but rather a blank file ready to receive the test code. I have them done, but I'm splitting into a separate PR to make this one smaller.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO when adding logic the test should be part of the PR, otherwise we are merging something that we cannot guarantee to work heh.

but I'll leave this up to your judgement , resolving

Copy link
Contributor

Choose a reason for hiding this comment

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

unresolving ->

quite a lot logic was added to this PR , It would be great to add these tests as well as part of it. I dont feel comfortable to approve such amount of logic without any automatic verification that it works.

<DraggableDialog open>
<div>Content</div>
</DraggableDialog>
);
});
});
Original file line number Diff line number Diff line change
@@ -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 <div className={mergeClasses(styles.root)}>Hello World!</div>;
import { DraggableDialogContextProvider } from '../../contexts/DraggableDialogContext';
import { DraggableDialogProps } from './DraggableDialog.types';
import { getParsedDraggableMargin, restrictToMarginModifier } from './utils';

export const DraggableDialog: React.FC<DraggableDialogProps> = (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;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we change this api to be more explicit ? return null ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't set as null, because that'll mean that we don't want any accessibility props. undefined will count as "we didn't pass any value".

}

return {
onDragStart: () => announcements.start,
Copy link
Contributor

Choose a reason for hiding this comment

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

this will allow invalid behaviours, if user uses announcements={} , undefined will be passed - nothing will be announced. IMO we should make those start,end props required

Copy link
Contributor Author

@marcosmoura marcosmoura Jan 31, 2024

Choose a reason for hiding this comment

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

The DndContext has sane defaults for when any values of the announcements props are not provided. I changed the way we create the announcements object, to only include the props that are passed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see . well that's the hit n miss with 3rd party libraries as some/most things are not obvious in a PR. thank you!

onDragEnd: () => announcements.end,
};
}, [announcements]);

return (
<DndContext
sensors={sensors}
modifiers={[restrictToMargin]}
Hotell marked this conversation as resolved.
Show resolved Hide resolved
accessibility={{
announcements: dndAnnouncements,
}}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<DraggableDialogContextProvider
value={{
id,
hasBeenDragged,
position,
isDragging,
}}
>
<Dialog {...props} />
</DraggableDialogContextProvider>
</DndContext>
);
};
Original file line number Diff line number Diff line change
@@ -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?: {
Copy link
Contributor

Choose a reason for hiding this comment

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

should this land on initial release ? just wondering as we are not sure if this is actually necessary ?

Copy link
Contributor Author

@marcosmoura marcosmoura Jan 30, 2024

Choose a reason for hiding this comment

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

The partners might not need announcements as an internal requirement, but they should be available and be part of an accessible Drag'n drop solution.
The announcements prop here is necessary, to translate the text that the underlying library outputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

right. I dont see any default values for these, in order to enforce a11y experiences should these be required ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if we need it, to be honest. The DndKit library has defaults for the narration, which will happen regardless of the usage of this prop. The value of this prop here is to provide a way to translate those default messages, which are in English. Partners that don't need to fulfil the narration requirement would have to provide with their defaults for those values, which they don't need.
What do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah we can keep it as is. I'd consider though to provide those defaults within our codebase to be explicit.

/**
* Announces the start of a drag action.
*/
start?: string;

/**
* Announces the end of a drag action.
*/
end?: string;
};
};
Loading
Loading