In most cases, focus will follow the tabbing order. However, sometimes it is necessary to change the context of the user's task. In doing so, focus is placed and constrained on the new task. The changing of the user's context and focus is considered navigating to/between focus groups.
The contents of a drawer should be contained withing a focus group.
Opening a drawer should always move the focus to something within the drawer.
{() => {
class StateManager extends Component {
constructor() {
super()
this.state = {
open: false,
}
this.open = this.open.bind(this)
this.handleClose = this.handleClose.bind(this)
}
open() {
this.setState({
open: true,
})
}
handleClose() {
this.setState(
() => ({
open: false,
}),
this.pop,
)
}
render() {
return (
<FocusManager>
{pop => {
this.pop = pop
return (
<Fragment>
<FocusGroup disableLock={true}>
{bind => (
<Button {...bind} onClick={this.open}>
Open Drawer
)}
</FocusGroup>
<Drawer
{...this.state}
handleClickOutside={this.handleClose}
placement="right"
>
<FocusGroup>
{bind => (
<SpacedGroup direction="vertical">
<TextField {...bind} focused={this.state.open} />
<TextField {...bind} />
<TextField {...bind} />
<TextField {...bind} />
<Button {...bind} onClick={this.handleClose}>
Close
</Button>
</SpacedGroup>
)}
</FocusGroup>
</Drawer>
</Fragment>
)
}}
</FocusManager>
)
}
}
return <StateManager />
}}
</Playground>
Menu does not moves focus between the element that opened the menu and the list items within the menu. Therefore, tabbing does not cycle the user through the menu list items.
{() => {
class StateManager extends Component {
constructor() {
super()
this.state = {
isOpenIndex: -1,
items: [
{ name: 'My Shared View', project: 'Project A' },
{ name: 'Backlog Shared View', project: 'Project A' },
{ name: 'Backlog Shared View', project: 'Project B' },
{ name: 'Holiday Party', project: 'Project A' },
{
name: 'Roadmaping Latest Quarter',
project: 'Project A',
},
{ name: 'Team Q Backlog', project: 'Project A' },
],
}
this.open = this.open.bind(this)
this.close = this.close.bind(this)
}
open(index) {
return () => this.setState({ isOpenIndex: index })
}
close() {
this.setState({ isOpenIndex: -1 })
}
render() {
const getMenuForItemAt = index => {
const isOpen = this.state.isOpenIndex === index
return (
<Menu
anchor={
<IconButton
title="more-actions"
icon={AlertIcon}
onClick={this.open(index)}
/>
}
open={isOpen}
placement="bottom-end"
onClickOutside={this.close}
>
<List bordered>
<ListItem onClick={() => {}}>
<ListItemText primary="Action 1" />
<ListItem onClick={() => {}}>
<ListItemText primary="Action 2" />
</ListItem>
</List>
</Menu>
)
}
const ListItems = this.state.items.map((item, index) => (
<ListItem key={index} secondaryAction={getMenuForItemAt(index)}>
<ListItemText primary={item.name} secondary={item.project} />
</ListItem>
))
return (
<SpacedGroup xs={24}>
<Paper>
<List bordered>{ListItems}</List>
</Paper>
</SpacedGroup>
)
}
}
return <StateManager />
}}
</Playground>
In some cases, a drawer may open in response to a menu item's primary action; in which case opening the drawer will close the menu and move the user's focus to the drawer. Ther user's focus is locked to the contents of the drawer and should return to the element which opened the menu upon closing the drawer.
{() => {
class StateManager extends Component {
constructor() {
super()
this.state = {
isOpenIndex: -1,
items: [
{ name: 'My Shared View', project: 'Project A' },
{ name: 'Backlog Shared View', project: 'Project A' },
{ name: 'Backlog Shared View', project: 'Project B' },
{ name: 'Holiday Party', project: 'Project A' },
{
name: 'Roadmaping Latest Quarter',
project: 'Project A',
},
{ name: 'Team Q Backlog', project: 'Project A' },
],
open: false,
}
this.open = this.open.bind(this)
this.close = this.close.bind(this)
this.openDrawer = this.openDrawer.bind(this)
this.closeDrawer = this.closeDrawer.bind(this)
}
open(index) {
return () => this.setState({ isOpenIndex: index })
}
close() {
this.setState({ isOpenIndex: -1 })
}
openDrawer() {
this.setState({ open: true, isOpenIndex: -1 })
}
closeDrawer() {
this.setState({ open: false }, () => {
this.pop()
})
}
render() {
const getMenuForItemAt = index => {
const isOpen = this.state.isOpenIndex === index
return (
<Menu
anchor={
<IconButton
title="more-actions"
icon={AlertIcon}
onClick={this.open(index)}
/>
}
open={isOpen}
placement="bottom-end"
onClickOutside={this.close}
>
<List bordered>
<ListItem onClick={this.openDrawer} focused={isOpen}>
<ListItemText primary="Action 1" />
<ListItem onClick={this.openDrawer}>
<ListItemText primary="Action 2" />
</ListItem>
</List>
</Menu>
)
}
const ListItems = this.state.items.map((item, index) => (
<ListItem key={index} secondaryAction={getMenuForItemAt(index)}>
<ListItemText primary={item.name} secondary={item.project} />
</ListItem>
))
return (
<SpacedGroup xs={24}>
<Paper>
<FocusManager>
{pop => {
this.pop = pop
return (
<Fragment>
<List bordered>{ListItems}</List>
<Drawer
open={this.state.open}
handleClickOutside={this.closeDrawer}
placement="right"
>
<FocusGroup>
{bind => (
<SpacedGroup direction="vertical">
<TextField {...bind} focused={this.state.open} />
<TextField {...bind} />
<TextField {...bind} />
<TextField {...bind} />
<Button {...bind} onClick={this.closeDrawer}>
Close
</Button>
</SpacedGroup>
)}
</FocusGroup>
</Drawer>
</Fragment>
)
}}
</FocusManager>
</Paper>
</SpacedGroup>
)
}
}
return <StateManager />
}}
</Playground>