Skip to content

Commit

Permalink
Merge d7b28be into f0be480
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthkp authored Nov 7, 2024
2 parents f0be480 + d7b28be commit 1b8ec21
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 10 deletions.
6 changes: 6 additions & 0 deletions packages/react/src/SelectPanel/SelectPanel.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
"defaultValue": "",
"description": "See [TextInput props](/react/TextInput#props)."
},
{
"name": "variant",
"type": "'anchored' | 'modal'",
"defaultValue": "anchored",
"description": "Anchored to the button or centered on the screen. Modal variant also adds Save and Cancel buttons."
},
{
"name": "footer",
"type": "string | React.ReactElement",
Expand Down
63 changes: 63 additions & 0 deletions packages/react/src/SelectPanel/SelectPanel.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,66 @@ export const WithGroups = () => {
/>
)
}

export const ModalVariant = () => {
const [selected, setSelected] = React.useState<ItemInput[]>([items[0], items[1]])
const [filter, setFilter] = React.useState('')
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [open, setOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)

return (
<>
<h1>Multi Select Panel as Modal</h1>
<SelectPanel
variant="modal"
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
anchorRef={buttonRef}
placeholderText="Filter Labels"
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
/>
</>
)
}

export const ModalVariantWithSecondaryAction = () => {
const [selected, setSelected] = React.useState<ItemInput[]>([items[0], items[1]])
const [filter, setFilter] = React.useState('')
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [open, setOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)

return (
<>
<h1>Multi Select Panel as Modal with secondary action</h1>
<SelectPanel
variant="modal"
// backward compatible API choice
// TODO: improve this API, rename it to a single secondaryAction or secondaryFooterSlotSomething
footer={<Button size="small">Edit labels</Button>}
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
anchorRef={buttonRef}
placeholderText="Filter Labels"
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
/>
</>
)
}
80 changes: 70 additions & 10 deletions packages/react/src/SelectPanel/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,25 @@ interface SelectPanelMultiSelection {
onSelectedChange: (selected: ItemInput[]) => void
}

type OpenGestures = 'anchor-click' | 'anchor-key-press'
type CloseGestures = 'click-outside' | 'escape' | 'selection' | 'save-click' | 'cancel-click'

interface SelectPanelBaseProps {
// TODO: Make `title` required in the next major version
title?: string | React.ReactElement
subtitle?: string | React.ReactElement
onOpenChange: (
open: boolean,
gesture: 'anchor-click' | 'anchor-key-press' | 'click-outside' | 'escape' | 'selection',
) => void
onOpenChange: (open: boolean, gesture: OpenGestures | CloseGestures) => void
placeholder?: string
// TODO: Make `inputLabel` required in next major version
inputLabel?: string
overlayProps?: Partial<OverlayProps>
footer?: string | React.ReactElement
// TODO: do we need keep the old variants for backward-compat? there isn't any usage in dotcom
variant?: 'anchored' | 'modal' | FilteredActionListProps['variant']
}

export type SelectPanelProps = SelectPanelBaseProps &
Omit<FilteredActionListProps, 'selectionVariant'> &
Omit<FilteredActionListProps, 'selectionVariant' | 'variant'> &
Pick<AnchoredOverlayProps, 'open'> &
AnchoredOverlayWrapperAnchorProps &
(SelectPanelSingleSelection | SelectPanelMultiSelection)
Expand Down Expand Up @@ -90,7 +92,8 @@ export function SelectPanel({
items,
footer,
textInputProps,
overlayProps,
variant = 'anchored',
overlayProps = variant === 'modal' ? {maxWidth: 'medium', height: 'fit-content', maxHeight: 'large'} : undefined,
sx,
...listProps
}: SelectPanelProps): JSX.Element {
Expand All @@ -111,12 +114,16 @@ export function SelectPanel({
[onOpenChange],
)
const onClose = useCallback(
(gesture: Parameters<Exclude<AnchoredOverlayProps['onClose'], undefined>>[0] | 'selection') => {
(gesture: Parameters<Exclude<AnchoredOverlayProps['onClose'], undefined>>[0] | CloseGestures) => {
onOpenChange(false, gesture)
},
[onOpenChange],
)

const onCancel = () => {
// TODO
}

const renderMenuAnchor = useMemo(() => {
if (renderAnchor === null) {
return null
Expand Down Expand Up @@ -258,6 +265,22 @@ export function SelectPanel({
}
{...position}
{...overlayProps}
sx={{
// TODO: check styles, do we need all of these?
display: 'flex',
padding: 0,
color: 'fg.default',

'&[data-variant="anchored"], &[data-variant="full-screen"]': {
margin: 0,
top: position?.top,
left: position?.left,
},
'&[data-variant="modal"]': {
inset: 0,
margin: 'auto',
},
}}
>
<LiveRegionOutlet />
{usingModernActionList ? null : (
Expand Down Expand Up @@ -300,20 +323,57 @@ export function SelectPanel({
// than the Overlay (which would break scrolling the items)
sx={{...sx, height: 'inherit', maxHeight: 'inherit'}}
/>
{footer && (
{footer || variant === 'modal' ? (
<Box
sx={{
display: 'flex',
borderTop: '1px solid',
borderColor: 'border.default',
padding: 2,
padding: variant === 'modal' ? 3 : 2,
justifyContent: footer ? 'space-between' : 'end',
alignItems: 'center',
flexShrink: 0,
minHeight: '44px',
'> button': {
// make button full width if there's just one
width: variant === 'modal' ? 'auto' : '100%',
},
}}
>
{footer}
{variant === 'modal' ? (
<Box sx={{display: 'flex', gap: 2}}>
<Button
type="button"
size="small"
onClick={() => {
onCancel()
onClose('cancel-click')
}}
>
Cancel
</Button>
{/* TODO: loading state for save? */}
<Button type="submit" size="small" variant="primary" onClick={() => onClose('save-click')}>
Save
</Button>
</Box>
) : null}
</Box>
)}
) : null}
</Box>
</Overlay>

{variant === 'modal' && (
<Box
data-backdrop
sx={{
backgroundColor: 'primer.canvas.backdrop',
position: 'fixed',
inset: 0,
}}
/>
)}
</LiveRegion>
)
}
Expand Down

0 comments on commit 1b8ec21

Please sign in to comment.