Skip to content

Commit 585c5fa

Browse files
hectahertzCopilot
andauthored
Remove use of useResponsiveValue hook - SegmentedControl (#7134)
Co-authored-by: Copilot <[email protected]>
1 parent 4a1013f commit 585c5fa

File tree

5 files changed

+323
-73
lines changed

5 files changed

+323
-73
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
SegmentedControl: Remove useResponsiveValue hook from fullWidth and variant props to use `getResponsiveAttributes` instead.

packages/react/src/SegmentedControl/SegmentedControl.module.css

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
.SegmentedControl {
2+
/* TODO: use primitive `control.medium.size` when it is available instead of '32px' */
3+
--segmented-control-icon-width: 32px;
4+
25
display: inline-flex;
36

47
/* TODO: use primitive `control.{small|medium}.size` when it is available */
@@ -10,9 +13,114 @@
1013
border: var(--borderWidth-thin) solid var(--controlTrack-borderColor-rest, transparent);
1114
border-radius: var(--borderRadius-medium);
1215

13-
&:where([data-full-width]) {
16+
/* Responsive full-width */
17+
&[data-full-width='true'] {
1418
display: flex;
1519
width: 100%;
20+
21+
--segmented-control-icon-width: 100%;
22+
}
23+
24+
&[data-full-width='false'] {
25+
display: inline-flex;
26+
width: auto;
27+
28+
--segmented-control-icon-width: 32px;
29+
}
30+
31+
@media (--viewportRange-narrow) {
32+
&[data-full-width-narrow='true'] {
33+
display: flex;
34+
width: 100%;
35+
36+
--segmented-control-icon-width: 100%;
37+
}
38+
39+
&[data-full-width-narrow='false'] {
40+
display: inline-flex;
41+
width: auto;
42+
43+
--segmented-control-icon-width: 32px;
44+
}
45+
}
46+
47+
@media (--viewportRange-regular) {
48+
&[data-full-width-regular='true'] {
49+
display: flex;
50+
width: 100%;
51+
52+
--segmented-control-icon-width: 100%;
53+
}
54+
55+
&[data-full-width-regular='false'] {
56+
display: inline-flex;
57+
width: auto;
58+
59+
--segmented-control-icon-width: 32px;
60+
}
61+
}
62+
63+
@media (--viewportRange-wide) {
64+
&[data-full-width-wide='true'] {
65+
display: flex;
66+
width: 100%;
67+
68+
--segmented-control-icon-width: 100%;
69+
}
70+
71+
&[data-full-width-wide='false'] {
72+
display: inline-flex;
73+
width: auto;
74+
75+
--segmented-control-icon-width: 32px;
76+
}
77+
78+
&[data-full-width-regular='true']:not([data-full-width-wide='true']) {
79+
display: inline-flex;
80+
width: auto;
81+
82+
--segmented-control-icon-width: 32px;
83+
}
84+
}
85+
86+
/* Hide when dropdown variant is active */
87+
&[data-variant='dropdown'] {
88+
display: none;
89+
}
90+
91+
/* Handle hideLabels variant - hide button text */
92+
&[data-variant='hideLabels'] .Text {
93+
display: none;
94+
}
95+
96+
@media (--viewportRange-narrow) {
97+
&[data-variant-narrow='dropdown'] {
98+
display: none;
99+
}
100+
101+
&[data-variant-narrow='hideLabels'] .Text {
102+
display: none;
103+
}
104+
}
105+
106+
@media (--viewportRange-regular) {
107+
&[data-variant-regular='dropdown'] {
108+
display: none;
109+
}
110+
111+
&[data-variant-regular='hideLabels'] .Text {
112+
display: none;
113+
}
114+
}
115+
116+
@media (--viewportRange-wide) {
117+
&[data-variant-wide='dropdown'] {
118+
display: none;
119+
}
120+
121+
&[data-variant-wide='hideLabels'] .Text {
122+
display: none;
123+
}
16124
}
17125

18126
&:where([data-size='small']) {
@@ -22,6 +130,33 @@
22130
}
23131
}
24132

133+
.DropdownContainer {
134+
display: none;
135+
136+
/* Show when dropdown variant is active */
137+
&[data-variant='dropdown'] {
138+
display: block;
139+
}
140+
141+
@media (--viewportRange-narrow) {
142+
&[data-variant-narrow='dropdown'] {
143+
display: block;
144+
}
145+
}
146+
147+
@media (--viewportRange-regular) {
148+
&[data-variant-regular='dropdown'] {
149+
display: block;
150+
}
151+
}
152+
153+
@media (--viewportRange-wide) {
154+
&[data-variant-wide='dropdown'] {
155+
display: block;
156+
}
157+
}
158+
}
159+
25160
.Item {
26161
position: relative;
27162
display: block;
@@ -138,12 +273,7 @@
138273
}
139274

140275
.IconButton {
141-
/* TODO: use primitive `control.medium.size` when it is available instead of '32px' */
142-
width: 32px;
143-
144-
.SegmentedControl:where([data-full-width]) & {
145-
width: 100%;
146-
}
276+
width: var(--segmented-control-icon-width, 32px);
147277
}
148278

149279
.Content {
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type {Meta, StoryFn} from '@storybook/react-vite'
2+
import React from 'react'
3+
import {SegmentedControl} from './SegmentedControl'
4+
import {EyeIcon, FileCodeIcon, PeopleIcon} from '@primer/octicons-react'
5+
6+
const meta: Meta = {
7+
title: 'Components/SegmentedControl/Responsive Tests',
8+
parameters: {
9+
layout: 'padded',
10+
controls: {expanded: true},
11+
},
12+
}
13+
14+
export default meta
15+
16+
/**
17+
* Test responsive fullWidth behavior.
18+
* Resize the viewport to see the control change width at different breakpoints.
19+
*/
20+
export const FullWidthResponsive: StoryFn = () => (
21+
<div>
22+
<p style={{marginBottom: '16px'}}>Full width: yes (narrow) → no (regular + wide)</p>
23+
<SegmentedControl aria-label="File view" fullWidth={{narrow: true, regular: false, wide: false}}>
24+
<SegmentedControl.Button defaultSelected leadingVisual={EyeIcon}>
25+
Preview
26+
</SegmentedControl.Button>
27+
<SegmentedControl.Button leadingVisual={FileCodeIcon}>Raw</SegmentedControl.Button>
28+
<SegmentedControl.Button leadingVisual={PeopleIcon}>Blame</SegmentedControl.Button>
29+
</SegmentedControl>
30+
</div>
31+
)
32+
33+
FullWidthResponsive.parameters = {
34+
docs: {
35+
description: {
36+
story:
37+
'The control fills the full width on **narrow** viewports and uses inline width on **regular** and **wide** viewports.',
38+
},
39+
},
40+
}
41+
42+
/**
43+
* Test responsive variant behavior with hideLabels.
44+
* Resize the viewport to see labels hide/show at different breakpoints.
45+
*/
46+
export const VariantHideLabelsResponsive: StoryFn = () => (
47+
<div>
48+
<p style={{marginBottom: '16px'}}>Labels: hidden (narrow) → visible (regular + wide)</p>
49+
<SegmentedControl aria-label="File view" variant={{narrow: 'hideLabels', regular: 'default', wide: 'default'}}>
50+
<SegmentedControl.Button defaultSelected leadingVisual={EyeIcon}>
51+
Preview
52+
</SegmentedControl.Button>
53+
<SegmentedControl.Button leadingVisual={FileCodeIcon}>Raw</SegmentedControl.Button>
54+
<SegmentedControl.Button leadingVisual={PeopleIcon}>Blame</SegmentedControl.Button>
55+
</SegmentedControl>
56+
</div>
57+
)
58+
59+
VariantHideLabelsResponsive.parameters = {
60+
docs: {
61+
description: {
62+
story:
63+
'Labels are **hidden** (icon-only) on narrow viewports and **visible** on regular and wide viewports. Note: leadingVisual prop is required for hideLabels variant.',
64+
},
65+
},
66+
}
67+
68+
/**
69+
* Test responsive variant behavior with dropdown.
70+
* Resize the viewport to see the control switch between dropdown and buttons.
71+
*/
72+
export const VariantDropdownResponsive: StoryFn = () => (
73+
<div>
74+
<p style={{marginBottom: '16px'}}>Variant: dropdown (narrow) → buttons (regular + wide)</p>
75+
<SegmentedControl aria-label="File view" variant={{narrow: 'dropdown', regular: 'default', wide: 'default'}}>
76+
<SegmentedControl.Button defaultSelected leadingVisual={EyeIcon}>
77+
Preview
78+
</SegmentedControl.Button>
79+
<SegmentedControl.Button leadingVisual={FileCodeIcon}>Raw</SegmentedControl.Button>
80+
<SegmentedControl.Button leadingVisual={PeopleIcon}>Blame</SegmentedControl.Button>
81+
</SegmentedControl>
82+
</div>
83+
)
84+
85+
VariantDropdownResponsive.parameters = {
86+
docs: {
87+
description: {
88+
story:
89+
'Renders as a **dropdown menu** on narrow viewports and as **segmented buttons** on regular and wide viewports.',
90+
},
91+
},
92+
}
93+
94+
/**
95+
* Test responsive variant behavior when only the narrow breakpoint is specified.
96+
*/
97+
export const VariantDropdownNarrowOnly: StoryFn = () => (
98+
<div>
99+
<p style={{marginBottom: '16px'}}>Variant: dropdown (narrow) → buttons (others inherit default)</p>
100+
<SegmentedControl aria-label="File view" variant={{narrow: 'dropdown'}}>
101+
<SegmentedControl.Button defaultSelected leadingVisual={EyeIcon}>
102+
Preview
103+
</SegmentedControl.Button>
104+
<SegmentedControl.Button leadingVisual={FileCodeIcon}>Raw</SegmentedControl.Button>
105+
<SegmentedControl.Button leadingVisual={PeopleIcon}>Blame</SegmentedControl.Button>
106+
</SegmentedControl>
107+
</div>
108+
)
109+
110+
VariantDropdownNarrowOnly.parameters = {
111+
docs: {
112+
description: {
113+
story:
114+
'Only the **narrow** breakpoint sets the dropdown variant; wider breakpoints fall back to the default segmented buttons.',
115+
},
116+
},
117+
}
118+
119+
/**
120+
* Test complex responsive behavior combining fullWidth and variant.
121+
*/
122+
export const ComplexResponsive: StoryFn = () => (
123+
<div>
124+
<p style={{marginBottom: '16px'}}>
125+
Complex: full-width + icon-only (narrow) → full-width + labels (regular) → inline + labels (wide)
126+
</p>
127+
<SegmentedControl
128+
aria-label="File view"
129+
fullWidth={{narrow: true, regular: true, wide: false}}
130+
variant={{narrow: 'hideLabels', regular: 'default', wide: 'default'}}
131+
>
132+
<SegmentedControl.Button defaultSelected leadingVisual={EyeIcon}>
133+
Preview
134+
</SegmentedControl.Button>
135+
<SegmentedControl.Button leadingVisual={FileCodeIcon}>Raw</SegmentedControl.Button>
136+
<SegmentedControl.Button leadingVisual={PeopleIcon}>Blame</SegmentedControl.Button>
137+
</SegmentedControl>
138+
</div>
139+
)
140+
141+
ComplexResponsive.parameters = {
142+
docs: {
143+
description: {
144+
story:
145+
'Complex: **full-width + icon-only** (narrow) → **full-width + labels** (regular) → **inline + labels** (wide)',
146+
},
147+
},
148+
}

0 commit comments

Comments
 (0)