Skip to content

Commit 9565259

Browse files
#217 Add dangerouslyBypassFocusLock prop on Modal which solves focus lock problems
Add dangerouslyBypassFocusLockprop on Modal which solves focus lock problems
1 parent 3aeb6e1 commit 9565259

File tree

3 files changed

+122
-3
lines changed

3 files changed

+122
-3
lines changed

libs/alert/src/Modal.tsx

+16-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export type ModalProps<T = unknown> = {
3939
* Icon shown on the left side of the modal title.
4040
*/
4141
icon?: React.ReactNode;
42+
43+
/**
44+
* Bypasses focus lock on modal element.
45+
*/
46+
dangerouslyBypassFocusLock?: boolean;
4247
} & UseModal<T> &
4348
ModalTokensProps;
4449

@@ -145,7 +150,16 @@ function useModalContext() {
145150
return context;
146151
}
147152

148-
function Modal<T = unknown>({ isOpen, onClose, state, icon, children, canDismiss = true, ...props }: ModalProps<T>) {
153+
function Modal<T = unknown>({
154+
isOpen,
155+
onClose,
156+
state,
157+
icon,
158+
children,
159+
canDismiss = true,
160+
dangerouslyBypassFocusLock = false,
161+
...props
162+
}: ModalProps<T>) {
149163
const tokens = useTokens("Modal", props.tokens);
150164

151165
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -178,7 +192,7 @@ function Modal<T = unknown>({ isOpen, onClose, state, icon, children, canDismiss
178192
);
179193

180194
return (
181-
<DialogOverlay isOpen={isOpen} onDismiss={onDismiss}>
195+
<DialogOverlay isOpen={isOpen} onDismiss={onDismiss} dangerouslyBypassFocusLock={dangerouslyBypassFocusLock}>
182196
<ModalContext.Provider value={modalContext}>
183197
<div className={baseClassName}>
184198
<div className={tokens.Container.Overlay.outer}>

storybook/src/alert/Modal.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ When modals become too long for the user’s viewport or device, they `scroll` i
2727
<Story id="component-library-alert-modal--with-scrollbar" />
2828
</Canvas>
2929

30+
When action on modal opens another popup which needs to get focused, modal will keep focus and will not let for example input element to get focus.
31+
In this case you should use `dangerouslyBypassFocusLock` prop.
32+
33+
<Canvas>
34+
<Story id="component-library-alert-modal--with-bypassed-focus-lock" />
35+
</Canvas>
36+
3037
## Modal With State
3138

3239
`State` prop is used for fetching or storing the **state** of the modal.

storybook/src/alert/Modal.stories.tsx

+99-1
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import * as React from "react";
2020
import { withDesign } from "storybook-addon-designs";
2121

2222
import { Modal, useModal } from "@tiller-ds/alert";
23-
import { Button } from "@tiller-ds/core";
23+
import { Button, Typography } from "@tiller-ds/core";
2424
import { DescriptionList } from "@tiller-ds/data-display";
2525
import { Icon } from "@tiller-ds/icons";
2626
import { Intl } from "@tiller-ds/intl";
2727

2828
import mdx from "./Modal.mdx";
2929
import { beautifySource } from "../utils";
30+
import { Input } from "@tiller-ds/form-elements";
31+
import { useState } from "react";
3032

3133
export default {
3234
title: "Component Library/Alert/Modal",
@@ -228,3 +230,99 @@ export const WithScrollbar = () => {
228230
</>
229231
);
230232
};
233+
234+
export const WithBypassedFocusLock = () => {
235+
// incl-code
236+
const modal = useModal();
237+
const modalWithBypassedFocusLock = useModal();
238+
const [showPopup, setShowPopup] = useState(false);
239+
const [showBypassedFocusLockPopup, setShowBypassedFocusLockPopup] = useState(false);
240+
241+
return (
242+
<>
243+
<div className="flex space-x-2">
244+
<Button id="open-button" onClick={modal.onOpen}>
245+
<Intl name="Normal" />
246+
</Button>
247+
<Button id="open-button" onClick={modalWithBypassedFocusLock.onOpen}>
248+
<Intl name="Bypassed focus lock" />
249+
</Button>
250+
</div>
251+
252+
<Modal {...modal} icon={<Modal.Icon icon={<Icon type="check" variant="bold" />} className="text-white" />}>
253+
<Modal.Content title={<Intl name="modalTitle" />}>
254+
<Intl name="modalContent" />
255+
</Modal.Content>
256+
257+
<Modal.Footer>
258+
<Button variant="filled" color="success" onClick={() => setShowPopup(true)}>
259+
<Intl name="Open popup" />
260+
</Button>
261+
<Button variant="text" color="white" onClick={modal.onClose}>
262+
<Intl name="cancel" />
263+
</Button>
264+
</Modal.Footer>
265+
</Modal>
266+
267+
<Modal
268+
{...modalWithBypassedFocusLock}
269+
icon={<Modal.Icon icon={<Icon type="check" variant="bold" />} className="text-white" />}
270+
dangerouslyBypassFocusLock={true}
271+
>
272+
<Modal.Content title={<Intl name="modalTitle" />}>
273+
<Intl name="modalContent" />
274+
</Modal.Content>
275+
276+
<Modal.Footer>
277+
<Button variant="filled" color="success" onClick={() => setShowBypassedFocusLockPopup(true)}>
278+
<Intl name="Open popup" />
279+
</Button>
280+
<Button variant="text" color="white" onClick={modalWithBypassedFocusLock.onClose}>
281+
<Intl name="cancel" />
282+
</Button>
283+
</Modal.Footer>
284+
</Modal>
285+
286+
<Popup
287+
text="Without this option, focus is locked on 'first' modal."
288+
isOpen={showPopup}
289+
setIsOpen={setShowPopup}
290+
/>
291+
<Popup
292+
text="With bypassed focus lock, input field can get focused."
293+
isOpen={showBypassedFocusLockPopup}
294+
setIsOpen={setShowBypassedFocusLockPopup}
295+
/>
296+
</>
297+
);
298+
};
299+
300+
type PopupProps = {
301+
text: string;
302+
isOpen: boolean;
303+
setIsOpen: (isOpen: boolean) => void;
304+
};
305+
306+
function Popup({ text, isOpen, setIsOpen }: PopupProps) {
307+
return isOpen ? (
308+
<div className="flex items-center justify-center z-[10000000000] fixed inset-0">
309+
<div className="rounded-lg shadow-lg bg-white outline-none focus:outline-none">
310+
<div className="flex items-start justify-between p-5 border-b border-solid border-blueGray-200 rounded-t">
311+
<h3 className="text-2xl font-semibold">Popup with input fields</h3>
312+
<button
313+
className="p-1 ml-auto bg-transparent border-0 text-black text-3xl leading-none font-bold outline-none focus:outline-none"
314+
onClick={() => setIsOpen(false)}
315+
>
316+
<span className="">×</span>
317+
</button>
318+
</div>
319+
<div className="flex flex-col items-start p-5 space-y-4">
320+
<Typography>{text}</Typography>
321+
<Input name="test" />
322+
</div>
323+
</div>
324+
</div>
325+
) : (
326+
<></>
327+
);
328+
}

0 commit comments

Comments
 (0)