Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
448a776
refactor(styled-react): update how we re-export components
joshblack Sep 4, 2025
d6817df
chore: add changesets
joshblack Sep 4, 2025
8fe405d
chore(lint): fix eslint error
joshblack Sep 4, 2025
3c52b30
chore: disable Box eslint warning in styled-react
joshblack Sep 4, 2025
8e84f1f
chore: remove eslint-disable
joshblack Sep 4, 2025
ab6d98c
refactor: add tests for styled-react entrypoint
joshblack Sep 5, 2025
c34dff9
test: add tests for each entrypoint
joshblack Sep 5, 2025
4907e80
docs: add architecture docs
joshblack Sep 5, 2025
ad98370
chore: clean up lint errors
joshblack Sep 5, 2025
02c3a4e
chore: add changesets
joshblack Sep 5, 2025
3c876dc
chore: add changeset
joshblack Sep 5, 2025
7094882
Merge branch 'main' into refactor/update-styled-react-entrypoint
joshblack Sep 5, 2025
1c864f6
docs: fix markdownlint errors
joshblack Sep 5, 2025
6e2373e
Merge branch 'refactor/update-styled-react-entrypoint' of github.com:…
joshblack Sep 5, 2025
064b24a
chore: remove BranchName from styled-react
joshblack Sep 5, 2025
967c7e3
Merge branch 'main' into refactor/update-styled-react-entrypoint
joshblack Sep 5, 2025
b457e81
chore: remove ProgressBar
joshblack Sep 5, 2025
9bd61a5
Merge branch 'main' into refactor/update-styled-react-entrypoint
joshblack Sep 8, 2025
a540961
Merge branch 'refactor/update-styled-react-entrypoint' of github.com:…
joshblack Sep 8, 2025
ba0563a
chore: remove details component
joshblack Sep 8, 2025
e84b0e5
Merge branch 'main' into refactor/update-styled-react-entrypoint
joshblack Sep 8, 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/chubby-colts-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/styled-react': patch
---

Refactor ToggleSwitch export type to match original type from @primer/react
5 changes: 5 additions & 0 deletions .changeset/great-hats-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Add ProgressBarItemProps and ProgressBarItemProps type exports to @primer/react
5 changes: 5 additions & 0 deletions .changeset/metal-cups-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/styled-react': patch
---

Remove several components that have no sx usage
5 changes: 5 additions & 0 deletions .changeset/nine-cobras-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Add ToggleSwitchProps type to package exports
17 changes: 17 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const config = defineConfig([
'contributor-docs/adrs/*',
'examples/codesandbox/**/*',
'packages/react/src/utils/polymorphic.ts',
'packages/styled-react/src/polymorphic.d.ts',
'**/storybook-static',
'**/CHANGELOG.md',
'**/node_modules/**/*',
Expand Down Expand Up @@ -376,6 +377,22 @@ const config = defineConfig([
'@typescript-eslint/triple-slash-reference': 'off',
},
},

// packages/styled-react overrides
{
files: ['packages/styled-react/**/*.{ts,tsx}'],
rules: {
'primer-react/no-unnecessary-components': 'off',
},
},
{
files: ['packages/styled-react/**/*.test.{ts,tsx}'],
rules: {
'github/a11y-aria-label-is-well-formatted': 'off',
'github/a11y-svg-has-accessible-name': 'off',
'primer-react/direct-slot-children': 'off',
},
},
])

export default tseslint.config(config)
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/react/src/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ type StyledProgressContainerProps = {
animated?: boolean
} & SxProp

export type ProgressBarItems = React.HTMLAttributes<HTMLSpanElement> & {
export type ProgressBarItemProps = React.HTMLAttributes<HTMLSpanElement> & {
'aria-label'?: string
className?: string
} & ProgressProp &
SxProp

export const Item = forwardRef<HTMLSpanElement, ProgressBarItems>(
export const Item = forwardRef<HTMLSpanElement, ProgressBarItemProps>(
(
{
progress,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/ProgressBar/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ProgressBar as Bar, Item} from './ProgressBar'

export type {ProgressBarProps} from './ProgressBar'
export type {ProgressBarProps, ProgressBarItemProps} from './ProgressBar'

/**
* Collection of ProgressBar related components.
Expand Down
244 changes: 121 additions & 123 deletions packages/react/src/ToggleSwitch/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,135 +72,133 @@ const LineIcon: React.FC<React.PropsWithChildren<InnerIconProps>> = ({size}) =>
</svg>
)

const ToggleSwitch = React.forwardRef<HTMLButtonElement, React.PropsWithChildren<ToggleSwitchProps>>(
function ToggleSwitch(props, ref) {
const {
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby,
defaultChecked,
disabled,
loading,
checked,
onChange,
onClick,
buttonType = 'button',
size = 'medium',
statusLabelPosition = 'start',
loadingLabelDelay = 2000,
loadingLabel = 'Loading',
className,
...rest
} = props
const isControlled = typeof checked !== 'undefined'
const [isOn, setIsOn] = useProvidedStateOrCreate<boolean>(checked, onChange, Boolean(defaultChecked))
const acceptsInteraction = !disabled && !loading

const [isLoadingLabelVisible, setIsLoadingLabelVisible] = React.useState(false)
const loadingLabelId = useId('loadingLabel')

const {safeSetTimeout} = useSafeTimeout()

const handleToggleClick: MouseEventHandler = useCallback(
e => {
if (disabled || loading) return

if (!isControlled) {
setIsOn(!isOn)
}
onClick && onClick(e)
},
[disabled, isControlled, loading, onClick, setIsOn, isOn],
)

useEffect(() => {
if (onChange && isControlled && !disabled) {
onChange(Boolean(checked))
const ToggleSwitch = React.forwardRef<HTMLButtonElement, ToggleSwitchProps>(function ToggleSwitch(props, ref) {
const {
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby,
defaultChecked,
disabled,
loading,
checked,
onChange,
onClick,
buttonType = 'button',
size = 'medium',
statusLabelPosition = 'start',
loadingLabelDelay = 2000,
loadingLabel = 'Loading',
className,
...rest
} = props
const isControlled = typeof checked !== 'undefined'
const [isOn, setIsOn] = useProvidedStateOrCreate<boolean>(checked, onChange, Boolean(defaultChecked))
const acceptsInteraction = !disabled && !loading

const [isLoadingLabelVisible, setIsLoadingLabelVisible] = React.useState(false)
const loadingLabelId = useId('loadingLabel')

const {safeSetTimeout} = useSafeTimeout()

const handleToggleClick: MouseEventHandler = useCallback(
e => {
if (disabled || loading) return

if (!isControlled) {
setIsOn(!isOn)
}
}, [onChange, checked, isControlled, disabled])

useEffect(() => {
if (!loading && isLoadingLabelVisible) {
setIsLoadingLabelVisible(false)
} else if (loading && !isLoadingLabelVisible) {
safeSetTimeout(() => {
setIsLoadingLabelVisible(true)
}, loadingLabelDelay)
}
}, [loading, isLoadingLabelVisible, loadingLabelDelay, safeSetTimeout])

let switchButtonDescribedBy = loadingLabelId
if (ariaDescribedby) switchButtonDescribedBy = `${switchButtonDescribedBy} ${ariaDescribedby}`

return (
<div className={clsx(classes.ToggleSwitch, className)} data-status-label-position={statusLabelPosition} {...rest}>
<VisuallyHidden>
<AriaStatus announceOnShow id={loadingLabelId}>
{isLoadingLabelVisible && loadingLabel}
</AriaStatus>
</VisuallyHidden>

{loading ? (
<div className={classes.LoadingSpinner} data-status-label-position={statusLabelPosition}>
<Spinner size="small" srText={null} />
</div>
) : null}

<span
className={classes.StatusText}
data-size={size}
data-disabled={!acceptsInteraction}
aria-hidden="true"
onClick={handleToggleClick}
>
<span className={classes.StatusTextItem} data-hidden={!isOn}>
On
</span>
<span className={classes.StatusTextItem} data-hidden={isOn}>
Off
</span>
onClick && onClick(e)
},
[disabled, isControlled, loading, onClick, setIsOn, isOn],
)

useEffect(() => {
if (onChange && isControlled && !disabled) {
onChange(Boolean(checked))
}
}, [onChange, checked, isControlled, disabled])

useEffect(() => {
if (!loading && isLoadingLabelVisible) {
setIsLoadingLabelVisible(false)
} else if (loading && !isLoadingLabelVisible) {
safeSetTimeout(() => {
setIsLoadingLabelVisible(true)
}, loadingLabelDelay)
}
}, [loading, isLoadingLabelVisible, loadingLabelDelay, safeSetTimeout])

let switchButtonDescribedBy = loadingLabelId
if (ariaDescribedby) switchButtonDescribedBy = `${switchButtonDescribedBy} ${ariaDescribedby}`

return (
<div className={clsx(classes.ToggleSwitch, className)} data-status-label-position={statusLabelPosition} {...rest}>
<VisuallyHidden>
<AriaStatus announceOnShow id={loadingLabelId}>
{isLoadingLabelVisible && loadingLabel}
</AriaStatus>
</VisuallyHidden>

{loading ? (
<div className={classes.LoadingSpinner} data-status-label-position={statusLabelPosition}>
<Spinner size="small" srText={null} />
</div>
) : null}

<span
className={classes.StatusText}
data-size={size}
data-disabled={!acceptsInteraction}
aria-hidden="true"
onClick={handleToggleClick}
>
<span className={classes.StatusTextItem} data-hidden={!isOn}>
On
</span>

<button
ref={ref}
// eslint-disable-next-line react/button-has-type
type={buttonType}
className={classes.SwitchButton}
data-size={size}
data-checked={isOn}
data-disabled={!acceptsInteraction}
onClick={handleToggleClick}
aria-labelledby={ariaLabelledby}
aria-describedby={isLoadingLabelVisible || ariaDescribedby ? switchButtonDescribedBy : undefined}
aria-pressed={isOn}
aria-disabled={!acceptsInteraction}
>
<div className={classes.SwitchButtonContent} aria-hidden="true">
<div
className={`${classes.IconContainer} ${classes.LineIconContainer}`}
data-checked={isOn}
data-disabled={!acceptsInteraction}
>
<LineIcon size={size} />
</div>
<div
className={`${classes.IconContainer} ${classes.CircleIconContainer}`}
data-checked={isOn}
data-disabled={!acceptsInteraction}
>
<CircleIcon size={size} />
</div>
<span className={classes.StatusTextItem} data-hidden={isOn}>
Off
</span>
</span>

<button
ref={ref}
// eslint-disable-next-line react/button-has-type
type={buttonType}
className={classes.SwitchButton}
data-size={size}
data-checked={isOn}
data-disabled={!acceptsInteraction}
onClick={handleToggleClick}
aria-labelledby={ariaLabelledby}
aria-describedby={isLoadingLabelVisible || ariaDescribedby ? switchButtonDescribedBy : undefined}
aria-pressed={isOn}
aria-disabled={!acceptsInteraction}
>
<div className={classes.SwitchButtonContent} aria-hidden="true">
<div
className={`${classes.IconContainer} ${classes.LineIconContainer}`}
data-checked={isOn}
data-disabled={!acceptsInteraction}
>
<LineIcon size={size} />
</div>
<div
className={classes.ToggleKnob}
className={`${classes.IconContainer} ${classes.CircleIconContainer}`}
data-checked={isOn}
data-disabled={!acceptsInteraction}
aria-hidden="true"
/>
</button>
</div>
)
},
)
>
<CircleIcon size={size} />
</div>
</div>
<div
className={classes.ToggleKnob}
data-checked={isOn}
data-disabled={!acceptsInteraction}
aria-hidden="true"
/>
</button>
</div>
)
})

if (__DEV__) {
ToggleSwitch.displayName = 'ToggleSwitch'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] =
"Portal",
"type PortalProps",
"ProgressBar",
"type ProgressBarItemProps",
"type ProgressBarProps",
"Radio",
"RadioGroup",
Expand Down Expand Up @@ -177,6 +178,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] =
"type TimelineItemsProps",
"type TimelineProps",
"ToggleSwitch",
"type ToggleSwitchProps",
"Token",
"type TokenProps",
"Tooltip",
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export type {PopoverProps, PopoverContentProps} from './Popover'
export {default as Portal, registerPortalRoot} from './Portal'
export type {PortalProps} from './Portal'
export {ProgressBar} from './ProgressBar'
export type {ProgressBarProps} from './ProgressBar'
export type {ProgressBarProps, ProgressBarItemProps} from './ProgressBar'
export {default as RadioGroup} from './RadioGroup'
export type {RelativeTimeProps} from './RelativeTime'
export {default as RelativeTime} from './RelativeTime'
Expand All @@ -151,6 +151,7 @@ export type {StateLabelProps} from './StateLabel'
export {default as SubNav} from './SubNav'
export type {SubNavProps, SubNavLinkProps, SubNavLinksProps} from './SubNav'
export {default as ToggleSwitch} from './ToggleSwitch'
export type {ToggleSwitchProps} from './ToggleSwitch'
export {default as TextInput} from './TextInput'
export type {TextInputProps} from './TextInput'
export {default as TextInputWithTokens} from './TextInputWithTokens'
Expand Down
Loading
Loading