Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 54 additions & 1 deletion src/modal/modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import { StoryObj, StoryFn, Meta } from "@storybook/react";

import { Button } from "../button";
import { FormControl } from "../form-control";
import { Spacer } from "../spacer";
import { Modal } from "./modal";
import {
Expand Down Expand Up @@ -225,6 +226,58 @@ export const StartAlignedBody: Story = {
},
};

const InitialFocusTemplate: StoryFn<ModalProps> = (args) => {
const [open, setOpen] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);

const handleClose = () => setOpen(false);

return (
<div>
<Button onClick={() => setOpen(true)}>Open modal</Button>
<Modal
{...args}
open={open}
onClose={handleClose}
initialFocus={inputRef}
>
<Modal.Header>Enter Your Name</Modal.Header>
<Modal.Body>
<p>Please enter your full name below:</p>
<FormControl
ref={inputRef}
type="text"
placeholder="Your full name"
componentClass="input"
/>
</Modal.Body>
<Modal.Footer>
<Button block size="large" variant="primary">
Submit
</Button>
<Spacer size="xxs" />
<Button block size="large" onClick={handleClose}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</div>
);
};

export const WithInitialFocus: Story = {
render: InitialFocusTemplate,
args: {},
parameters: {
docs: {
description: {
story:
"This example demonstrates the `initialFocus` prop. When the modal opens, focus will be set to the input field instead of the default close button. This is useful for forms or other scenarios where you want to direct the user's attention to a specific element immediately.",
},
},
},
};

export const EndAlignedFooter: Story = {
render: EndAlignedFooterTemplate,
args: {
Expand Down
42 changes: 42 additions & 0 deletions src/modal/modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,46 @@ describe("<Modal />", () => {

expect(onClose).toHaveBeenCalledTimes(1);
});

it("should focus on the specified element when `initialFocus` is provided", async () => {
const initialFocusRef = React.createRef<HTMLButtonElement>();

render(
<Modal open={true} onClose={() => {}} initialFocus={initialFocusRef}>
<Modal.Header>Lorem ipsum</Modal.Header>
<Modal.Body>
<p>Laboriosam autem non et nisi.</p>
</Modal.Body>
<Modal.Footer>
<Button ref={initialFocusRef} block size="large">
Custom Focus Button
</Button>
<Button block size="large">
Submit
</Button>
<Button block size="large">
Cancel
</Button>
</Modal.Footer>
</Modal>,
);

const dialog = await screen.findByRole("dialog", { name: "Lorem ipsum" });
expect(dialog).toBeInTheDocument();

const customFocusButton = within(dialog).getByRole("button", {
name: "Custom Focus Button",
});
expect(customFocusButton).toHaveFocus();
});

it("should focus on the close button when `initialFocus` is undefined", async () => {
setup({ open: true });

const dialog = await screen.findByRole("dialog", { name: "Lorem ipsum" });
expect(dialog).toBeInTheDocument();

const closeButton = within(dialog).getByRole("button", { name: "Close" });
expect(closeButton).toHaveFocus();
});
});
2 changes: 2 additions & 0 deletions src/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const Modal = ({
onKeyDown,
size = "medium",
variant = "default",
initialFocus,
}: ModalProps) => {
let panelClasses = PANEL_DEFAULT_CLASSES;

Expand All @@ -123,6 +124,7 @@ const Modal = ({
onClose={onClose}
className="relative z-1050 w-screen h-screen"
onKeyDown={onKeyDown}
initialFocus={initialFocus}
>
{/* The backdrop, rendered as a fixed sibling to the panel container */}
<div aria-hidden className="fixed inset-0 bg-gray-900 opacity-50" />
Expand Down
3 changes: 2 additions & 1 deletion src/modal/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode } from "react";
import { type ReactNode, type MutableRefObject } from "react";

export interface ModalProps {
children: ReactNode;
Expand All @@ -7,6 +7,7 @@ export interface ModalProps {
onKeyDown?: React.KeyboardEventHandler;
size?: "medium" | "large" | "xLarge";
variant?: "default" | "danger";
initialFocus?: MutableRefObject<HTMLElement | null>;
}

export interface HeaderProps {
Expand Down