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

SelectPanel: Add variant=modal #5817

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fbd625c
wip
siddharthkp Mar 24, 2025
1b75223
call onCancel before OnOpenChange for modal + click outside
siddharthkp Mar 26, 2025
cc94b85
data-variant → data-responsive
siddharthkp Mar 26, 2025
be1a38a
disable anchor animation
siddharthkp Mar 26, 2025
9e3f6de
update snapshot
siddharthkp Mar 26, 2025
ddd5d7d
changeset
siddharthkp Mar 27, 2025
1f73c86
add e2e test
siddharthkp Mar 27, 2025
0cd98d7
test(vrt): update snapshots
siddharthkp Mar 27, 2025
93ab700
set primer_react_css_modules_ga for responsive vrt test
siddharthkp Mar 27, 2025
0cd4c67
Merge branch 'selectpanel-modal' of github.com:primer/react into sele…
siddharthkp Mar 27, 2025
def876a
test(vrt): update snapshots
siddharthkp Mar 27, 2025
9d48d0a
was trying to be smart, backfired in dotcom
siddharthkp Mar 27, 2025
ab31fb9
forgot to show x icon in the sx prop
siddharthkp Mar 28, 2025
0169cf0
data-responsive → responsiveVariant
siddharthkp Mar 28, 2025
15f98a3
bring back the css + add media query
siddharthkp Mar 28, 2025
6e34ef4
Merge branch 'main' into selectpanel-modal
siddharthkp Mar 28, 2025
054f0eb
reply changes back on top of main
siddharthkp Mar 28, 2025
1a336ed
update jest snapshot
siddharthkp Mar 28, 2025
d9640e3
test(vrt): update snapshots
siddharthkp Mar 28, 2025
2191153
Center the panel
hectahertz Apr 2, 2025
6e3c5bf
Clean up showCancelSaveButtons logic
hectahertz Apr 3, 2025
3c54302
Show the close icon on modals
hectahertz Apr 3, 2025
174401f
Cancel and X buttons don't need onCancel function
hectahertz Apr 3, 2025
583dd69
Add AsSingleSelectModal story
hectahertz Apr 3, 2025
b4928a3
Single select modal selection logic
hectahertz Apr 3, 2025
e906b5b
Realign close button
hectahertz Apr 3, 2025
a76b712
Simplify AsMultiSelectModal story
hectahertz Apr 3, 2025
973ebab
Render radio icons on single select modal select panel
hectahertz Apr 3, 2025
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/selectpanel-modal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

SelectPanel: Add variant="modal"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/react/src/ActionList/ActionList.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@
visibility: hidden;
}

.SingleSelectRadio {
margin-right: var(--base-size-8);
}

/* button or a tag */

/* [ [spacer] [leadingAction] [leadingVisual] [content] ] */
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/ActionList/Selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Box from '../Box'
import {useFeatureFlag} from '../FeatureFlags'
import classes from './ActionList.module.css'
import {actionListCssModulesFlag} from './featureflag'
import Radio from '../Radio'

type SelectionProps = Pick<ActionListItemProps, 'selected' | 'className'>
export const Selection: React.FC<React.PropsWithChildren<SelectionProps>> = ({selected, className}) => {
Expand All @@ -34,6 +35,17 @@ export const Selection: React.FC<React.PropsWithChildren<SelectionProps>> = ({se
}
}

if (selectionVariant === 'radio') {
return (
<VisualContainer className={className}>
{/* This is just a way to get the visuals from Radio, but it should be ignored in terms of accessibility */}
<div aria-hidden="true">
<Radio className={classes.SingleSelectRadio} value="unused" checked={selected} />
</div>
</VisualContainer>
)
}

if (selectionVariant === 'single' || listRole === 'menu') {
if (enabled) {
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/ActionList/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export type ActionListProps = React.PropsWithChildren<{
/**
* Whether multiple Items or a single Item can be selected.
*/
selectionVariant?: 'single' | 'multiple'
selectionVariant?: 'single' | 'radio' | 'multiple'
/**
* Display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
width={width}
top={currentResponsiveVariant === 'anchored' ? position?.top || 0 : undefined}
left={currentResponsiveVariant === 'anchored' ? position?.left || 0 : undefined}
data-variant={currentResponsiveVariant}
responsiveVariant={variant.narrow === 'fullscreen' ? 'fullscreen' : undefined}
anchorSide={position?.anchorSide}
className={className}
preventOverflow={preventOverflow}
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/Overlay/Overlay.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@
{
"name": "sx",
"type": "SystemStyleObject"
},
{
"name": "responsiveVariant",
"type": "'fullscreen'",
"required": false,
"description": "Optional prop to set responsive variant for narrow screen sizes",
"defaultValue": ""
}
],
"subcomponents": []
Expand Down
16 changes: 9 additions & 7 deletions packages/react/src/Overlay/Overlay.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,14 @@
visibility: hidden;
}

&:where([data-variant='fullscreen']) {
top: 0;
left: 0;
width: 100vw;
height: 100vh;
margin: 0;
border-radius: unset;
&:where([data-responsive='fullscreen']) {
@media screen and (--viewportRange-narrow) {
Copy link
Member Author

@siddharthkp siddharthkp Mar 27, 2025

Choose a reason for hiding this comment

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

Note for reviewer: I changed full screen to only be a responsive thing instead of variant you can apply whenever. data-variantdata-responsive

top: 0;
left: 0;
width: 100vw;
height: 100vh;
margin: 0;
border-radius: unset;
}
}
}
19 changes: 12 additions & 7 deletions packages/react/src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ const StyledOverlay = toggleStyledComponent(
max-width: calc(100vw - 2rem);
}

&:where([data-variant='fullscreen']) {
Copy link
Member Author

@siddharthkp siddharthkp Mar 26, 2025

Choose a reason for hiding this comment

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

Note for reviewer: Overlay's css module is in GA, so we don't need this css in sx anymore

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 let's keep this for now just in case, in theory the ga FF could still be turned off at any moment? The team can remove this when they remove the FF entirely from this component 🙏

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought we weren't adding adding css for new features? Based on #5761 (comment)

I recently added this css in #5759, so thought I'll clean it up as well. yay/nay?

Copy link
Member

Choose a reason for hiding this comment

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

yeah I think the issue here is the styles won't be added at all because if FF is not enabled the Overlay classname will not be added so these styles will never make it in (why you need that FF on that other test), the correct way of adding new features without sx styling is to always have it go to css modules, for example if you created a separate class and added it to both (enabled, not enabled) implementations

top: 0;
left: 0;
width: 100vw;
height: 100vh;
margin: 0;
border-radius: unset;
&:where([data-responsive='fullscreen']) {
@media screen and (max-width: calc(768px - 0.02px)) {
top: 0;
left: 0;
width: 100vw;
height: 100vh;
margin: 0;
border-radius: unset;
}
}

${sx};
Expand All @@ -127,6 +129,7 @@ type BaseOverlayProps = {
role?: AriaRole
children?: React.ReactNode
className?: string
responsiveVariant?: 'fullscreen' // we only support fullscreen today but we might add bottomsheet in the future
}

type OwnOverlayProps = Merge<StyledOverlayProps, BaseOverlayProps>
Expand Down Expand Up @@ -265,6 +268,7 @@ const Overlay = React.forwardRef<HTMLDivElement, internalOverlayProps>(
role = 'none',
visibility = 'visible',
width = 'auto',
responsiveVariant,
...props
},
forwardedRef,
Expand Down Expand Up @@ -322,6 +326,7 @@ const Overlay = React.forwardRef<HTMLDivElement, internalOverlayProps>(
right={right}
height={height}
visibility={visibility}
data-responsive={responsiveVariant}
{...props}
/>
</Portal>
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/SelectPanel/SelectPanel.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
{
"name": "onCancel",
"type": "() => void",
"description": "(Narrow screens) Callback when the user hits cancel or close",
"description": "(Narrow screens and variant=modal) Callback when the user hits cancel or close",
"defaultValue": ""
},
{
Expand All @@ -145,6 +145,12 @@
"defaultValue": "",
"description": "See [TextInput props](/react/TextInput#props)."
},
{
"name": "variant",
"type": "'anchored' | 'modal'",
"description": "Anchored by default, SelectPanel can be opened as a modal",
"defaultValue": "'anchored'"
},
{
"name": "footer",
"type": "string | React.ReactElement",
Expand Down
77 changes: 77 additions & 0 deletions packages/react/src/SelectPanel/SelectPanel.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -857,3 +857,80 @@ export const WithOnCancel = () => {
</FormControl>
)
}

export const AsMultiSelectModal = () => {
const [intialSelection, setInitialSelection] = React.useState<ItemInput[]>(items.slice(1, 3))

const [selected, setSelected] = React.useState<ItemInput[]>(intialSelection)
const [filter, setFilter] = React.useState('')
const filteredItems = items.filter(
item =>
// design guidelines say to always show selected items in the list
selected.some(selectedItem => selectedItem.text === item.text) ||
// then filter the rest
item.text.toLowerCase().startsWith(filter.toLowerCase()),
)
// design guidelines say to sort selected items first
const selectedItemsSortedFirst = filteredItems.sort((a, b) => {
const aIsSelected = selected.some(selectedItem => selectedItem.text === a.text)
const bIsSelected = selected.some(selectedItem => selectedItem.text === b.text)
if (aIsSelected && !bIsSelected) return -1
if (!aIsSelected && bIsSelected) return 1
return 0
})

const [open, setOpen] = useState(false)
React.useEffect(() => {
if (!open) setInitialSelection(selected) // set initialSelection for next time
}, [open, selected])

return (
<SelectPanel
variant="modal"
title="Select labels"
placeholder="Select labels"
subtitle="Use labels to organize issues and pull requests"
renderAnchor={({children, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} {...anchorProps} aria-haspopup="dialog">
{children}
</Button>
)}
open={open}
onOpenChange={setOpen}
items={selectedItemsSortedFirst}
selected={selected}
onSelectedChange={setSelected}
onCancel={() => setSelected(intialSelection)}
onFilterChange={setFilter}
width="medium"
/>
)
}

export const AsSingleSelectModal = () => {
const [selected, setSelected] = useState<ItemInput | undefined>(undefined)
const [filter, setFilter] = useState('')
const [open, setOpen] = useState(false)
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))

return (
<SelectPanel
variant="modal"
title="Select labels"
placeholder="Select labels"
subtitle="Use labels to organize issues and pull requests"
renderAnchor={({children, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} {...anchorProps} aria-haspopup="dialog">
{children}
</Button>
)}
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
width="medium"
/>
)
}
20 changes: 17 additions & 3 deletions packages/react/src/SelectPanel/SelectPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
.Header {
display: flex;
justify-content: space-between;
align-items: center;
align-items: flex-start;
padding-top: var(--base-size-8);
padding-right: var(--base-size-8);
padding-left: var(--base-size-8);
Expand Down Expand Up @@ -124,15 +124,29 @@
@media screen and (--viewportRange-narrow) {
display: inline-grid;
}

&:where([data-variant='modal'] &) {
display: inline-grid;
}
}

.ResponsiveFooter {
display: none;
padding: var(--base-size-16);
gap: var(--stack-gap-condensed);
justify-content: right;

@media screen and (--viewportRange-narrow) {
display: flex;
gap: var(--stack-gap-condensed);
justify-content: right;
}

&:where([data-variant='modal'] &) {
display: flex;
}
}

.Backdrop {
position: absolute;
inset: 0;
background-color: var(--overlay-backdrop-bgColor);
}
Loading
Loading