Skip to content

Commit

Permalink
Revert "SelectPanel2: Use html dialog (#4020)"
Browse files Browse the repository at this point in the history
This reverts commit c2a53a0.
  • Loading branch information
broccolinisoup authored Dec 21, 2023
1 parent c2809d6 commit 2d36acf
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 105 deletions.
5 changes: 0 additions & 5 deletions .changeset/eleven-lizards-draw.md

This file was deleted.

2 changes: 1 addition & 1 deletion src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function getSlideAnimationStartingVector(anchorSide?: AnchorSide): {x: number; y
return {x: 0, y: 0}
}

export const StyledOverlay = styled.div<StyledOverlayProps>`
const StyledOverlay = styled.div<StyledOverlayProps>`
background-color: ${get('colors.canvas.overlay')};
box-shadow: ${get('shadows.overlay.shadow')};
position: absolute;
Expand Down
134 changes: 35 additions & 99 deletions src/drafts/SelectPanel2/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
IconButton,
Heading,
Box,
AnchoredOverlay,
AnchoredOverlayProps,
Tooltip,
TextInput,
TextInputProps,
Expand All @@ -18,9 +20,8 @@ import {
} from '../../../src/index'
import {ActionListContainerContext} from '../../../src/ActionList/ActionListContainerContext'
import {useSlots} from '../../hooks/useSlots'
import {useProvidedRefOrCreate, useId, useAnchoredPosition} from '../../hooks'
import {useProvidedRefOrCreate, useId} from '../../hooks'
import {useFocusZone} from '../../hooks/useFocusZone'
import {StyledOverlay, OverlayProps} from '../../Overlay/Overlay'

const SelectPanelContext = React.createContext<{
title: string
Expand Down Expand Up @@ -57,8 +58,8 @@ export type SelectPanelProps = {
onSubmit?: (event?: React.FormEvent<HTMLFormElement>) => void

// TODO: move these to SelectPanel.Overlay or overlayProps
width?: OverlayProps['width']
height?: OverlayProps['height']
width?: AnchoredOverlayProps['width']
height?: AnchoredOverlayProps['height']

children: React.ReactNode
}
Expand All @@ -81,38 +82,24 @@ const Panel: React.FC<SelectPanelProps> = ({
height = 'large',
...props
}) => {
const [internalOpen, setInternalOpen] = React.useState(defaultOpen)

// sync open state with props
if (propsOpen !== undefined && internalOpen !== propsOpen) setInternalOpen(propsOpen)

// TODO: replace this hack with clone element?
const anchorRef = useProvidedRefOrCreate(providedAnchorRef)

// 🚨 Hack for good API!
// we strip out Anchor from children and wire it up to Dialog
// we strip out Anchor from children and pass it to AnchoredOverlay to render
// with additional props for accessibility
let Anchor: React.ReactElement | undefined
const anchorRef = useProvidedRefOrCreate(providedAnchorRef)

const onAnchorClick = () => {
if (!internalOpen) setInternalOpen(true)
else onInternalClose()
}

let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null
const contents = React.Children.map(props.children, child => {
if (React.isValidElement(child) && child.type === SelectPanelButton) {
Anchor = React.cloneElement(child, {
// @ts-ignore TODO
ref: anchorRef,
onClick: onAnchorClick,
'aria-haspopup': true,
'aria-expanded': internalOpen,
})
renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
return null
}
return child
})

const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
// sync open state
if (propsOpen !== undefined && internalOpen !== propsOpen) setInternalOpen(propsOpen)

const onInternalClose = () => {
if (propsOpen === undefined) setInternalOpen(false)
if (typeof propsOnCancel === 'function') propsOnCancel()
Expand Down Expand Up @@ -148,77 +135,26 @@ const Panel: React.FC<SelectPanelProps> = ({
[internalOpen],
)

/* Dialog */
const dialogRef = React.useRef<HTMLDialogElement>(null)
if (internalOpen) dialogRef.current?.showModal()
else dialogRef.current?.close()

// dialog handles Esc automatically, so we have to sync internal state
React.useEffect(() => dialogRef.current?.addEventListener('close', onInternalClose))

// React doesn't support autoFocus for dialog: https://github.com/facebook/react/issues/23301
// tl;dr: react takes over autofocus instead of letting the browser handle it,
// but not for dialogs, so we have to do it
React.useEffect(() => {
if (internalOpen) document.querySelector('input')?.focus()
}, [internalOpen])

/* Anchored */
const {position} = useAnchoredPosition(
{
anchorElementRef: anchorRef,
floatingElementRef: dialogRef,
side: 'outside-bottom',
align: 'start',
},
[anchorRef.current, dialogRef.current],
)

/*
We don't close the panel when clicking outside.
For many years, we used to save changes and closed the dialog (for label picker)
which isn't accessible, clicking outside should discard changes and close the dialog
Fixing this a11y bug would confuse users, so as a middle ground,
we don't close the menu and nudge the user towards the footer actions
*/
const [footerAnimationEnabled, setFooterAnimationEnabled] = React.useState(false)
const onClickOutside = () => {
setFooterAnimationEnabled(true)
window.setTimeout(() => setFooterAnimationEnabled(false), 350)
}

return (
<>
{Anchor}

<StyledOverlay
as="dialog"
ref={dialogRef}
aria-labelledby={`${panelId}--title`}
aria-describedby={description ? `${panelId}--description` : undefined}
<AnchoredOverlay
anchorRef={anchorRef}
renderAnchor={renderAnchor}
open={internalOpen}
onOpen={() => setInternalOpen(true)}
onClose={onInternalClose}
width={width}
height={height}
sx={{
...position,
// reset dialog default styles
border: 'none',
padding: 0,
margin: 0,
'::backdrop': {background: 'transparent'},

'& [data-selectpanel-primary-actions]': {
animation: footerAnimationEnabled ? 'selectpanel-gelatine 350ms linear' : 'none',
},
'@keyframes selectpanel-gelatine': {
'0%': {transform: 'scale(1, 1)'},
'25%': {transform: 'scale(0.9, 1.1)'},
'50%': {transform: 'scale(1.1, 0.9)'},
'75%': {transform: 'scale(0.95, 1.05)'},
'100%': {transform: 'scale(1, 1)'},
},
focusZoneSettings={{
// we only want focus trap from the overlay,
// we don't want focus zone on the whole overlay because
// we have a focus zone on the list
disabled: true,
}}
onClick={event => {
if (event.target === event.currentTarget) onClickOutside()
overlayProps={{
role: 'dialog',
'aria-labelledby': `${panelId}--title`,
'aria-describedby': description ? `${panelId}--description` : undefined,
}}
>
<SelectPanelContext.Provider
Expand All @@ -235,16 +171,15 @@ const Panel: React.FC<SelectPanelProps> = ({
>
<Box
as="form"
method="dialog"
onSubmit={onInternalSubmit}
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
{slots.header ?? /* render default header as fallback */ <SelectPanelHeader />}

{/* render default header as fallback */}
{slots.header ?? <SelectPanelHeader />}
<Box
as="div"
ref={listContainerRef as React.RefObject<HTMLDivElement>}
Expand Down Expand Up @@ -274,7 +209,7 @@ const Panel: React.FC<SelectPanelProps> = ({
{slots.footer}
</Box>
</SelectPanelContext.Provider>
</StyledOverlay>
</AnchoredOverlay>
</>
)
}
Expand Down Expand Up @@ -344,7 +279,6 @@ const SelectPanelHeader: React.FC<React.PropsWithChildren> = ({children, ...prop
}

const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChange, ...props}) => {
// TODO: use forwardedRef
const inputRef = React.createRef<HTMLInputElement>()

const {setSearchQuery} = React.useContext(SelectPanelContext)
Expand All @@ -358,6 +292,9 @@ const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChan

return (
<TextInput
// this autofocus doesn't seem to apply 🤔
// probably because the focus zone overrides autoFocus
autoFocus
ref={inputRef}
block
leadingVisual={SearchIcon}
Expand All @@ -366,7 +303,6 @@ const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChan
<TextInput.Action
icon={XCircleFillIcon}
aria-label="Clear"
tooltipDirection="w"
sx={{color: 'fg.subtle', bg: 'none'}}
onClick={() => {
if (inputRef.current) inputRef.current.value = ''
Expand Down Expand Up @@ -413,7 +349,7 @@ const SelectPanelFooter = ({...props}) => {
<Box sx={{flexGrow: hidePrimaryActions ? 1 : 0}}>{props.children}</Box>

{hidePrimaryActions ? null : (
<Box data-selectpanel-primary-actions sx={{display: 'flex', gap: 2}}>
<Box sx={{display: 'flex', gap: 2}}>
<Button size="small" type="button" onClick={() => onCancel()}>
Cancel
</Button>
Expand Down

0 comments on commit 2d36acf

Please sign in to comment.