Skip to content

SelectPanel2: Use html dialog #4018

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

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6a12091
breaking changes + cleanup
siddharthkp Nov 7, 2023
f584204
add SelectPanel.Warning
siddharthkp Nov 7, 2023
6d56c15
add link styles
siddharthkp Nov 7, 2023
6a30cf3
remove todo story :)
siddharthkp Nov 7, 2023
52a3608
typo
siddharthkp Nov 7, 2023
5309eb1
I not H
siddharthkp Nov 7, 2023
8930146
change example from labels to assignees
siddharthkp Nov 9, 2023
96f4ca0
1. make it work....
siddharthkp Nov 9, 2023
4393e0c
move implementation details from story → component
siddharthkp Nov 10, 2023
d42096e
absorb EmptyMessage into SelectPanel.Message
siddharthkp Nov 10, 2023
cda8096
absorb WarningMessage into SelectPanel.Message
siddharthkp Nov 10, 2023
310fefe
absorb ErrorMessage into SelectPanel.Message
siddharthkp Nov 10, 2023
336b42e
stricter types for empty
siddharthkp Nov 10, 2023
8d15989
clean up lint errors
siddharthkp Nov 10, 2023
8cc84c6
Merge branch 'main' into drafts-selectpanel-validation
siddharthkp Nov 10, 2023
24127c1
tiny cleanup for bug
siddharthkp Nov 10, 2023
db5359f
clean up message types real good!
siddharthkp Nov 10, 2023
68af462
child can be null
siddharthkp Nov 10, 2023
bd44d73
types: first round
siddharthkp Nov 10, 2023
030416b
types: round 2 - cleanup
siddharthkp Nov 10, 2023
20cc878
types: round 3 more cleanup
siddharthkp Nov 10, 2023
abfa75c
types: 4 clean up stories ignores
siddharthkp Nov 10, 2023
bac97c2
types: 5 clean up stories
siddharthkp Nov 10, 2023
3f0b47a
Merge branch 'main' into drafts-selectpanel-types
siddharthkp Nov 16, 2023
0afa927
SelectPanel.Loading no longer exists
siddharthkp Nov 20, 2023
540e2d3
Revert "SelectPanel.Loading no longer exists"
siddharthkp Nov 21, 2023
c5ac721
use TextInputProps for SelectPanel.SearchInput
siddharthkp Nov 21, 2023
4da0ec1
sync internal and props.open with state
siddharthkp Nov 28, 2023
4acebcd
use ?? instead of ||
siddharthkp Nov 28, 2023
b004f32
copy changes over from branch:drafts-selectpanel-dialog
siddharthkp Dec 4, 2023
efb7070
wip
siddharthkp Dec 5, 2023
fbd159d
always use showModal
siddharthkp Dec 5, 2023
4e60c66
make backdrop transparent
siddharthkp Dec 5, 2023
7e15545
Merge branch 'main' into drafts-selectpanel-with-dialog
siddharthkp Dec 5, 2023
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 src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function getSlideAnimationStartingVector(anchorSide?: AnchorSide): {x: number; y
return {x: 0, y: 0}
}

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

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

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

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

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

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

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

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

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 @@ -135,26 +147,43 @@ const Panel: React.FC<SelectPanelProps> = ({
[internalOpen],
)

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

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

return (
<>
<AnchoredOverlay
anchorRef={anchorRef}
renderAnchor={renderAnchor}
open={internalOpen}
onOpen={() => setInternalOpen(true)}
onClose={onInternalClose}
{Anchor}

<StyledOverlay
as="dialog"
ref={dialogRef}
aria-labelledby={`${panelId}--title`}
aria-describedby={description ? `${panelId}--description` : undefined}
width={width}
height={height}
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,
}}
overlayProps={{
role: 'dialog',
'aria-labelledby': `${panelId}--title`,
'aria-describedby': description ? `${panelId}--description` : undefined,
sx={{
...position,
// reset dialog default styles
border: 'none',
padding: 0,
margin: 0,
'::backdrop': {background: 'transparent'},
}}
>
<SelectPanelContext.Provider
Expand All @@ -171,6 +200,7 @@ const Panel: React.FC<SelectPanelProps> = ({
>
<Box
as="form"
method="dialog"
onSubmit={onInternalSubmit}
sx={{
display: 'flex',
Expand Down Expand Up @@ -209,7 +239,7 @@ const Panel: React.FC<SelectPanelProps> = ({
{slots.footer}
</Box>
</SelectPanelContext.Provider>
</AnchoredOverlay>
</StyledOverlay>
</>
)
}
Expand Down Expand Up @@ -290,11 +320,11 @@ const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChan
else setSearchQuery(event.target.value)
}

// sad but React doesn't handle autofocus inside dialog yet: https://github.com/facebook/react/issues/23301
React.useEffect(() => inputRef.current?.setAttribute('autofocus', 'true'), [inputRef])

return (
<TextInput
// this autofocus doesn't seem to apply 🤔
// probably because the focus zone overrides autoFocus
autoFocus
ref={inputRef}
block
leadingVisual={SearchIcon}
Expand All @@ -313,12 +343,10 @@ const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChan
}}
/>
}
sx={
{
/* TODO: uncommenting this breaks keyboard navigation, that's odd */
// '& input:empty + .TextInput-action': {display: 'none'},
}
}
sx={{
// UX nicety: If input is empty, hide clear action
'& input:placeholder-shown + .TextInput-action': {display: 'none'},
}}
onChange={internalOnChange}
{...props}
/>
Expand Down