Skip to content
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

SelectPanel2: Add SelectPanel.SecondaryAction #4145

Merged
merged 5 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -389,6 +395,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 @@ -401,38 +408,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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is but maybe with "a" instead of Link?

Copy link
Member Author

@siddharthkp siddharthkp Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, that also throws some type errors 🤔

Will put an task to triage and clean up at the end of the project!

<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 @@ -536,7 +591,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
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 @@ -574,7 +576,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 @@ -589,10 +591,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
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
Loading