Skip to content

Commit

Permalink
Merge 6f91cb8 into 9723020
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthkp authored Feb 20, 2024
2 parents 9723020 + 6f91cb8 commit 65e58c2
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-sloths-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

experimental/SelectPanel: Add back button
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React from 'react'
import {SelectPanel} from './SelectPanel'
import {ActionList, ActionMenu, Avatar, Box, Button, Text} from '../../index'
import {ArrowRightIcon, EyeIcon, GitBranchIcon, TriangleDownIcon, GearIcon} from '@primer/octicons-react'
import {ActionList, ActionMenu, Avatar, Box, Button, Octicon, Text} from '../../index'
import {
ArrowRightIcon,
EyeIcon,
GitBranchIcon,
TriangleDownIcon,
GearIcon,
GitPullRequestIcon,
GitMergeIcon,
GitPullRequestDraftIcon,
} from '@primer/octicons-react'
import data from './mock-story-data'

export default {
Expand Down Expand Up @@ -435,8 +444,8 @@ export const OpenFromMenu = () => {
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>

<SelectPanel
variant="modal"
title="Custom"
open={selectPanelOpen}
onSubmit={() => {
Expand All @@ -450,6 +459,13 @@ export const OpenFromMenu = () => {
setMenuOpen(true)
}}
>
<SelectPanel.Header
onBack={() => {
setSelectedCustomEvents(initialCustomEvents)
setSelectPanelOpen(false)
setMenuOpen(true)
}}
/>
<ActionList>
{itemsToShow.map(item => (
<ActionList.Item
Expand Down Expand Up @@ -639,6 +655,130 @@ export const ShortSelectPanel = () => {
)
}

export const NestedSelection = () => {
const [panelToShow, setPanelToShow] = React.useState<null | 'repos' | 'pull_requests'>(null)

const anchorRef = React.useRef<HTMLButtonElement>(null)

/* First level: Repo selection */
const [selectedRepo, setSelectedRepo] = React.useState<string>('')

const reposToShow = data.repos

/* Second level: Pull request selection */
const iconMap = {
open: <Octicon icon={GitPullRequestIcon} sx={{color: 'open.emphasis'}} />,
merged: <Octicon icon={GitMergeIcon} sx={{color: 'done.emphasis'}} />,
draft: <Octicon icon={GitPullRequestDraftIcon} />,
}

const initialSelectedPullRequestIds = ['4278']
const [selectedPullRequestIds, setSelectedPullRequestIds] = React.useState<string[]>(initialSelectedPullRequestIds)
/* Selection */
const onPullRequestSelect = (pullId: string) => {
if (!selectedPullRequestIds.includes(pullId)) setSelectedPullRequestIds([...selectedPullRequestIds, pullId])
else setSelectedPullRequestIds(selectedPullRequestIds.filter(id => id !== pullId))
}

return (
<>
<h1>Nested selection</h1>

<Button
ref={anchorRef}
onClick={() => setPanelToShow('repos')}
variant="invisible"
trailingAction={GearIcon}
sx={{width: '200px', '[data-component=buttonContent]': {justifyContent: 'start'}}}
>
Development
</Button>

<ActionList>
{data.pulls
.filter(pull => selectedPullRequestIds.includes(pull.id))
.map(pull => (
<ActionList.Item key={pull.name}>
<ActionList.LeadingVisual>{iconMap[pull.status as keyof typeof iconMap]}</ActionList.LeadingVisual>
{pull.name}
<ActionList.Description variant="inline">#{pull.id}</ActionList.Description>
<ActionList.Description variant="block">{pull.description}</ActionList.Description>
</ActionList.Item>
))}
</ActionList>

<SelectPanel
open={panelToShow === 'repos'}
anchorRef={anchorRef}
title="Link a pull request or branch"
description="Select a repository first to search for pull requests orbranches."
selectionVariant="instant"
onSubmit={() => setPanelToShow('pull_requests')}
onCancel={() => setPanelToShow(null)}
>
<SelectPanel.Header>
<SelectPanel.SearchInput placeholder="Search (not implemented in demo)" />
</SelectPanel.Header>

<ActionList>
{reposToShow.map(repo => (
<ActionList.Item
key={repo.name}
selected={selectedRepo === `${repo.org}/${repo.name}`}
onSelect={() => setSelectedRepo(`${repo.org}/${repo.name}`)}
>
<ActionList.LeadingVisual>
<Avatar src={`https://github.com/${repo.org}.png`} />
</ActionList.LeadingVisual>
{repo.org}/{repo.name}
<ActionList.Description>{repo.description}</ActionList.Description>
<ActionList.TrailingVisual>
<ArrowRightIcon />
</ActionList.TrailingVisual>
</ActionList.Item>
))}
</ActionList>

<SelectPanel.Footer />
</SelectPanel>

<SelectPanel
open={panelToShow === 'pull_requests'}
anchorRef={anchorRef}
title={selectedRepo}
description="Link a pull request"
selectionVariant="multiple"
onSubmit={() => setPanelToShow(null)}
onCancel={() => {
setSelectedPullRequestIds(initialSelectedPullRequestIds)
setPanelToShow('repos')
}}
>
<SelectPanel.Header onBack={() => setPanelToShow('repos')}>
<SelectPanel.SearchInput placeholder="Search (not implemented in demo)" />
</SelectPanel.Header>

<ActionList>
{data.pulls.map(pull => (
<ActionList.Item
key={pull.name}
selected={selectedPullRequestIds.includes(pull.id)}
onSelect={() => onPullRequestSelect(pull.id)}
>
<ActionList.LeadingVisual>{iconMap[pull.status as keyof typeof iconMap]}</ActionList.LeadingVisual>
{pull.name}
<ActionList.Description variant="inline">#{pull.id}</ActionList.Description>
<ActionList.Description variant="block">{pull.description}</ActionList.Description>
</ActionList.Item>
))}
</ActionList>

<SelectPanel.Footer />
</SelectPanel>
</>
)
}

// ----- Suspense implementation details ----

const cache = new Map()
Expand Down
43 changes: 30 additions & 13 deletions packages/react/src/drafts/SelectPanel2/SelectPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import {SearchIcon, XCircleFillIcon, XIcon, FilterRemoveIcon, AlertIcon} from '@primer/octicons-react'
import {SearchIcon, XCircleFillIcon, XIcon, FilterRemoveIcon, AlertIcon, ArrowLeftIcon} from '@primer/octicons-react'
import {FocusKeys} from '@primer/behaviors'

import type {ButtonProps, TextInputProps, ActionListProps, LinkProps, CheckboxProps} from '../../index'
Expand Down Expand Up @@ -192,7 +192,10 @@ const Panel: React.FC<SelectPanelProps> = ({
// 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()
if (internalOpen) {
const searchInput = document.querySelector('dialog[open] input') as HTMLInputElement | undefined
searchInput?.focus()
}
}, [internalOpen])

/* Anchored */
Expand Down Expand Up @@ -316,7 +319,7 @@ const SelectPanelButton = React.forwardRef<HTMLButtonElement, ButtonProps>((prop
return <Button ref={anchorRef} {...props} />
})

const SelectPanelHeader: React.FC<React.PropsWithChildren> = ({children, ...props}) => {
const SelectPanelHeader: React.FC<React.PropsWithChildren & {onBack?: () => void}> = ({children, onBack, ...props}) => {
const [slots, childrenWithoutSlots] = useSlots(children, {
searchInput: SelectPanelSearchInput,
})
Expand All @@ -343,18 +346,32 @@ const SelectPanelHeader: React.FC<React.PropsWithChildren> = ({children, ...prop
marginBottom: slots.searchInput ? 2 : 0,
}}
>
<Box sx={{marginLeft: 2, marginTop: description ? '2px' : 0}}>
{/* heading element is intentionally hardcoded to h1, it is not customisable
<Box sx={{display: 'flex'}}>
{onBack ? (
<Tooltip text="Back" direction="s">
<IconButton
type="button"
variant="invisible"
icon={ArrowLeftIcon}
aria-label="Back"
onClick={() => onBack()}
/>
</Tooltip>
) : null}

<Box sx={{marginLeft: onBack ? 1 : 2, marginTop: description ? '2px' : 0}}>
{/* heading element is intentionally hardcoded to h1, it is not customisable
see https://github.com/github/primer/issues/2578 for context
*/}
<Heading as="h1" id={`${panelId}--title`} sx={{fontSize: 14, fontWeight: 600}}>
{title}
</Heading>
{description ? (
<Text id={`${panelId}--description`} sx={{fontSize: 0, color: 'fg.muted', display: 'block'}}>
{description}
</Text>
) : null}
<Heading as="h1" id={`${panelId}--title`} sx={{fontSize: 14, fontWeight: 600}}>
{title}
</Heading>
{description ? (
<Text id={`${panelId}--description`} sx={{fontSize: 0, color: 'fg.muted', display: 'block'}}>
{description}
</Text>
) : null}
</Box>
</Box>

<Box>
Expand Down
59 changes: 59 additions & 0 deletions packages/react/src/drafts/SelectPanel2/mock-story-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,65 @@ const data = {
{id: 'v35.0.0', name: 'v35.0.0'},
{id: 'v34.0.0', name: 'v34.0.0'},
],
repos: [
{
org: 'github',
name: 'primer',
description: "The internal hub for GitHub's open-source design system",
},
{
org: 'github',
name: 'github',
description: 'You’re lookin’ at it.',
},
{
org: 'primer',
name: 'react',
description: "An implementation of GitHub's Primer Design System using React",
},
{
org: 'primer',
name: 'primitives',
description: 'Color, typography, and spacing primitives in json',
},
{
org: 'primer',
name: 'brand',
description: 'React components and Primitives for GitHub marketing websites',
},
{
org: 'facebook',
name: 'react',
description: 'The library for web and native user interfaces',
},
{
org: 'primer',
name: 'view_components',
description: 'ViewComponents for the Primer Design System',
},
],
pulls: [
{
id: '4286',
name: 'chore(deps): bump @mdx-js/mdx from 1.6.22 to 3.0.1',
description: 'Opened 14 hours ago',
status: 'open',
},
{id: '4278', name: 'Address a few v8 color bugs', description: 'Opened 4 days ago', status: 'open'},
{id: '4277', name: 'SelectPanel2: Responsive variants', description: 'Opened 4 days ago', status: 'draft'},
{
name: 'SelectPanel2: Submit panel when an item is selected with Enter',
id: '4265',
description: 'Merged last week',
status: 'merged',
},
{
name: 'test(e2e): add e2e test for SelectPanel2 default story',
id: '4279',
description: 'Opened 3 days ago',
status: 'open',
},
],
}

export default data

0 comments on commit 65e58c2

Please sign in to comment.