Skip to content
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

355 feature make actionmodal context api #356

Merged
merged 3 commits into from
Aug 9, 2024
Merged
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
2 changes: 1 addition & 1 deletion docs/app/data/categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"containment": {
"name": "Containment",
"description": "Components that contain other components.",
"items": ["Confirm Modal", "Prompt Modal"]
"items": ["Confirm Modal", "Prompt Modal", "Modal"]
},
"navigation": {
"name": "Navigation",
Expand Down
30 changes: 30 additions & 0 deletions docs/app/react/modal/a11y.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
---

import {
WhenToUseAdmonition
} from '@/app/components/Admonition'
import OverviewList from '@/app/components/OverviewList'

## Use Cases

<OverviewList intro="Users should be able to:" rules={[
'Open and close a modal',
'Provide and submit other inputs if the modal is interactive, such as a text field or selectable list',
'Keep focus within the modal until it is closed',
]} />

### Interaction &amp; Style

Modals are purposefully interruptive. This means they appear in front of app content and disrupt the flow of content for users who may, for example, be using a screen reader to navigate the page.

As such, modals should be used sparingly and only to provide critical information. Less critical information should be presented in a non-blocking way within the flow of app content.

## Keyboard Navigation

| Keys | Actions |
| -------- | --------------------------------------------------------------- |
| Tab | Focus lands on the next interactive element contained in the modal, or the first element if focus is currently on the last element |
| Shift + Tab | Focus lands on the previous interactive element contained in the modal, or the last element if focus is currently on the first element |
| Space / Enter | Triggers or commits the action of the focused element |
| Esc | Closes the modal and returns focus to the element that triggered the modal |
113 changes: 113 additions & 0 deletions docs/app/react/modal/components/modal-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use client'

import { Model } from '@cerberus-design/icons'
import {
Modal,
ModalHeader,
ModalHeading,
ModalDescription,
useModal,
trapFocus,
Button,
Portal,
} from '@cerberus-design/react'
import { css } from '@cerberus/styled-system/css'
import { hstack } from '@cerberus/styled-system/patterns'

export function OverviewPreview() {
const { modalRef, show, close } = useModal()
const handleKeyDown = trapFocus(modalRef)

return (
<div>
<Button onClick={show}>Open Modal</Button>

<Portal>
<Modal onKeyDown={handleKeyDown} ref={modalRef}>
<ModalHeader>
<ModalHeading>This is a custom modal</ModalHeading>
</ModalHeader>
<ModalDescription>
This is a custom modal that is can be whatever you need.
</ModalDescription>

<div
className={hstack({
color: 'info.text.100',
mb: '10',
})}
>
<Model size="5rem" />
<Model size="5rem" />
<Model size="5rem" />
</div>

<Button usage="outlined" onClick={close}>
Close
</Button>
</Modal>
</Portal>
</div>
)
}

export function CustomPreview() {
const { modalRef, show, close } = useModal()
const handleKeyDown = trapFocus(modalRef)

return (
<div>
<Button
className={css({
bgColor: 'black',
color: 'yellow',
_hover: {
bgColor: 'yellow',
color: 'black',
},
})}
onClick={show}
>
Enter the Wu
</Button>

<Portal>
<Modal
className={css({
bgColor: 'black',
})}
onKeyDown={handleKeyDown}
ref={modalRef}
>
<ModalHeader>
<ModalHeading
className={css({
color: 'yellow !important',
})}
>
Inspectah Deck
</ModalHeading>
<ModalDescription>
Swingin&apos; through your town like your neighborhood Spider-man!
</ModalDescription>
</ModalHeader>

<Button
className={css({
borderColor: 'yellow',
color: 'yellow',
_hover: {
borderColor: 'yellow',
color: 'yellow',
},
})}
usage="outlined"
onClick={close}
>
Close
</Button>
</Modal>
</Portal>
</div>
)
}
177 changes: 177 additions & 0 deletions docs/app/react/modal/dev.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
npm: '@cerberus-design/react'
source: 'context/confirm-modal.tsx'
recipe: ''
---

import CodePreview from '@/app/components/CodePreview'
import {
NoteAdmonition,
} from '@/app/components/Admonition'
import {
OverviewPreview,
CustomPreview
} from '@/app/react/modal/components/modal-preview'

```ts
import {
Modal,
ModalHeader,
ModalHeading,
ModalDescription,
trapFocus,
useModal
} from '@cerberus-design/react'
```

## Usage

### Basic Usage

<CodePreview preview={<OverviewPreview />}>
```tsx title="some-page.tsx"
import { Model } from '@cerberus-design/icons'
import {
Modal,
ModalHeader,
ModalHeading,
ModalDescription,
Portal,
useModal,
trapFocus,
Button,
} from '@cerberus-design/react'
import { hstack } from '@cerberus/styled-system/patterns'

function OverviewPreview() {
const { modalRef, show, close } = useModal()
const handleKeyDown = trapFocus(modalRef)

return (
<div>
<Button onClick={show}>Open Modal</Button>

<Portal>
<Modal onKeyDown={handleKeyDown} ref={modalRef}>
<ModalHeader>
<ModalHeading>This is a custom modal</ModalHeading>
</ModalHeader>
<ModalDescription>
This is a custom modal that is can be whatever you need.
</ModalDescription>

<div
className={hstack({
color: 'info.text.100',
mb: '10',
})}
>
<Model size="5rem" />
<Model size="5rem" />
<Model size="5rem" />
</div>

<Button usage="outlined" onClick={close}>
Close
</Button>
</Modal>
</Portal>
</div>
)
}
```
</CodePreview>

## Customization

You can customize each Modal component like you would any other component.

<CodePreview preview={<CustomPreview />}>
```tsx title="some-page.tsx"
import { Model } from '@cerberus-design/icons'
import {
Modal,
ModalHeader,
ModalHeading,
ModalDescription,
Portal,
useModal,
trapFocus,
Button,
} from '@cerberus-design/react'
import { css } from '@cerberus/styled-system/css'

function CustomPreview() {
const { modalRef, show, close } = useModal()
const handleKeyDown = trapFocus(modalRef)

return (
<div>
<Button
className={css({
bgColor: 'black',
color: 'yellow',
_hover: {
bgColor: 'yellow',
color: 'black',
},
})}
onClick={show}
>
Enter the Wu
</Button>

<Portal>
<Modal
className={css({
bgColor: 'black',
})}
onKeyDown={handleKeyDown}
ref={modalRef}
>
<ModalHeader>
<ModalHeading
className={css({
color: 'yellow !important',
})}
>
Inspectah Deck
</ModalHeading>
<ModalDescription>
Swingin' through your town like your neighborhood Spider-man!
</ModalDescription>
</ModalHeader>

<Button
className={css({
borderColor: 'yellow',
color: 'yellow',
_hover: {
borderColor: 'yellow',
color: 'yellow',
},
})}
usage="outlined"
onClick={close}
>
Close
</Button>
</Modal>
</Portal>
</div>
)
}
```
</CodePreview>

## API

```ts showLineNumbers=false
define function Modal(props: HTMLAttributes<HTMLDialogElement>): ReactNode

define function ModalHeader(props: HTMLAttributes<HTMLDivElement>): ReactNode

define function ModalHeading(props: HTMLAttributes<HTMLParagraphElement>): ReactNode

define function ModalDescription(props: HTMLAttributes<HTMLParagraphElement>): ReactNode
````
23 changes: 23 additions & 0 deletions docs/app/react/modal/guidelines.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
---

import CodePreview from '@/app/components/CodePreview'
import {
WhenToUseAdmonition,
WhenNotToUseAdmonition,
} from '@/app/components/Admonition'
import {
OverviewPreview
} from '@/app/react/modal/components/modal-preview'

## Custom Modal Usage

<WhenToUseAdmonition description="When you need to disrupt the flow of the user experience with content." />

<CodePreview preview={<OverviewPreview />} />

### Use sparingly

Modals should be used sparingly, and only for important decisions or actions that cannot be undone. They should not be used for trivial actions or actions that can be easily undone.

For most actions, a simple **Notification** message is sufficient to inform the user of the result of their action (or allow them to "undo" it).
28 changes: 28 additions & 0 deletions docs/app/react/modal/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
heading: 'Modal'
description: 'Modals provide important prompts in a user flow.'
a11y: 'touch-target'
---

import CodePreview from '@/app/components/CodePreview'
import OverviewList from '@/app/components/OverviewList'
import {
OverviewPreview
} from '@/app/react/modal/components/modal-preview'

<OverviewList rules={[
'Use modals to make sure users act on information',
'Should be dedicated to completing a single task',
'Can also display information relevant to the task',
'Commonly used in high-risk actions like deleting progress',
]} />

## Example

<CodePreview preview={<OverviewPreview />} />

## Resources

| Name | Resource | Status |
| -------- | -------- | ---------------------------------------------------- |
| Figma | [Design Kit (Figma)](https://www.figma.com/design/ducwqOCxoxcWc3ReV3FYd8/Digital-University-Component-Library?m=auto&node-id=0-1) | Private |
Loading