Skip to content

Commit

Permalink
TreeView: Create ErrorDialog component (#2452)
Browse files Browse the repository at this point in the history
* Create TreeView.ErrorDialog component

* Fix icon size

* Add error dialog tests

* Update docs

* Create tough-lobsters-greet.md

* Remove settimeout from test
  • Loading branch information
colebemis authored Oct 20, 2022
1 parent c367b44 commit aca96c0
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-lobsters-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

TreeView: Add `TreeView.ErrorDialog` component
70 changes: 47 additions & 23 deletions docs/content/TreeView.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,42 @@ See [Storybook](https://primer.style/react/storybook?path=/story/components-tree
{/* <PropsTableSxRow /> */}
</PropsTable>

### TreeView.LeadingVisual

<PropsTable>
<PropsTableRow
name="children"
type={`| React.ReactNode
| (props: {isExpanded: boolean}) => React.ReactNode`}
/>
<PropsTableRow
name="label"
type="string"
description="Provide an accessible label for the leading visual. This is not necessary for decorative visuals"
/>
{/* <PropsTableSxRow /> */}
</PropsTable>

### TreeView.TrailingVisual

<PropsTable>
<PropsTableRow
name="children"
type={`| React.ReactNode
| (props: {isExpanded: boolean}) => React.ReactNode`}
/>
<PropsTableRow
name="label"
type="string"
description="Provide an accessible label for the trailing visual. This is not necessary for decorative visuals"
/>
{/* <PropsTableSxRow /> */}
</PropsTable>

### TreeView.DirectoryIcon

<PropsTable>{/* <PropsTableSxRow /> */}</PropsTable>

### TreeView.SubTree

<PropsTable>
Expand Down Expand Up @@ -315,42 +351,30 @@ See [Storybook](https://primer.style/react/storybook?path=/story/components-tree
<PropsTableSxRow />
</PropsTable>

### TreeView.LeadingVisual
### TreeView.ErrorDialog

<PropsTable>
<PropsTableRow
name="children"
type={`| React.ReactNode
| (props: {isExpanded: boolean}) => React.ReactNode`}
type="React.ReactNode"
required
description="The content of the dialog. This is usually a message explaining the error."
/>
<PropsTableRow
name="label"
name="title"
type="string"
description="Provide an accessible label for the leading visual. This is not necessary for decorative visuals"
/>
{/* <PropsTableSxRow /> */}
</PropsTable>

### TreeView.TrailingVisual

<PropsTable>
<PropsTableRow
name="children"
type={`| React.ReactNode
| (props: {isExpanded: boolean}) => React.ReactNode`}
defaultValue="'Error'"
description="The title of the dialog. This is usually a short description of the error."
/>
<PropsTableRow
name="label"
type="string"
description="Provide an accessible label for the trailing visual. This is not necessary for decorative visuals"
name="onRetry"
type="() => void"
description="Event handler called when the user clicks the retry button."
/>
<PropsTableRow name="onDismiss" type="() => void" description="Event handler called when the dialog is dismissed." />
{/* <PropsTableSxRow /> */}
</PropsTable>

### TreeView.DirectoryIcon

<PropsTable>{/* <PropsTableSxRow /> */}</PropsTable>

## Status

<ComponentChecklist
Expand Down
47 changes: 31 additions & 16 deletions src/TreeView/TreeView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {ActionList} from '../ActionList'
import {ActionMenu} from '../ActionMenu'
import Box from '../Box'
import {Button} from '../Button'
import {ConfirmationDialog} from '../Dialog/ConfirmationDialog'
import StyledOcticon from '../StyledOcticon'
import {SubTreeState, TreeView} from './TreeView'

Expand Down Expand Up @@ -421,6 +420,12 @@ export const AsyncSuccess: Story = args => {
<Box sx={{p: 3}}>
<nav aria-label="Files">
<TreeView aria-label="Files">
<TreeView.Item>
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Some file
</TreeView.Item>
<TreeView.Item
onExpandedChange={async isExpanded => {
if (asyncItems.length === 0 && isExpanded) {
Expand Down Expand Up @@ -449,6 +454,12 @@ export const AsyncSuccess: Story = args => {
))}
</TreeView.SubTree>
</TreeView.Item>
<TreeView.Item>
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Another file
</TreeView.Item>
</TreeView>
</nav>
</Box>
Expand Down Expand Up @@ -568,7 +579,6 @@ async function alwaysFails(responseTime: number) {

export const AsyncError: Story = args => {
const [isLoading, setIsLoading] = React.useState(false)
const [isExpanded, setIsExpanded] = React.useState(false)
const [asyncItems, setAsyncItems] = React.useState<string[]>([])
const [error, setError] = React.useState<Error | null>(null)

Expand Down Expand Up @@ -602,11 +612,14 @@ export const AsyncError: Story = args => {
<Box sx={{p: 3}}>
<nav aria-label="Files">
<TreeView aria-label="Files">
<TreeView.Item>
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Some file
</TreeView.Item>
<TreeView.Item
expanded={isExpanded}
onExpandedChange={isExpanded => {
setIsExpanded(isExpanded)

if (isExpanded) {
loadItems()
}
Expand All @@ -618,21 +631,17 @@ export const AsyncError: Story = args => {
Directory with async items
<TreeView.SubTree state={state}>
{error ? (
<ConfirmationDialog
title="Error"
onClose={gesture => {
<TreeView.ErrorDialog
onRetry={() => {
setError(null)
loadItems()
}}
onDismiss={() => {
setError(null)

if (gesture === 'confirm') {
loadItems()
} else {
setIsExpanded(false)
}
}}
confirmButtonContent="Retry"
>
{error.message}
</ConfirmationDialog>
</TreeView.ErrorDialog>
) : null}
{asyncItems.map(item => (
<TreeView.Item key={item}>
Expand All @@ -644,6 +653,12 @@ export const AsyncError: Story = args => {
))}
</TreeView.SubTree>
</TreeView.Item>
<TreeView.Item>
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Another file
</TreeView.Item>
</TreeView>
</nav>
</Box>
Expand Down
78 changes: 77 additions & 1 deletion src/TreeView/TreeView.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {fireEvent, render} from '@testing-library/react'
import {fireEvent, render, waitFor} from '@testing-library/react'
import React from 'react'
import {ThemeProvider} from '../ThemeProvider'
import {SubTreeState, TreeView} from './TreeView'
Expand Down Expand Up @@ -1121,4 +1121,80 @@ describe('Asyncronous loading', () => {
expect(firstChild).toHaveFocus()
})
})

it.only('moves focus to parent item after closing error dialog', async () => {
function TestTree() {
const [error, setError] = React.useState('Test error')

return (
<TreeView aria-label="Test tree">
<TreeView.Item defaultExpanded>
Parent
<TreeView.SubTree>
{error ? (
<TreeView.ErrorDialog
onRetry={() => {
setError('')
}}
onDismiss={() => setError('')}
>
{error}
</TreeView.ErrorDialog>
) : null}
</TreeView.SubTree>
</TreeView.Item>
</TreeView>
)
}

const {getByRole} = renderWithTheme(<TestTree />)

const dialog = getByRole('alertdialog')
const parentItem = getByRole('treeitem', {name: 'Parent'})

// Parent item should not be focused
expect(parentItem).not.toHaveFocus()

// Dialog should be visible
expect(dialog).toBeVisible()

// Press esc to close error dialog
fireEvent.keyDown(document.activeElement || document.body, {key: 'Escape'})

// Dialog should not be visible
expect(dialog).not.toBeVisible()

await waitFor(() => {
// Parent item should be focused
expect(parentItem).toHaveFocus()
})
})

it('ignores arrow keys when error dialog is open', async () => {
const {getByRole} = renderWithTheme(
<TreeView aria-label="Test tree">
<TreeView.Item defaultExpanded>
Parent
<TreeView.SubTree>
<TreeView.ErrorDialog>Opps</TreeView.ErrorDialog>
<TreeView.Item>Child</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
</TreeView>
)

const parentItem = getByRole('treeitem', {name: 'Parent'})

// Parent item should be expanded
expect(parentItem).toHaveAttribute('aria-expanded', 'true')

// Focus first item
parentItem.focus()

// Press ←
fireEvent.keyDown(document.activeElement || document.body, {key: 'ArrowLeft'})

// Parent item should still be expanded
expect(parentItem).toHaveAttribute('aria-expanded', 'true')
})
})
Loading

0 comments on commit aca96c0

Please sign in to comment.