Skip to content

Commit

Permalink
SelectPanel2: Add SelectPanel.SecondaryAction (#4145)
Browse files Browse the repository at this point in the history
* add secondary actions: link and checkbox

* merge secondary actions

* Create sharp-seahorses-guess.md

* update playground

* explicit type for footer context
  • Loading branch information
siddharthkp authored Jan 23, 2024
1 parent 91a899e commit 996475f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-seahorses-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

experimental/SelectPanel: Add SelectPanel.SecondaryAction
111 changes: 83 additions & 28 deletions src/drafts/SelectPanel2/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ import {
Text,
ActionListProps,
Octicon,
Link,
LinkProps,
Checkbox,
CheckboxProps,
} from '../../index'
import {ActionListContainerContext} from '../../ActionList/ActionListContainerContext'
import {useSlots} from '../../hooks/useSlots'
import {useProvidedRefOrCreate, useId, useAnchoredPosition} from '../../hooks'
import {useFocusZone} from '../../hooks/useFocusZone'
import {StyledOverlay, OverlayProps} from '../../Overlay/Overlay'
import InputLabel from '../../internal/components/InputLabel'
import {invariant} from '../../utils/invariant'

const SelectPanelContext = React.createContext<{
title: string
Expand Down Expand Up @@ -416,6 +422,7 @@ const SelectPanelSearchInput: React.FC<TextInputProps> = ({onChange: propsOnChan
)
}

const FooterContext = React.createContext<boolean>(false)
const SelectPanelFooter = ({...props}) => {
const {onCancel, selectionVariant} = React.useContext(SelectPanelContext)

Expand All @@ -428,38 +435,86 @@ const SelectPanelFooter = ({...props}) => {
}

return (
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
padding: 3,
borderTop: '1px solid',
borderColor: 'border.default',
}}
>
<Box sx={{flexGrow: hidePrimaryActions ? 1 : 0}}>{props.children}</Box>

{hidePrimaryActions ? null : (
<Box data-selectpanel-primary-actions sx={{display: 'flex', gap: 2}}>
<Button size="small" type="button" onClick={() => onCancel()}>
Cancel
</Button>
<Button size="small" type="submit" variant="primary">
Save
</Button>
</Box>
)}
</Box>
<FooterContext.Provider value={true}>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: hidePrimaryActions ? 2 : 3,
minHeight: '44px',
borderTop: '1px solid',
borderColor: 'border.default',
}}
>
<Box sx={{flexGrow: hidePrimaryActions ? 1 : 0}}>{props.children}</Box>

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

// TODO: is this the right way to add button props?
const SelectPanelSecondaryButton: React.FC<ButtonProps> = props => {
const SecondaryButton: React.FC<ButtonProps> = props => {
return <Button type="button" size="small" block {...props} />
}
// SelectPanel.SecondaryLink = props => {
// return <a {...props}>{props.children}</a>
// }

const SecondaryLink: React.FC<LinkProps> = props => {
return (
// @ts-ignore TODO: is as prop is not recognised by button?
<Button as={Link} size="small" variant="invisible" block {...props} sx={{fontSize: 0}}>
{props.children}
</Button>
)
}

const SecondaryCheckbox: React.FC<CheckboxProps> = ({id, children, ...props}) => {
const checkboxId = useId(id)
const {selectionVariant} = React.useContext(SelectPanelContext)

// Checkbox should not be used with instant selection
invariant(
selectionVariant !== 'instant',
'Sorry! SelectPanel.SecondaryAction with variant="checkbox" is not allowed inside selectionVariant="instant"',
)

return (
<Box sx={{display: 'flex', alignItems: 'center', gap: 2}}>
<Checkbox id={checkboxId} sx={{marginTop: 0}} {...props} />
<InputLabel htmlFor={checkboxId} sx={{fontSize: 0}}>
{children}
</InputLabel>
</Box>
)
}

type SelectPanelSecondaryActionProps = {children: React.ReactNode} & (
| ({variant: 'button'} & Partial<Omit<ButtonProps, 'variant'>>)
| ({variant: 'link'} & Partial<LinkProps>)
| ({variant: 'checkbox'; id?: string} & CheckboxProps)
)

const SelectPanelSecondaryAction: React.FC<SelectPanelSecondaryActionProps> = ({variant, ...props}) => {
const insideFooter = React.useContext(FooterContext)
invariant(insideFooter, 'SelectPanel.SecondaryAction is only allowed inside SelectPanel.Footer')

// @ts-ignore TODO
if (variant === 'button') return <SecondaryButton {...props} />
// @ts-ignore TODO
else if (variant === 'link') return <SecondaryLink {...props} />
// @ts-ignore TODO
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else if (variant === 'checkbox') return <SecondaryCheckbox {...props} />
}

const SelectPanelLoading: React.FC<{children: string}> = ({children = 'Fetching items...'}) => {
return (
Expand Down Expand Up @@ -563,7 +618,7 @@ export const SelectPanel = Object.assign(Panel, {
Header: SelectPanelHeader,
SearchInput: SelectPanelSearchInput,
Footer: SelectPanelFooter,
SecondaryButton: SelectPanelSecondaryButton,
Loading: SelectPanelLoading,
Message: SelectPanelMessage,
SecondaryAction: SelectPanelSecondaryAction,
})
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const Default = () => {
)}

<SelectPanel.Footer>
<SelectPanel.SecondaryButton>Edit labels</SelectPanel.SecondaryButton>
<SelectPanel.SecondaryAction variant="button">Edit labels</SelectPanel.SecondaryAction>
</SelectPanel.Footer>
</SelectPanel>
</>
Expand Down
11 changes: 6 additions & 5 deletions src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ export const AsyncWithSuspendedList = () => {
<React.Suspense fallback={<SelectPanel.Loading>Fetching labels...</SelectPanel.Loading>}>
<SuspendedActionList query={query} />
<SelectPanel.Footer>
<SelectPanel.SecondaryButton>Edit labels</SelectPanel.SecondaryButton>
<SelectPanel.SecondaryAction variant="link" href="/settings">
Edit labels
</SelectPanel.SecondaryAction>
</SelectPanel.Footer>
</React.Suspense>
</SelectPanel>
Expand Down Expand Up @@ -556,7 +558,7 @@ export const WithFilterButtons = () => {
Try a different search term
</SelectPanel.Message>
) : (
<ActionList selectionVariant="single">
<ActionList>
{itemsToShow.map(item => (
<ActionList.Item
key={item.id}
Expand All @@ -571,10 +573,9 @@ export const WithFilterButtons = () => {
)}

<SelectPanel.Footer>
{/* @ts-ignore TODO as prop is not identified by button? */}
<SelectPanel.SecondaryButton as="a" href={`/${selectedFilter}`}>
<SelectPanel.SecondaryAction variant="link" href={`/${selectedFilter}`}>
View all {selectedFilter}
</SelectPanel.SecondaryButton>
</SelectPanel.SecondaryAction>
</SelectPanel.Footer>
</SelectPanel>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const InstantSelectionVariant = () => {
))}
</ActionList>
<SelectPanel.Footer>
<SelectPanel.SecondaryButton>Edit tags</SelectPanel.SecondaryButton>
<SelectPanel.SecondaryAction variant="button">Edit tags</SelectPanel.SecondaryAction>
</SelectPanel.Footer>
</SelectPanel>
</>
Expand Down
16 changes: 12 additions & 4 deletions src/drafts/SelectPanel2/stories/SelectPanel.playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ export default {
args: {
title: 'Select labels',
selectionVariant: 'multiple',
secondaryActionVariant: 'button',
},
argTypes: {
secondaryButtonText: {
name: 'Secondary button text',
secondaryActionVariant: {
name: 'Secondary action variant',
type: 'enum',
options: ['button', 'link', 'checkbox'],
},
secondaryActionText: {
name: 'Secondary action text',
type: 'string',
},
},
Expand Down Expand Up @@ -125,8 +131,10 @@ export const Playground: StoryFn = args => {
)}

<SelectPanel.Footer>
{args.secondaryButtonText ? (
<SelectPanel.SecondaryButton>{args.secondaryButtonText}</SelectPanel.SecondaryButton>
{args.secondaryActionText ? (
<SelectPanel.SecondaryAction variant={args.secondaryActionVariant}>
{args.secondaryActionText}
</SelectPanel.SecondaryAction>
) : null}
</SelectPanel.Footer>
</SelectPanel>
Expand Down

0 comments on commit 996475f

Please sign in to comment.