-
-
Notifications
You must be signed in to change notification settings - Fork 11
[1/N] feat: ModalOverlay migration (extension)
#1120
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
13ac07b
feat: `ModalOverlay` migration
kirillzyusko 99b6ee6
fix: include tokens in dev deps to successfully build projects in iso…
kirillzyusko badcc46
fix: include tokens in dev deps to successfully build projects in iso…
kirillzyusko 61e057a
fix: give up and inline the constant
kirillzyusko 0b6252a
fix: types issue
kirillzyusko 2fd397b
fix: lint
kirillzyusko 49c77bf
fix: PR comments
kirillzyusko 9dae9ab
Merge branch 'main' into feat/modal-overlay-migration
kirillzyusko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
packages/design-system-react/src/components/ModalOverlay/ModalOverlay.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { ButtonVariant } from '@metamask/design-system-shared'; | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
| import React, { useState } from 'react'; | ||
|
|
||
| import { Button } from '../Button'; | ||
|
|
||
| import { ModalOverlay } from './ModalOverlay'; | ||
| import type { ModalOverlayProps } from './ModalOverlay.types'; | ||
| import README from './README.mdx'; | ||
|
|
||
| const meta: Meta<ModalOverlayProps> = { | ||
| title: 'React Components/ModalOverlay', | ||
| component: ModalOverlay, | ||
| parameters: { | ||
| docs: { | ||
| page: README, | ||
| }, | ||
| }, | ||
| argTypes: { | ||
| className: { | ||
| control: 'text', | ||
| description: | ||
| 'Optional prop for additional CSS classes to be applied to the ModalOverlay component', | ||
| }, | ||
| onClick: { | ||
| action: 'onClick', | ||
| description: | ||
| 'Optional click handler. Useful when used directly without Modal context to dismiss content rendered above the overlay.', | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<ModalOverlayProps>; | ||
|
|
||
| export const Default: Story = { | ||
| args: {}, | ||
| render: (args) => <ModalOverlay {...args} />, | ||
| }; | ||
|
|
||
| export const OnClick: Story = { | ||
| render: (args) => { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| return ( | ||
| <> | ||
| <Button variant={ButtonVariant.Primary} onClick={() => setIsOpen(true)}> | ||
| Show modal overlay | ||
| </Button> | ||
| {isOpen && <ModalOverlay {...args} onClick={() => setIsOpen(false)} />} | ||
| </> | ||
| ); | ||
| }, | ||
| }; |
69 changes: 69 additions & 0 deletions
69
packages/design-system-react/src/components/ModalOverlay/ModalOverlay.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { fireEvent, render, screen } from '@testing-library/react'; | ||
| import React, { createRef } from 'react'; | ||
|
|
||
| import { ModalOverlay } from './ModalOverlay'; | ||
|
|
||
| describe('ModalOverlay', () => { | ||
| it('renders without crashing', () => { | ||
| render(<ModalOverlay data-testid="modal-overlay" />); | ||
| expect(screen.getByTestId('modal-overlay')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('applies overlay positioning, z-index, and motion-safe fade-in classes', () => { | ||
| render(<ModalOverlay data-testid="modal-overlay" />); | ||
| expect(screen.getByTestId('modal-overlay')).toHaveClass( | ||
| 'fixed', | ||
| 'inset-0', | ||
| 'z-[1050]', | ||
| 'motion-safe:animate-fade-in', | ||
| ); | ||
| }); | ||
|
|
||
| it('applies the overlay-default background color', () => { | ||
| render(<ModalOverlay data-testid="modal-overlay" />); | ||
| expect(screen.getByTestId('modal-overlay')).toHaveClass( | ||
| 'bg-overlay-default', | ||
| ); | ||
| }); | ||
|
|
||
| it('marks the overlay as decorative for assistive tech', () => { | ||
| render(<ModalOverlay data-testid="modal-overlay" />); | ||
| expect(screen.getByTestId('modal-overlay')).toHaveAttribute( | ||
| 'aria-hidden', | ||
| 'true', | ||
| ); | ||
| }); | ||
|
|
||
| it('merges custom className alongside default classes', () => { | ||
| render(<ModalOverlay data-testid="modal-overlay" className="opacity-50" />); | ||
| const overlay = screen.getByTestId('modal-overlay'); | ||
| expect(overlay).toHaveClass('opacity-50'); | ||
| expect(overlay).toHaveClass('fixed', 'inset-0'); | ||
| }); | ||
|
|
||
| it('fires the onClick handler when clicked', () => { | ||
| const handleClick = jest.fn(); | ||
| render(<ModalOverlay data-testid="modal-overlay" onClick={handleClick} />); | ||
| fireEvent.click(screen.getByTestId('modal-overlay')); | ||
| expect(handleClick).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('forwards ref to the underlying element', () => { | ||
| const ref = createRef<HTMLDivElement>(); | ||
| render(<ModalOverlay ref={ref} data-testid="modal-overlay" />); | ||
| expect(ref.current).toBe(screen.getByTestId('modal-overlay')); | ||
| }); | ||
|
|
||
| it('forwards arbitrary HTML attributes to the underlying element', () => { | ||
| render( | ||
| <ModalOverlay | ||
| data-testid="modal-overlay" | ||
| id="overlay" | ||
| role="presentation" | ||
| />, | ||
| ); | ||
| const overlay = screen.getByTestId('modal-overlay'); | ||
| expect(overlay).toHaveAttribute('id', 'overlay'); | ||
| expect(overlay).toHaveAttribute('role', 'presentation'); | ||
| }); | ||
| }); |
23 changes: 23 additions & 0 deletions
23
packages/design-system-react/src/components/ModalOverlay/ModalOverlay.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import React, { forwardRef } from 'react'; | ||
|
|
||
| import { twMerge } from '../../utils/tw-merge'; | ||
| import { Box, BoxBackgroundColor } from '../Box'; | ||
|
|
||
| import type { ModalOverlayProps } from './ModalOverlay.types'; | ||
|
|
||
| export const ModalOverlay = forwardRef<HTMLDivElement, ModalOverlayProps>( | ||
| ({ className, ...props }, ref) => ( | ||
| <Box | ||
| ref={ref} | ||
| backgroundColor={BoxBackgroundColor.OverlayDefault} | ||
| aria-hidden="true" | ||
| className={twMerge( | ||
| 'fixed inset-0 z-[1050] motion-safe:animate-fade-in', | ||
| className, | ||
| )} | ||
| {...props} | ||
| /> | ||
| ), | ||
| ); | ||
|
|
||
| ModalOverlay.displayName = 'ModalOverlay'; |
9 changes: 9 additions & 0 deletions
9
packages/design-system-react/src/components/ModalOverlay/ModalOverlay.types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import type { ComponentProps } from 'react'; | ||
|
|
||
| export type ModalOverlayProps = ComponentProps<'div'> & { | ||
| /** | ||
| * Optional prop for additional CSS classes to be applied to the ModalOverlay component. | ||
| * These classes will be merged with the component's default classes using twMerge. | ||
| */ | ||
| className?: string; | ||
| }; |
84 changes: 84 additions & 0 deletions
84
packages/design-system-react/src/components/ModalOverlay/README.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { Controls, Canvas } from '@storybook/addon-docs/blocks'; | ||
|
|
||
| import * as ModalOverlayStories from './ModalOverlay.stories'; | ||
|
|
||
| # ModalOverlay | ||
|
|
||
| `ModalOverlay` is a transparent backdrop that covers the entire viewport. Use it to dim and isolate the page behind a modal or dialog. The component is purely presentational; pair it with `ModalContent` and the rest of the modal stack when building a full modal experience. | ||
|
|
||
| ```tsx | ||
| import { ModalOverlay } from '@metamask/design-system-react'; | ||
|
|
||
| <ModalOverlay onClick={handleClose} />; | ||
| ``` | ||
|
|
||
| <Canvas of={ModalOverlayStories.Default} /> | ||
|
|
||
| ## Props | ||
|
|
||
| ### `onClick` | ||
|
|
||
| Optional click handler invoked when the overlay is clicked. Useful for closing the modal when the user clicks outside its content. Not necessary when used inside `Modal` with `closeOnClickOutside` enabled. | ||
|
|
||
| <table> | ||
| <thead> | ||
| <tr> | ||
| <th align="left">TYPE</th> | ||
| <th align="left">REQUIRED</th> | ||
| <th align="left">DEFAULT</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr> | ||
| <td align="left"> | ||
| <code>(event: MouseEvent<HTMLDivElement>) => void</code> | ||
| </td> | ||
| <td align="left">No</td> | ||
| <td align="left"> | ||
| <code>undefined</code> | ||
| </td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
|
|
||
| <Canvas of={ModalOverlayStories.OnClick} /> | ||
|
|
||
| ### `className` | ||
|
|
||
| Use the `className` prop to add custom CSS classes to the component. These classes will be merged with the component's default classes using `twMerge`, allowing you to: | ||
|
|
||
| - Add new styles that don't exist in the default component | ||
| - Override the component's default styles when needed | ||
|
|
||
| <table> | ||
| <thead> | ||
| <tr> | ||
| <th align="left">TYPE</th> | ||
| <th align="left">REQUIRED</th> | ||
| <th align="left">DEFAULT</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr> | ||
| <td align="left"> | ||
| <code>string</code> | ||
| </td> | ||
| <td align="left">No</td> | ||
| <td align="left"> | ||
| <code>undefined</code> | ||
| </td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
|
|
||
| ## Component API | ||
|
|
||
| <Controls of={ModalOverlayStories.Default} /> | ||
|
|
||
| ## Migration Guide | ||
|
|
||
| Migrating from `ui/components/component-library/modal-overlay` in MetaMask Extension? See the [ModalOverlay Migration Guide](../../../MIGRATION.md#modaloverlay-component) for prop mappings, before/after examples, and breaking changes. | ||
|
|
||
| ## References | ||
|
|
||
| [MetaMask Design System Guides](https://www.notion.so/MetaMask-Design-System-Guides-Design-f86ecc914d6b4eb6873a122b83c12940) | ||
2 changes: 2 additions & 0 deletions
2
packages/design-system-react/src/components/ModalOverlay/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { ModalOverlay } from './ModalOverlay'; | ||
| export type { ModalOverlayProps } from './ModalOverlay.types'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocking: This README links to
../../../MIGRATION.md#modaloverlay-component, butpackages/design-system-react/MIGRATION.mddoes not have aModalOverlaysection yet. Since this PR is part of the extension migration flow, can we add theModalOverlaymigration entry before merging so consumers have the actual prop mapping / before-after examples?