-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Modal, Dialog and AlertDialog components (#481)
* scaffolding Dialog component * scaffolding Modal component * boilerplate stories * modal dialog example boilerplate * roughing out Dialog structure * fleshing out Modal/Dialog structure and styling * building out example Modal Dialog on vite * close button behaviour * overlay animation * forking AlertDialog component * alert dialog variants and theming * expose and set ARIA role * set default role to dialog instead of alertdialog * import modal props and update styling * stripping Dialog down to the studs * make left icon hideable * make close button toggleable on Dialog and AlertDialog * expose size controls for Modal container * simplifying generic Dialog structure * close button positioning in generic Dialog * sketching out stories * working on generic Dialog example * clean up Modal example * finishing up Modal and Dialog styling and behaviour * simplifying modal and dialog props and styling * cleaning up examples on vite * fleshing out stories for Modal, Dialog and AlertDialog * WIP dialogs docs * add AlertDialog docs and expand Dialogs docs * Dialog/Alert Dialog docs * remove redundant prop declaration from Dialog * Use ReactAriaComponents ModalOverlayProps * simplify modal structure and styling of modaloverlay * add width and z-index to Modal CSS * Add passing test for Modal * Add DialogRenderProps type information to Dialog to make Modal test pass * Add passing test for Dialog * Remove unused React import from AlertDialog, reorder SVG imports * Export DialogRenderProps interface from Dialog, use in AlertDialog * Add missing key props to kitchen sink InlineAlert buttons * Add missing label to kitchen sink TextField instance * Add passing test for AlertDialog * Dialog isCloseable defaults to true to match AlertDialog * Update Dialog tests for updated isCloseable prop default behavior * Remove non-functional onPress props from AlertDialog instances in kitchen sink app * Add key props to Button instances in AlertDialogs in kitchen sink app * Add key props to Button instances in AlertDialog stories * AlertDialogWithoutCloseButton story uses Modal isDismissable to close with click outside * Update AlertDialog interface comment for prop * Refactor AlertDialog to use children slot instead of description string * Flatten nested CSS rules for AlertDialog * Flatten nested CSS rules for Dialog * Use JS tokens for styles in Dialog stories * ModalOverlay animation added for fade-in and fade-out --------- Co-authored-by: Tyler Krys <[email protected]>
- Loading branch information
1 parent
b1a5560
commit 2b948cf
Showing
25 changed files
with
1,372 additions
and
6 deletions.
There are no files selected for viewing
This file contains 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
60 changes: 60 additions & 0 deletions
60
packages/react-components/src/components/AlertDialog/AlertDialog.css
This file contains 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,60 @@ | ||
.bcds-react-aria-AlertDialog { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
|
||
/* Variant icons */ | ||
.bcds-react-aria-AlertDialog.info .bcds-react-aria-AlertDialog--Icon { | ||
color: var(--icons-color-primary); | ||
} | ||
.bcds-react-aria-AlertDialog.confirmation .bcds-react-aria-AlertDialog--Icon { | ||
color: var(--icons-color-success); | ||
} | ||
.bcds-react-aria-AlertDialog.warning .bcds-react-aria-AlertDialog--Icon { | ||
color: var(--icons-color-warning); | ||
} | ||
.bcds-react-aria-AlertDialog.error .bcds-react-aria-AlertDialog--Icon { | ||
color: var(--icons-color-danger); | ||
} | ||
.bcds-react-aria-AlertDialog.destructive .bcds-react-aria-AlertDialog--Icon { | ||
color: var(--icons-color-danger); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog--Header { | ||
display: inline-flex; | ||
flex-direction: row; | ||
gap: var(--layout-padding-small); | ||
justify-content: space-between; | ||
padding: var(--layout-padding-medium) var(--layout-padding-large); | ||
border-bottom: var(--layout-border-width-small) solid | ||
var(--surface-color-border-default); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog--Title { | ||
flex-grow: 1; | ||
font: var(--typography-bold-h5); | ||
color: var(--typography-color-primary); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog--Icon { | ||
justify-self: flex-start; | ||
align-self: center; | ||
padding-top: var(--layout-padding-xsmall); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog--closeIcon { | ||
justify-self: flex-end; | ||
color: var(--icons-color-primary); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog--children { | ||
font: var(--typography-regular-body); | ||
color: var(--typography-color-primary); | ||
padding: var(--layout-padding-medium) var(--layout-padding-large); | ||
border-bottom: var(--layout-border-width-small) solid | ||
var(--surface-color-border-default); | ||
} | ||
|
||
.bcds-react-aria-AlertDialog > .bcds-ButtonGroup { | ||
padding: var(--layout-padding-medium) var(--layout-padding-large); | ||
} |
133 changes: 133 additions & 0 deletions
133
packages/react-components/src/components/AlertDialog/AlertDialog.test.tsx
This file contains 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,133 @@ | ||
import { fireEvent, render, screen } from "@testing-library/react"; | ||
import "@testing-library/jest-dom"; | ||
|
||
import AlertDialog from "./AlertDialog"; | ||
import Button from "../Button"; | ||
import { DialogTrigger } from "../Dialog"; | ||
import Modal from "../Modal"; | ||
|
||
describe("AlertDialog", () => { | ||
it("text within AlertDialog is not initially visible", () => { | ||
render( | ||
<DialogTrigger> | ||
<Button>Open</Button> | ||
<Modal> | ||
<AlertDialog>Lorem ipsum</AlertDialog> | ||
</Modal> | ||
</DialogTrigger> | ||
); | ||
|
||
const text = screen.queryByText(/lorem ipsum/i); | ||
expect(text).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("text within AlertDialog is visible after pressing open button", () => { | ||
render( | ||
<DialogTrigger> | ||
<Button>Open</Button> | ||
<Modal> | ||
<AlertDialog>Lorem ipsum</AlertDialog> | ||
</Modal> | ||
</DialogTrigger> | ||
); | ||
|
||
const button = screen.getByText(/open/i); | ||
fireEvent.click(button); | ||
const text = screen.queryByText(/lorem ipsum/i); | ||
expect(text).toBeInTheDocument(); | ||
}); | ||
|
||
it("with `isCloseable` set to `false`, no close button is present", () => { | ||
render( | ||
<DialogTrigger> | ||
<Button>Open</Button> | ||
<Modal> | ||
<AlertDialog isCloseable={false}>Lorem ipsum</AlertDialog> | ||
</Modal> | ||
</DialogTrigger> | ||
); | ||
|
||
const missingModalText = screen.queryByText(/lorem ipsum/i); | ||
expect(missingModalText).not.toBeInTheDocument(); | ||
|
||
const openButton = screen.getByText(/open/i); | ||
fireEvent.click(openButton); | ||
const presentModalText = screen.queryByText(/lorem ipsum/i); | ||
expect(presentModalText).toBeInTheDocument(); | ||
|
||
const closeButton = screen.queryByLabelText(/close/i); | ||
expect(closeButton).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("by default, a close button is present and pushing it closes the AlertDialog", () => { | ||
render( | ||
<DialogTrigger> | ||
<Button>Open</Button> | ||
<Modal> | ||
<AlertDialog>Lorem ipsum</AlertDialog> | ||
</Modal> | ||
</DialogTrigger> | ||
); | ||
|
||
const missingModalText = screen.queryByText(/lorem ipsum/i); | ||
expect(missingModalText).not.toBeInTheDocument(); | ||
|
||
const openButton = screen.getByText(/open/i); | ||
fireEvent.click(openButton); | ||
const presentModalText = screen.queryByText(/lorem ipsum/i); | ||
expect(presentModalText).toBeInTheDocument(); | ||
|
||
const closeButton = screen.getByLabelText(/close/i); | ||
expect(closeButton).toBeInTheDocument(); | ||
fireEvent.click(closeButton); | ||
const dismissedModalText = screen.queryByText(/lorem ipsum/i); | ||
expect(dismissedModalText).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("can be passed Button components that can be pressed when open", () => { | ||
const handleCancel = jest.fn(); | ||
const handleSubmit = jest.fn(); | ||
render( | ||
<DialogTrigger> | ||
<Button>Open</Button> | ||
<Modal> | ||
<AlertDialog | ||
buttons={[ | ||
<Button | ||
variant="secondary" | ||
onPress={handleCancel} | ||
key="secondary-button" | ||
> | ||
Cancel | ||
</Button>, | ||
<Button | ||
variant="primary" | ||
onPress={handleSubmit} | ||
key="primary-button" | ||
> | ||
Submit | ||
</Button>, | ||
]} | ||
> | ||
Lorem ipsum | ||
</AlertDialog> | ||
</Modal> | ||
</DialogTrigger> | ||
); | ||
|
||
const missingCancelButton = screen.queryByText(/cancel/i); | ||
expect(missingCancelButton).not.toBeInTheDocument(); | ||
const missingSubmitButton = screen.queryByText(/submit/i); | ||
expect(missingSubmitButton).not.toBeInTheDocument(); | ||
|
||
const openButton = screen.getByText(/open/i); | ||
fireEvent.click(openButton); | ||
|
||
const presentCancelButton = screen.getByText(/cancel/i); | ||
const presentSubmitButton = screen.getByText(/submit/i); | ||
fireEvent.click(presentCancelButton); | ||
expect(handleCancel).toHaveBeenCalledTimes(1); | ||
fireEvent.click(presentSubmitButton); | ||
expect(handleSubmit).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
112 changes: 112 additions & 0 deletions
112
packages/react-components/src/components/AlertDialog/AlertDialog.tsx
This file contains 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,112 @@ | ||
import { | ||
Dialog as ReactAriaDialog, | ||
DialogTrigger, | ||
DialogProps as ReactAriaDialogProps, | ||
} from "react-aria-components"; | ||
|
||
import Button from "../Button"; | ||
import ButtonGroup from "../ButtonGroup"; | ||
import { DialogRenderProps } from "../Dialog/Dialog"; | ||
import SvgCheckCircleIcon from "../Icons/SvgCheckCircleIcon"; | ||
import SvgCloseIcon from "../Icons/SvgCloseIcon"; | ||
import SvgExclamationCircleIcon from "../Icons/SvgExclamationCircleIcon"; | ||
import SvgExclamationIcon from "../Icons/SvgExclamationIcon"; | ||
import SvgInfoIcon from "../Icons/SvgInfoIcon"; | ||
|
||
import "./AlertDialog.css"; | ||
|
||
export interface AlertDialogProps extends ReactAriaDialogProps { | ||
/* Dialog theme */ | ||
variant?: "info" | "confirmation" | "warning" | "error" | "destructive"; | ||
/* Dialog title */ | ||
title?: string; | ||
/* Show or hide left icon */ | ||
isIconHidden?: boolean; | ||
/* Show or hide close button */ | ||
isCloseable?: boolean; | ||
/* Array of Button components */ | ||
buttons?: React.ReactNode; | ||
} | ||
|
||
/* Sets correct left icon for selected variant */ | ||
function getIcon(variant: string) { | ||
switch (variant) { | ||
case "info": | ||
return <SvgInfoIcon />; | ||
case "confirmation": | ||
return <SvgCheckCircleIcon />; | ||
case "warning": | ||
return <SvgExclamationIcon />; | ||
case "error": | ||
return <SvgExclamationCircleIcon />; | ||
case "destructive": | ||
return <SvgExclamationCircleIcon />; | ||
default: | ||
return; | ||
} | ||
} | ||
|
||
export default function AlertDialog({ | ||
children, | ||
variant = "info", | ||
role = "dialog", | ||
title, | ||
isCloseable = true, | ||
isIconHidden = false, | ||
buttons, | ||
...props | ||
}: AlertDialogProps) { | ||
return ( | ||
<ReactAriaDialog | ||
className={`bcds-react-aria-AlertDialog ${variant}`} | ||
role={role} | ||
{...props} | ||
> | ||
{({ close }: DialogRenderProps) => ( | ||
<> | ||
<div className="bcds-react-aria-AlertDialog--Header"> | ||
{!isIconHidden && ( | ||
<div className="bcds-react-aria-AlertDialog--Icon"> | ||
{getIcon(variant)} | ||
</div> | ||
)} | ||
{title && ( | ||
<div className="bcds-react-aria-AlertDialog--Title" slot="title"> | ||
{title} | ||
</div> | ||
)} | ||
{isCloseable && ( | ||
<div className="bcds-react-aria-AlertDialog--closeIcon"> | ||
<Button | ||
variant="tertiary" | ||
isIconButton | ||
size="small" | ||
aria-label="Close" | ||
type="button" | ||
onPress={close} | ||
> | ||
<SvgCloseIcon /> | ||
</Button> | ||
</div> | ||
)} | ||
</div> | ||
{children && ( | ||
<div | ||
className="bcds-react-aria-AlertDialog--children" | ||
slot="children" | ||
> | ||
<>{children}</> | ||
</div> | ||
)} | ||
{buttons && ( | ||
<ButtonGroup alignment="end" orientation="horizontal"> | ||
{buttons} | ||
</ButtonGroup> | ||
)} | ||
</> | ||
)} | ||
</ReactAriaDialog> | ||
); | ||
} | ||
|
||
export { DialogTrigger }; |
2 changes: 2 additions & 0 deletions
2
packages/react-components/src/components/AlertDialog/index.ts
This file contains 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 { default, DialogTrigger } from "./AlertDialog"; | ||
export type { AlertDialogProps } from "./AlertDialog"; |
22 changes: 22 additions & 0 deletions
22
packages/react-components/src/components/Dialog/Dialog.css
This file contains 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,22 @@ | ||
.bcds-react-aria-Dialog { | ||
display: flex; | ||
flex-direction: column; | ||
min-height: var(--layout-margin-xxxlarge); | ||
} | ||
|
||
.bcds-react-aria-Dialog--Container { | ||
position: relative; | ||
} | ||
|
||
/* Close icon */ | ||
.bcds-react-aria-Dialog--closeIcon { | ||
color: var(--icons-color-primary); | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
padding: var(--layout-padding-small); | ||
} | ||
.bcds-react-aria-Dialog--closeIcon > svg { | ||
min-width: var(--icons-size-medium); | ||
height: var(--icons-size-medium); | ||
} |
Oops, something went wrong.