Skip to content

Commit

Permalink
Overlay: Add proper roles w/ keyboard expectations to stories (#5175)
Browse files Browse the repository at this point in the history
* Add proper roles w/ keyboard expectations to stories

* Make prop optional
  • Loading branch information
TylerJDev authored Oct 28, 2024
1 parent c9e68d2 commit a2536bf
Showing 1 changed file with 99 additions and 9 deletions.
108 changes: 99 additions & 9 deletions packages/react/src/Overlay/Overlay.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useState, useRef, useCallback} from 'react'
import type {Meta} from '@storybook/react'
import {TriangleDownIcon, PlusIcon, IssueDraftIcon} from '@primer/octicons-react'
import {TriangleDownIcon, PlusIcon, IssueDraftIcon, XIcon} from '@primer/octicons-react'
import {
Overlay,
ButtonGroup,
Expand All @@ -16,15 +16,18 @@ import {
Label,
ActionList,
ActionMenu,
useFocusTrap,
} from '..'
import type {AnchorSide} from '@primer/behaviors'
import type {AriaRole} from '../utils/types'
import {Tooltip} from '../TooltipV2'

export default {
title: 'Private/Components/Overlay/Features',
component: Overlay,
args: {
anchorSide: 'inside-top',
role: 'dialog',
},
argTypes: {
anchorSide: {
Expand All @@ -43,16 +46,22 @@ export default {
'outside-right',
],
},
role: {
type: 'string',
},
},
} as Meta

interface OverlayProps {
anchorSide: AnchorSide
anchorSide?: AnchorSide
role?: AriaRole
right?: boolean
}

export const DropdownOverlay = ({anchorSide}: OverlayProps) => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)

return (
<>
<Button ref={buttonRef} sx={{position: 'relative'}} onClick={() => setIsOpen(!isOpen)}>
Expand All @@ -67,8 +76,9 @@ export const DropdownOverlay = ({anchorSide}: OverlayProps) => {
onEscape={() => setIsOpen(false)}
onClickOutside={() => setIsOpen(false)}
anchorSide={anchorSide}
role="none"
>
<ActionList>
<ActionList role="menu">
<ActionList.Item>Copy link</ActionList.Item>
<ActionList.Item>Quote reply</ActionList.Item>
<ActionList.Item>Reference in new issue</ActionList.Item>
Expand All @@ -82,12 +92,15 @@ export const DropdownOverlay = ({anchorSide}: OverlayProps) => {
)
}

export const DialogOverlay = ({anchorSide}: OverlayProps) => {
export const DialogOverlay = ({anchorSide, role}: OverlayProps) => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const confirmButtonRef = useRef<HTMLButtonElement>(null)
const anchorRef = useRef<HTMLDivElement>(null)
const closeOverlay = () => setIsOpen(false)
useFocusTrap({containerRef, disabled: !isOpen, initialFocusRef: confirmButtonRef, returnFocusRef: buttonRef})

return (
<Box ref={anchorRef}>
<Button ref={buttonRef} onClick={() => setIsOpen(!isOpen)}>
Expand All @@ -102,6 +115,9 @@ export const DialogOverlay = ({anchorSide}: OverlayProps) => {
onClickOutside={closeOverlay}
width="small"
anchorSide={anchorSide}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={containerRef}
>
<Box display="flex" flexDirection="column" p={2}>
<Text>Are you sure?</Text>
Expand All @@ -118,7 +134,7 @@ export const DialogOverlay = ({anchorSide}: OverlayProps) => {
)
}

export const OverlayOnTopOfOverlay = ({anchorSide}: OverlayProps) => {
export const OverlayOnTopOfOverlay = ({anchorSide, role}: OverlayProps) => {
const [isOpen, setIsOpen] = useState(false)
const [isSecondaryOpen, setIsSecondaryOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
Expand All @@ -130,6 +146,14 @@ export const OverlayOnTopOfOverlay = ({anchorSide}: OverlayProps) => {
const items = ['🔵 Cyan', '🔴 Magenta', '🟡 Yellow']
const [selectedItem, setSelectedItem] = React.useState(items[0])

const primaryContainer = useRef<HTMLDivElement>(null)
const secondaryContainer = useRef<HTMLDivElement>(null)

useFocusTrap({
containerRef: !isSecondaryOpen ? primaryContainer : secondaryContainer,
disabled: !isOpen,
})

return (
<Box position="absolute" top={0} left={0} bottom={0} right={0} ref={anchorRef}>
<input placeholder="Input for focus testing" />
Expand All @@ -145,6 +169,9 @@ export const OverlayOnTopOfOverlay = ({anchorSide}: OverlayProps) => {
onClickOutside={closeOverlay}
width="small"
anchorSide={anchorSide}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={primaryContainer}
>
<Button ref={secondaryButtonRef} onClick={() => setIsSecondaryOpen(!isSecondaryOpen)}>
open overlay
Expand All @@ -158,6 +185,9 @@ export const OverlayOnTopOfOverlay = ({anchorSide}: OverlayProps) => {
width="small"
sx={{top: '40px'}}
anchorSide={anchorSide}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={secondaryContainer}
>
<Box display="flex" flexDirection="column" p={2}>
<Text>Select an option!</Text>
Expand Down Expand Up @@ -186,12 +216,14 @@ export const OverlayOnTopOfOverlay = ({anchorSide}: OverlayProps) => {
)
}

export const MemexNestedOverlays = () => {
export const MemexNestedOverlays = ({role}: OverlayProps) => {
const [overlayOpen, setOverlayOpen] = React.useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const containerRef = useRef<HTMLDivElement>(null)

const durations = ['days', 'weeks']
const [duration, setDuration] = React.useState(durations[0])
useFocusTrap({containerRef, disabled: !overlayOpen, returnFocusRef: buttonRef})

return (
<div>
Expand All @@ -213,6 +245,9 @@ export const MemexNestedOverlays = () => {
ignoreClickRefs={[buttonRef]}
top={60}
left={16}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={containerRef}
>
<Box as="form" onSubmit={() => setOverlayOpen(false)} sx={{display: 'flex', flexDirection: 'column', py: 2}}>
<Box sx={{paddingX: 3, display: 'flex', alignItems: 'center', gap: 1}}>
Expand Down Expand Up @@ -247,12 +282,19 @@ export const MemexNestedOverlays = () => {
)
}

export const NestedOverlays = () => {
export const NestedOverlays = ({role}: OverlayProps) => {
const [listOverlayOpen, setListOverlayOpen] = React.useState(false)
const [createListOverlayOpen, setCreateListOverlayOpen] = React.useState(false)

const buttonRef = useRef<HTMLButtonElement>(null)
const secondaryButtonRef = useRef<HTMLButtonElement>(null)
const primaryContainer = useRef<HTMLDivElement>(null)
const secondaryContainer = useRef<HTMLDivElement>(null)

useFocusTrap({
containerRef: !createListOverlayOpen ? primaryContainer : secondaryContainer,
disabled: !listOverlayOpen,
})

React.useEffect(() => {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -285,6 +327,9 @@ export const NestedOverlays = () => {
ignoreClickRefs={[buttonRef]}
top={100}
left={16}
ref={primaryContainer}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
>
<Box sx={{display: 'flex', flexDirection: 'column', py: 2}}>
<Box sx={{paddingX: 3, paddingY: 2}}>
Expand Down Expand Up @@ -324,6 +369,9 @@ export const NestedOverlays = () => {
ignoreClickRefs={[secondaryButtonRef]}
top={120}
left={64}
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={secondaryContainer}
>
<Box as="form" sx={{display: 'flex', flexDirection: 'column', p: 3}}>
<Text color="fg.muted" sx={{fontSize: 1, mb: 3}}>
Expand All @@ -344,11 +392,12 @@ export const NestedOverlays = () => {
)
}

export const MemexIssueOverlay = () => {
export const MemexIssueOverlay = ({role}: OverlayProps) => {
const [overlayOpen, setOverlayOpen] = React.useState(false)
const linkRef = useRef<HTMLAnchorElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
const containerRef = useRef<HTMLDivElement>(null)

const [title, setTitle] = React.useState('Implement draft issue editor')
const [editing, setEditing] = React.useState(false)
Expand All @@ -358,6 +407,8 @@ export const MemexIssueOverlay = () => {
if (editing) inputRef.current?.focus()
}, [editing])

useFocusTrap({containerRef, disabled: !overlayOpen, initialFocusRef: buttonRef, returnFocusRef: linkRef})

return (
<>
<Link
Expand Down Expand Up @@ -389,6 +440,9 @@ export const MemexIssueOverlay = () => {
returnFocusRef={linkRef}
top={0}
left="calc(100vw - 480px)"
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={containerRef}
>
<Box sx={{p: 4, height: '100vh'}}>
<Box sx={{display: 'flex', alignItems: 'center', gap: 1, mb: 2}}>
Expand Down Expand Up @@ -451,13 +505,21 @@ export const MemexIssueOverlay = () => {
)
}

export const PositionedOverlays = ({right}: {right?: boolean}) => {
export const PositionedOverlays = ({right, role}: OverlayProps) => {
const [isOpen, setIsOpen] = useState(false)
const [direction, setDirection] = useState<'left' | 'right'>(right ? 'right' : 'left')
const buttonRef = useRef<HTMLButtonElement>(null)
const confirmButtonRef = useRef<HTMLButtonElement>(null)
const anchorRef = useRef<HTMLDivElement>(null)
const closeOverlay = () => setIsOpen(false)

const containerRef = useRef<HTMLDivElement>(null)

useFocusTrap({
containerRef,
disabled: !isOpen,
})

return (
<Box ref={anchorRef}>
<Button
Expand Down Expand Up @@ -491,6 +553,9 @@ export const PositionedOverlays = ({right}: {right?: boolean}) => {
onClickOutside={closeOverlay}
width="auto"
anchorSide="inside-right"
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={containerRef}
>
<Box
sx={{
Expand All @@ -501,6 +566,17 @@ export const PositionedOverlays = ({right}: {right?: boolean}) => {
alignItems: 'center',
}}
>
<IconButton
aria-label="Close"
onClick={closeOverlay}
icon={XIcon}
variant="invisible"
sx={{
position: 'absolute',
left: '5px',
top: '5px',
}}
/>
<Text>Look! left aligned</Text>
</Box>
</Overlay>
Expand All @@ -515,6 +591,9 @@ export const PositionedOverlays = ({right}: {right?: boolean}) => {
anchorSide={'inside-left'}
right={0}
position="fixed"
role={role}
aria-modal={role === 'dialog' ? 'true' : undefined}
ref={containerRef}
>
<Box
sx={{
Expand All @@ -525,6 +604,17 @@ export const PositionedOverlays = ({right}: {right?: boolean}) => {
alignItems: 'center',
}}
>
<IconButton
aria-label="Close"
onClick={closeOverlay}
icon={XIcon}
variant="invisible"
sx={{
position: 'absolute',
right: '5px',
top: '5px',
}}
/>
<Text>Look! right aligned</Text>
</Box>
</Overlay>
Expand Down

0 comments on commit a2536bf

Please sign in to comment.