Skip to content

Commit

Permalink
🐛 Fix: Trap focus in form (#153)
Browse files Browse the repository at this point in the history
* * Add focus-trap-react dependency

* * Use FocusTrap in the EventForm component

* * Return the `context` property from the `useFloating` hook

* * Use the FloatingFocusManager component to trap the focus in the SomedayEventForm
  • Loading branch information
DiegoMutre authored Oct 14, 2024
1 parent ec50652 commit a81d455
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 100 deletions.
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"css-loader": "^6.3.0",
"dayjs": "^1.10.7",
"eslint-plugin-testing-library": "^5.0.5",
"focus-trap-react": "^10.3.0",
"html-webpack-plugin": "^5.3.2",
"http-server": "^14.1.0",
"immutability-helper": "^3.1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Key } from "ts-key-enum";
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
import { DraggableProvided } from "@hello-pangea/dnd";
import { FloatingPortal } from "@floating-ui/react";
import { FloatingFocusManager, FloatingPortal } from "@floating-ui/react";
import { Categories_Event, Schema_Event } from "@core/types/event.types";
import { Schema_GridEvent } from "@web/common/types/web.event.types";
import { SIDEBAR_OPEN_WIDTH } from "@web/views/Calendar/layout.constants";
Expand Down Expand Up @@ -42,7 +42,7 @@ export const SomedayEvent = ({
}: Props) => {
const formType =
category === Categories_Event.SOMEDAY_WEEK ? "sidebarWeek" : "sidebarMonth";
const { y, reference, floating, strategy } = useEventForm(formType);
const { y, reference, floating, strategy, context } = useEventForm(formType);

const [isFocused, setIsFocused] = useState(false);

Expand Down Expand Up @@ -104,25 +104,27 @@ export const SomedayEvent = ({

<FloatingPortal>
{shouldOpenForm && (
<StyledFloatContainer
ref={floating}
strategy={strategy}
top={y ?? 40}
left={SIDEBAR_OPEN_WIDTH}
>
<SomedayEventForm
event={event}
onClose={() => {
setShouldOpenForm(false);
onClose();
}}
onConvert={() =>
console.log("TODO: convert someday event to grid event")
}
onSubmit={onSubmit}
setEvent={setEvent}
/>
</StyledFloatContainer>
<FloatingFocusManager context={context}>
<StyledFloatContainer
ref={floating}
strategy={strategy}
top={y ?? 40}
left={SIDEBAR_OPEN_WIDTH}
>
<SomedayEventForm
event={event}
onClose={() => {
setShouldOpenForm(false);
onClose();
}}
onConvert={() =>
console.log("TODO: convert someday event to grid event")
}
onSubmit={onSubmit}
setEvent={setEvent}
/>
</StyledFloatContainer>
</FloatingFocusManager>
)}
</FloatingPortal>
</>
Expand Down
165 changes: 88 additions & 77 deletions packages/web/src/views/Forms/EventForm/EventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from "@web/common/utils/web.date.util";
import { StyledMigrateArrowInForm } from "@web/views/Calendar/components/LeftSidebar/SomedaySection/SomedayEvents/styled";
import { ID_EVENT_FORM } from "@web/common/constants/web.constants";
import FocusTrap from "focus-trap-react";

import { FormProps, SetEventFormField } from "./types";
import { DateTimeSection } from "./DateTimeSection";
Expand Down Expand Up @@ -224,84 +225,94 @@ export const EventForm: React.FC<FormProps> = ({
};

return (
<StyledEventForm
{...props}
isOpen={isFormOpen}
name={ID_EVENT_FORM}
onKeyDown={onFormKeyDown}
onMouseUp={(e) => {
e.stopPropagation();
e.preventDefault();

if (isStartDatePickerOpen) {
setIsStartDatePickerOpen(false);
}

if (isEndDatePickerOpen) {
setIsEndDatePickerOpen(false);
}
<FocusTrap
focusTrapOptions={{
// To avoid conflicting with other events, like clicking outside closes the form
allowOutsideClick: true,
}}
onMouseDown={(e) => {
e.stopPropagation();
}}
priority={priority}
role="form"
>
<StyledIconRow>
{!isDraft && (
<StyledMigrateArrowInForm
onClick={(e) => {
e.stopPropagation();
onConvert();
}}
role="button"
title="Move to sidebar"
>
{"<"}
</StyledMigrateArrowInForm>
)}
<DeleteIcon onDelete={onDeleteForm} title="Delete Event" />
</StyledIconRow>

<StyledTitleField
autoFocus
onChange={onChangeEventTextField("title")}
onKeyDown={ignoreDelete}
placeholder="Title"
role="textarea"
name="Event Title"
value={title}
/>

<PrioritySection onSetEventField={onSetEventField} priority={priority} />

<DateTimeSection
bgColor={getColor(colorNameByPriority[priority])}
event={event}
category={category}
endTime={endTime}
isEndDatePickerOpen={isEndDatePickerOpen}
isStartDatePickerOpen={isStartDatePickerOpen}
selectedEndDate={selectedEndDate}
selectedStartDate={selectedStartDate}
setEndTime={setEndTime}
setSelectedEndDate={setSelectedEndDate}
setSelectedStartDate={setSelectedStartDate}
setStartTime={setStartTime}
startTime={startTime}
setIsEndDatePickerOpen={setIsEndDatePickerOpen}
setIsStartDatePickerOpen={setIsStartDatePickerOpen}
setEvent={setEvent}
/>

<StyledDescriptionField
onChange={onChangeEventTextField("description")}
onKeyDown={ignoreDelete}
placeholder="Description"
value={event.description || ""}
/>

<SaveSection priority={priority} onSubmit={onSubmitForm} />
</StyledEventForm>
<StyledEventForm
{...props}
isOpen={isFormOpen}
name={ID_EVENT_FORM}
onKeyDown={onFormKeyDown}
onMouseUp={(e) => {
e.stopPropagation();
e.preventDefault();

if (isStartDatePickerOpen) {
setIsStartDatePickerOpen(false);
}

if (isEndDatePickerOpen) {
setIsEndDatePickerOpen(false);
}
}}
onMouseDown={(e) => {
e.stopPropagation();
}}
priority={priority}
role="form"
>
<StyledIconRow>
{!isDraft && (
<StyledMigrateArrowInForm
onClick={(e) => {
e.stopPropagation();
onConvert();
}}
role="button"
title="Move to sidebar"
>
{"<"}
</StyledMigrateArrowInForm>
)}
<DeleteIcon onDelete={onDeleteForm} title="Delete Event" />
</StyledIconRow>

<StyledTitleField
autoFocus
onChange={onChangeEventTextField("title")}
onKeyDown={ignoreDelete}
placeholder="Title"
role="textarea"
name="Event Title"
value={title}
/>

<PrioritySection
onSetEventField={onSetEventField}
priority={priority}
/>

<DateTimeSection
bgColor={getColor(colorNameByPriority[priority])}
event={event}
category={category}
endTime={endTime}
isEndDatePickerOpen={isEndDatePickerOpen}
isStartDatePickerOpen={isStartDatePickerOpen}
selectedEndDate={selectedEndDate}
selectedStartDate={selectedStartDate}
setEndTime={setEndTime}
setSelectedEndDate={setSelectedEndDate}
setSelectedStartDate={setSelectedStartDate}
setStartTime={setStartTime}
startTime={startTime}
setIsEndDatePickerOpen={setIsEndDatePickerOpen}
setIsStartDatePickerOpen={setIsStartDatePickerOpen}
setEvent={setEvent}
/>

<StyledDescriptionField
onChange={onChangeEventTextField("description")}
onKeyDown={ignoreDelete}
placeholder="Description"
value={event.description || ""}
/>

<SaveSection priority={priority} onSubmit={onSubmitForm} />
</StyledEventForm>
</FocusTrap>
);
};
3 changes: 2 additions & 1 deletion packages/web/src/views/Forms/hooks/useEventForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ export const useEventForm = (
};
}

const { x, y, reference, floating, strategy } = useFloating(options);
const { x, y, reference, floating, strategy, context } = useFloating(options);

return {
x,
y,
reference,
floating,
strategy,
context,
};
};

Expand Down
17 changes: 16 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5648,6 +5648,21 @@ fn.name@1.x.x:
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==

focus-trap-react@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-10.3.0.tgz#79e2b63459d30a2f5545cf8491a8b02c1779882e"
integrity sha512-XrCTj44uNE0clTA47y1AbIb7tM7w6+zi6WrJzb4RxRe3uAIIivkBCwlsCqe7R3vPRT/LCQzfe4+N/KjtJMQMgw==
dependencies:
focus-trap "^7.6.0"
tabbable "^6.2.0"

focus-trap@^7.6.0:
version "7.6.0"
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.6.0.tgz#7f3edab8135eaca92ab59b6e963eb5cc42ded715"
integrity sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==
dependencies:
tabbable "^6.2.0"

follow-redirects@^1.0.0, follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
Expand Down Expand Up @@ -10159,7 +10174,7 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==

tabbable@^6.0.1:
tabbable@^6.0.1, tabbable@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
Expand Down

0 comments on commit a81d455

Please sign in to comment.