Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9f95c06
subtle variant, action slot
liuliu-dev Aug 19, 2026
7b4621e
divider
liuliu-dev Aug 19, 2026
ca86081
Merge branch 'main' into liuliu/segmentedcontrol-add-subtle-and-actio…
liuliu-dev Aug 19, 2026
fd8848b
variant styles
liuliu-dev Aug 20, 2026
9d2577f
Merge branch 'liuliu/segmentedcontrol-add-subtle-and-action-slot' of …
liuliu-dev Aug 20, 2026
db27ab0
Merge branch 'main' into liuliu/segmentedcontrol-add-subtle-and-actio…
liuliu-dev Aug 20, 2026
fda8f81
copilot feedback
liuliu-dev Aug 21, 2026
3e02e39
chore: auto-fix lint and formatting issues
liuliu-dev Aug 21, 2026
79a6a51
format
liuliu-dev Aug 21, 2026
b7d0490
Merge branch 'liuliu/segmentedcontrol-add-subtle-and-action-slot' of …
liuliu-dev Aug 21, 2026
8ab63ed
type
liuliu-dev Aug 21, 2026
82489ef
clean up and more generic action api
liuliu-dev Aug 21, 2026
414fc90
type fix
liuliu-dev Aug 21, 2026
46b7ec8
story name
liuliu-dev Aug 24, 2026
02ee20d
Potential fix for pull request finding
liuliu-dev Aug 24, 2026
ab72db0
data-size, story names, playground
liuliu-dev Aug 24, 2026
8f50f45
Merge branch 'liuliu/segmentedcontrol-add-subtle-and-action-slot' of …
liuliu-dev Aug 24, 2026
94bae63
docs
liuliu-dev Aug 24, 2026
61bdfe7
change action so it will render in the dropdown
liuliu-dev Aug 25, 2026
fe586fe
Potential fix for pull request finding
liuliu-dev Aug 25, 2026
89a890c
make subtle responsive too
liuliu-dev Aug 26, 2026
4b625f1
props fix
liuliu-dev Aug 26, 2026
6fd21a2
test(vrt): update snapshots
liuliu-dev Aug 26, 2026
047f826
test
liuliu-dev Aug 26, 2026
7397074
test(vrt): update snapshots
liuliu-dev Aug 26, 2026
df8fc63
restore unchanged snapshots
liuliu-dev Aug 26, 2026
4f9f335
divider
liuliu-dev Aug 27, 2026
6c229ba
story update
liuliu-dev Aug 27, 2026
5b786d7
test(vrt): update snapshots
liuliu-dev Aug 27, 2026
b11f4ae
restore unrelated actionmenu snapshot
liuliu-dev Aug 27, 2026
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/segmented-control-subtle-add-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

SegmentedControl: Add a subtle visual variant with group dividers and an optional trailing action child.
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.
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.
14 changes: 14 additions & 0 deletions e2e/components/SegmentedControl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@ const stories = [
title: 'Variant Narrow Action Menu',
id: 'components-segmentedcontrol-features--variant-narrow-action-menu',
},
{
title: 'Variant Narrow Action Menu With Action',
id: 'components-segmentedcontrol-features--variant-narrow-action-menu-with-action',
},
{
title: 'Variant Narrow Hide Labels',
id: 'components-segmentedcontrol-features--variant-narrow-hide-labels',
},
{
title: 'Variant Subtle',
id: 'components-segmentedcontrol-features--variant-subtle',
},
{
title: 'Controlled',
id: 'components-segmentedcontrol-features--controlled',
Expand Down Expand Up @@ -102,6 +110,12 @@ test.describe('SegmentedControl', () => {
await page.keyboard.press('Shift+Tab')
await expect(page).toHaveScreenshot(`SegmentedControl.${story.title}.${theme}.middle.selected.focus.png`)
}

if (story.title === 'Variant Narrow Action Menu With Action') {
await page.setViewportSize({width: viewports['primer.breakpoint.xs'], height: 768})
await page.locator('[data-component="ActionMenu.Button"]').click()
await expect(page).toHaveScreenshot(`SegmentedControl.${story.title}.${theme}.menu.png`)
}
})
})
}
Expand Down
59 changes: 56 additions & 3 deletions packages/react/src/SegmentedControl/SegmentedControl.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
{
"id": "components-segmentedcontrol-features--variant-narrow-action-menu"
},
{
"id": "components-segmentedcontrol-features--variant-narrow-action-menu-with-action"
},
{
"id": "components-segmentedcontrol-features--fullwidth-narrow"
},
Expand All @@ -33,6 +36,9 @@
},
{
"id": "components-segmentedcontrol-features--associated-with-a-label-and-caption"
},
{
"id": "components-segmentedcontrol-features--variant-subtle"
}
],
"importPath": "@primer/react",
Expand Down Expand Up @@ -69,9 +75,9 @@
},
{
"name": "variant",
"type": "| 'default' | { narrow?: 'hideLabels' | 'dropdown' | 'default' regular?: 'hideLabels' | 'dropdown' | 'default' wide?: 'hideLabels' | 'dropdown' | 'default' }",
"type": "| 'default' | 'subtle' | { narrow?: 'hideLabels' | 'dropdown' | 'default' | 'subtle' regular?: 'hideLabels' | 'dropdown' | 'default' | 'subtle' wide?: 'hideLabels' | 'dropdown' | 'default' | 'subtle' }",
"defaultValue": "'default'",
"description": "Configure alternative ways to render the control when it gets rendered in tight spaces"
"description": "Configure the control's visual treatment or responsive behavior in tight spaces."
},
{
"name": "size",
Expand Down Expand Up @@ -177,6 +183,53 @@
"defaultValue": ""
}
]
},
{
"name": "SegmentedControl.Divider",
"props": [
{
"name": "className",
"type": "string",
"required": false
},
{
"name": "style",
"type": "React.CSSProperties",
"required": false
}
]
},
{
"name": "SegmentedControl.Action",
"props": [
{
"name": "label",
"type": "string",
"required": true,
"description": "Accessible label for the action icon button."
},
{
"name": "icon",
"type": "Component",
"required": true,
"description": "The icon rendered by the action button."
},
{
"name": "onClick",
"type": "() => void",
"description": "The handler that gets called when the action button is clicked."
},
{
"name": "disabled",
"type": "boolean",
"description": "Whether the action is disabled."
},
{
"name": "loading",
"type": "boolean",
"description": "Whether the action is in a loading state."
}
]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
gap: var(--base-size-4);
}

.ResetButton {
margin-top: var(--base-size-24);
}

@media screen and (min-width: 768px) {
.LabelAndCaptionContainer {
flex-direction: row;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {useState} from 'react'
import type {Meta} from '@storybook/react-vite'
import {PlusIcon, EyeIcon, FileCodeIcon, PeopleIcon} from '@primer/octicons-react'
import {SegmentedControl} from '.'
import {EyeIcon, FileCodeIcon, PeopleIcon} from '@primer/octicons-react'
import {Button} from '../Button'
import Text from '../Text'
import classes from './SegmentedControl.features.stories.module.css'

Expand Down Expand Up @@ -34,6 +35,20 @@ export const WithCounterLabels = () => (
</SegmentedControl>
)

export const VariantSubtle = () => (
<SegmentedControl aria-label="View" variant="subtle">
<SegmentedControl.Button defaultSelected count={5}>
All
</SegmentedControl.Button>
<SegmentedControl.Divider />
<SegmentedControl.Button count={3}>Active</SegmentedControl.Button>
<SegmentedControl.Button count={10}>Review requests</SegmentedControl.Button>
<SegmentedControl.Divider />
<SegmentedControl.Button count={2}>Done</SegmentedControl.Button>
</SegmentedControl>
)
VariantSubtle.storyName = '[variant: subtle] Low emphasis'

export const Controlled = () => {
const [selectedIndex, setSelectedIndex] = useState(0)
const handleChange = (i: number) => {
Expand Down Expand Up @@ -78,6 +93,56 @@ export const VariantNarrowActionMenu = () => (
)
VariantNarrowActionMenu.storyName = '[variant: narrow] Action menu'

export const VariantNarrowActionMenuWithAction = () => {
const initialViews = [
{id: 'all', label: 'All'},
{id: 'active', label: 'Active'},
{id: 'review-requests', label: 'Review requests'},
{id: 'done', label: 'Done'},
]
const [views, setViews] = useState(initialViews)
const [selectedViewId, setSelectedViewId] = useState(initialViews[0].id)

const handleAddView = () => {
setViews(currentViews => {
const viewNumber = currentViews.length - 3
return [...currentViews, {id: `new-view-${viewNumber}`, label: `New view ${viewNumber}`}]
})
}

const handleChange = (index: number) => {
const nextSelectedViewId = views.at(index)?.id
if (nextSelectedViewId) setSelectedViewId(nextSelectedViewId)
}

const handleReset = () => {
setViews(initialViews)
setSelectedViewId(initialViews[0].id)
}

return (
<>
<SegmentedControl
aria-label="View"
onChange={handleChange}
variant={{narrow: 'dropdown', regular: 'subtle', wide: 'subtle'}}
>
{views.flatMap(view => [
...(view.label === 'Active' ? [<SegmentedControl.Divider key={`${view.label}-divider`} />] : []),
<SegmentedControl.Button key={view.id} selected={view.id === selectedViewId}>
{view.label}
</SegmentedControl.Button>,
])}
<SegmentedControl.Action label="Add view" icon={PlusIcon} onClick={handleAddView} />
</SegmentedControl>
<Button className={classes.ResetButton} size="small" onClick={handleReset}>
Reset views
</Button>
</>
)
}
VariantNarrowActionMenuWithAction.storyName = '[variant: narrow] Action menu with trailing action'

export const FullwidthNarrow = () => (
<SegmentedControl aria-label="File view" fullWidth={{narrow: true, regular: false, wide: false}}>
<SegmentedControl.Button defaultSelected aria-label={'Preview'} leadingVisual={EyeIcon}>
Expand Down
Loading
Loading