Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/replace-usetheme-with-theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": major
---

Replaces `useTheme` usage with `theme`. If an application uses a custom theme that modifies one of the following 5 tokens, they will be reset to the default theme values. (`space.2, colors.success.fg, colors.border.default, colors.border.muted, animation.easeOutCubic`)
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type React from 'react'
import {useState, useCallback} from 'react'
import type {Meta} from '@storybook/react-vite'
import {useTheme} from '..'
import {Button} from '../Button'
import {ActionMenu} from '../ActionMenu'
import {ActionList} from '../ActionList'
Expand All @@ -15,18 +14,17 @@ export default {

export const ShorthandHook = () => {
const confirm = useConfirm()
const {theme} = useTheme()
const onButtonClick = useCallback(
async (event: React.MouseEvent) => {
if (
(await confirm({title: 'Are you sure?', content: 'Do you really want to turn this button green?'})) &&
event.target instanceof HTMLElement
) {
event.target.style.color = theme?.colors.success.fg ?? 'green'
event.target.style.color = 'var(--fgColor-success)'
event.target.textContent = "I'm green!"
}
},
[confirm, theme],
[confirm],
)
return (
<div className={classes.ButtonContainer}>
Expand Down
6 changes: 2 additions & 4 deletions packages/react/src/LabelGroup/LabelGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {get} from '../constants'
import VisuallyHidden from '../_VisuallyHidden'
import {AnchoredOverlay} from '../AnchoredOverlay'
import {Button, IconButton} from '../Button'
import {useTheme} from '../ThemeProvider'
import theme from '../theme'
import classes from './LabelGroup.module.css'

export type LabelGroupProps = {
Expand Down Expand Up @@ -171,9 +171,7 @@ const LabelGroup: React.FC<React.PropsWithChildren<LabelGroupProps>> = ({
toJSON: () => undefined,
})

const {theme} = useTheme()

const overlayPaddingPx = parseInt(get('space.2')(theme), 10)
const overlayPaddingPx = parseInt(theme.space[2], 10)

const hiddenItemIds = Object.keys(visibilityMap).filter(key => !visibilityMap[key])

Expand Down
8 changes: 3 additions & 5 deletions packages/react/src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import type {ComponentPropsWithRef, ReactElement} from 'react'
import React, {useEffect, useRef} from 'react'
import useLayoutEffect from '../utils/useIsomorphicLayoutEffect'
import {get} from '../constants'
import type {AriaRole, Merge} from '../utils/types'
import type {TouchOrMouseEvent} from '../hooks'
import {useOverlay} from '../hooks'
import Portal from '../Portal'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import type {AnchorSide} from '@primer/behaviors'
import {useTheme} from '../ThemeProvider'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {useFeatureFlag} from '../FeatureFlags'
import classes from './Overlay.module.css'
import {clsx} from 'clsx'
import theme from '../theme'

type StyledOverlayProps = {
width?: keyof typeof widthMap
Expand Down Expand Up @@ -192,9 +191,8 @@ const Overlay = React.forwardRef<HTMLDivElement, internalOverlayProps>(
): ReactElement => {
const overlayRef = useRef<HTMLDivElement>(null)
useRefObjectAsForwardedRef(forwardedRef, overlayRef)
const {theme} = useTheme()
const slideAnimationDistance = parseInt(get('space.2')(theme).replace('px', ''))
const slideAnimationEasing = get('animation.easeOutCubic')(theme)
const slideAnimationDistance = parseInt(theme.space[2], 10)
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

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

The parseInt function should specify a radix (base 10) to avoid potential parsing issues. While you've added the radix parameter, the original code was parsing a string with 'px' suffix, but theme.space[2] may not have the 'px' suffix that the original code was handling with .replace('px', '').

Suggested change
const slideAnimationDistance = parseInt(theme.space[2], 10)
const slideAnimationDistance = parseInt(String(theme.space[2]).replace('px', ''), 10)

Copilot uses AI. Check for mistakes.

Copy link
Member Author

@siddharthkp siddharthkp Oct 6, 2025

Choose a reason for hiding this comment

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

This is fine because theme.space[2] is hardcoded to '8px'

parseInt('8px', 10) = 8

const slideAnimationEasing = theme.animation.easeOutCubic

useOverlay({
overlayRef,
Expand Down
4 changes: 1 addition & 3 deletions packages/react/src/SegmentedControl/SegmentedControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type {SegmentedControlIconButtonProps} from './SegmentedControlIconButton
import SegmentedControlIconButton from './SegmentedControlIconButton'
import {ActionList} from '../ActionList'
import {ActionMenu} from '../ActionMenu'
import {useTheme} from '../ThemeProvider'
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
import {useResponsiveValue} from '../hooks/useResponsiveValue'
import type {WidthOnlyViewportRangeKeys} from '../utils/types/ViewportRangeKeys'
Expand Down Expand Up @@ -40,7 +39,6 @@ const Root: React.FC<React.PropsWithChildren<SegmentedControlProps>> = ({
...rest
}) => {
const segmentedControlContainerRef = useRef<HTMLUListElement>(null)
const {theme} = useTheme()
const isUncontrolled =
onChange === undefined ||
React.Children.toArray(children).some(
Expand Down Expand Up @@ -176,7 +174,7 @@ const Root: React.FC<React.PropsWithChildren<SegmentedControlProps>> = ({
selected: index === selectedIndex,
style: {
'--separator-color':
index === selectedIndex || index === selectedIndex - 1 ? 'transparent' : theme?.colors.border.default,
index === selectedIndex || index === selectedIndex - 1 ? 'transparent' : 'var(--borderColor-default)',
...child.props.style,
},
}
Expand Down
7 changes: 2 additions & 5 deletions packages/react/src/UnderlineNav/UnderlineNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import React, {useRef, forwardRef, useCallback, useState, useEffect} from 'react
import {UnderlineNavContext} from './UnderlineNavContext'
import type {ResizeObserverEntry} from '../hooks/useResizeObserver'
import {useResizeObserver} from '../hooks/useResizeObserver'
import {useTheme} from '../ThemeProvider'
import type {ChildWidthArray, ResponsiveProps, ChildSize} from './types'
import VisuallyHidden from '../_VisuallyHidden'
import {moreBtnStyles, getDividerStyle, menuStyles, menuItemStyles, baseMenuStyles, baseMenuMinWidth} from './styles'
import {moreBtnStyles, dividerStyles, menuStyles, menuItemStyles, baseMenuStyles, baseMenuMinWidth} from './styles'
import {UnderlineItemList, UnderlineWrapper, LoadingCounter, GAP} from '../internal/components/UnderlineTabbedInterface'
import styled from 'styled-components'
import {Button} from '../Button'
Expand Down Expand Up @@ -151,7 +150,6 @@ export const UnderlineNav = forwardRef(
const containerRef = React.useRef<HTMLUListElement>(null)
const disclosureWidgetId = useId()

const {theme} = useTheme()
const [isWidgetOpen, setIsWidgetOpen] = useState(false)
const [iconsVisible, setIconsVisible] = useState<boolean>(true)
const [childWidthArray, setChildWidthArray] = useState<ChildWidthArray>([])
Expand Down Expand Up @@ -298,7 +296,6 @@ export const UnderlineNav = forwardRef(
return (
<UnderlineNavContext.Provider
value={{
theme,
setChildrenWidth,
setNoIconChildrenWidth,
loadingCounters,
Expand All @@ -311,7 +308,7 @@ export const UnderlineNav = forwardRef(
{listItems}
{menuItems.length > 0 && (
<MoreMenuListItem ref={moreMenuRef}>
{!onlyMenuVisible && <div style={getDividerStyle(theme)}></div>}
{!onlyMenuVisible && <div style={dividerStyles}></div>}
<Button
ref={moreMenuBtnRef}
sx={moreBtnStyles}
Expand Down
3 changes: 0 additions & 3 deletions packages/react/src/UnderlineNav/UnderlineNavContext.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import type React from 'react'
import {createContext} from 'react'
import type {Theme} from '../ThemeProvider'

export const UnderlineNavContext = createContext<{
theme: Theme | undefined
setChildrenWidth: React.Dispatch<{text: string; width: number}>
setNoIconChildrenWidth: React.Dispatch<{text: string; width: number}>
loadingCounters: boolean
iconsVisible: boolean
}>({
theme: {},
setChildrenWidth: () => null,
setNoIconChildrenWidth: () => null,
loadingCounters: false,
Expand Down
9 changes: 4 additions & 5 deletions packages/react/src/UnderlineNav/styles.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type {Theme} from '../ThemeProvider'
import type {BetterSystemStyleObject} from '../sx'
import {getAnchoredPosition} from '@primer/behaviors'

export const getDividerStyle = (theme?: Theme) => ({
export const dividerStyles = {
display: 'inline-block',
borderLeft: '1px solid',
width: '1px',
borderLeftColor: `${theme?.colors.border.muted}`,
marginRight: '4px',
borderLeftColor: 'var(--borderColor-muted)',
marginRight: 'var(--base-size-4)',
height: '24px', // The height of the divider - reference from Figma
})
}

export const moreBtnStyles = {
//set margin 0 here because safari puts extra margin around the button, rest is to reset style to make it look like a list element
Expand Down
4 changes: 1 addition & 3 deletions packages/react/src/stories/useFocusZone.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Link from '../Link'
import {FocusKeys} from '@primer/behaviors'
import type {Direction} from '@primer/behaviors'
import {useFocusZone} from '../hooks/useFocusZone'
import {useTheme} from '../ThemeProvider'
import classes from './FocusZoneStories.module.css'

export default {
Expand Down Expand Up @@ -482,15 +481,14 @@ export const ActiveDescendant = () => {

const containerRef = useRef<HTMLElement>(null)
const controllingElementRef = useRef<HTMLElement>(null)
const {theme: themeFromContext} = useTheme()

useFocusZone({
containerRef,
activeDescendantFocus: controllingElementRef,
bindKeys: FocusKeys.ArrowVertical,
onActiveDescendantChanged: (current, previous) => {
if (current) {
current.style.outline = `2px solid ${themeFromContext?.colors.accent.fg}`
current.style.outline = `2px solid var(--fgColor-accent)`
}
if (previous) {
previous.style.outline = ''
Expand Down
Loading