Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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/fresh-points-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': major
---

Remove sx Props and BoxWithFallBack from Timeline component.
4 changes: 0 additions & 4 deletions e2e/components/Timeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ const stories = [
title: 'Timeline Break',
id: 'components-timeline-features--timeline-break',
},
{
title: 'SX Props',
id: 'components-timeline-dev--sx-props',
},
] as const

test.describe('Timeline', () => {
Expand Down
72 changes: 4 additions & 68 deletions packages/react/src/Timeline/Timeline.dev.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,74 +15,10 @@ export default {
},
} as Meta<ComponentProps<typeof Timeline>>

export const SxProps = () => (
<Timeline
sx={{
ml: 4,
}}
style={{border: '1px solid red'}}
>
<Timeline.Item
sx={{
pt: 2,
pb: 1,
}}
style={{border: '1px solid pink'}}
>
<Timeline.Badge
sx={{
backgroundColor: 'canvas.default',
}}
>
<Octicon icon={GitCommitIcon} aria-label="Commit" />
</Timeline.Badge>
<Timeline.Body
sx={{
color: 'fg.subtle',
fontSize: '14px',
width: '100%',
paddingRight: 5,
display: 'flex',
flexDirection: 'column',
}}
className="mt-0"
style={{border: '1px solid green'}}
>
This is a message
</Timeline.Body>
</Timeline.Item>
<Timeline.Item color="gray">
<Timeline.Badge
sx={{
bg: 'danger.emphasis',
}}
>
<Octicon icon={GitCommitIcon} aria-label="Commit" />
</Timeline.Badge>
<Timeline.Body
sx={{
color: 'fg.default',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
This is a message
</Timeline.Body>
</Timeline.Item>
<Timeline.Break
sx={{
borderWidth: '2px',
}}
style={{border: '1px solid gray'}}
/>
<Timeline.Item condensed={true}>
<Timeline.Badge
sx={{
mt: 2,
bg: 'accent.emphasis',
}}
>
export const Default = () => (
<Timeline>
<Timeline.Item>
<Timeline.Badge>
<Octicon icon={GitCommitIcon} aria-label="Commit" />
</Timeline.Badge>
<Timeline.Body>This is a message</Timeline.Body>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.BadgeWithDoneBackground {
background-color: var(--bgColor-done-emphasis);
}

.LinkWithBoldStyle {
font-weight: var(--base-text-weight-semibold);
color: var(--fgColor-default);
margin-right: var(--base-size-4);
}

.LinkWithBoldStyle:hover {
color: var(--fgColor-accent);
}
9 changes: 3 additions & 6 deletions packages/react/src/Timeline/Timeline.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Timeline from './Timeline'
import Octicon from '../Octicon'
import {GitBranchIcon, GitCommitIcon, GitMergeIcon} from '@primer/octicons-react'
import Link from '../Link'
import classes from './Timeline.features.stories.module.css'

export default {
title: 'Components/Timeline/Features',
Expand Down Expand Up @@ -53,7 +54,7 @@ export const CondensedItems = () => (
export const TimelineBreak = () => (
<Timeline>
<Timeline.Item>
<Timeline.Badge sx={{bg: 'done.emphasis'}}>
<Timeline.Badge className={classes.BadgeWithDoneBackground}>
<Octicon icon={GitMergeIcon} color="fg.onEmphasis" aria-label="Merged" />
</Timeline.Badge>
<Timeline.Body>This is a message</Timeline.Body>
Expand All @@ -75,11 +76,7 @@ export const WithInlineLinks = () => (
<Octicon icon={GitCommitIcon} aria-label="Commit" />
</Timeline.Badge>
<Timeline.Body>
<Link
href="#"
sx={{fontWeight: 'bold', color: 'fg.default', mr: 1, '&:hover': {color: 'var(--fgColor-accent)'}}}
muted
>
<Link href="#" className={classes.LinkWithBoldStyle} muted>
Monalisa
</Link>
enabled auto-merge (squash)
Expand Down
32 changes: 15 additions & 17 deletions packages/react/src/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import {clsx} from 'clsx'
import React from 'react'
import type {SxProp} from '../sx'
import classes from './Timeline.module.css'
import {BoxWithFallback} from '../internal/components/BoxWithFallback'

type StyledTimelineProps = {clipSidebar?: boolean; className?: string} & SxProp
type StyledTimelineProps = {clipSidebar?: boolean; className?: string}

export type TimelineProps = StyledTimelineProps & React.ComponentPropsWithoutRef<'div'>

const Timeline = React.forwardRef<HTMLDivElement, TimelineProps>(({clipSidebar, className, ...props}, forwardRef) => {
return (

Copilot AI Sep 16, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] Replacing BoxWithFallback with div removes the sx prop functionality, but the data-testid and other props are still being spread. Consider adding a comment explaining that sx props are no longer supported to help developers understand the breaking change.

Suggested change
return (
return (
{/* NOTE: The `sx` prop is no longer supported since replacing BoxWithFallback with div.
Any `sx` prop passed will be ignored. */}

Copilot uses AI. Check for mistakes.
<BoxWithFallback
<div
{...props}
className={clsx(className, classes.Timeline)}
ref={forwardRef}
Expand All @@ -21,19 +19,19 @@ const Timeline = React.forwardRef<HTMLDivElement, TimelineProps>(({clipSidebar,

Timeline.displayName = 'Timeline'

type StyledTimelineItemProps = {condensed?: boolean; className?: string} & SxProp
type StyledTimelineItemProps = {condensed?: boolean; className?: string}

/**
* @deprecated Use the `TimelineItemProps` type instead
*/
export type TimelineItemsProps = StyledTimelineItemProps & SxProp & React.ComponentPropsWithoutRef<'div'>
export type TimelineItemsProps = StyledTimelineItemProps & React.ComponentPropsWithoutRef<'div'>

export type TimelineItemProps = StyledTimelineItemProps & SxProp & React.ComponentPropsWithoutRef<'div'>
export type TimelineItemProps = StyledTimelineItemProps & React.ComponentPropsWithoutRef<'div'>

const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(
({condensed, className, ...props}, forwardRef) => {
return (
<BoxWithFallback
<div
{...props}
className={clsx(className, 'Timeline-Item', classes.TimelineItem)}
ref={forwardRef}
Expand All @@ -45,13 +43,15 @@ const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(

TimelineItem.displayName = 'TimelineItem'

export type TimelineBadgeProps = {children?: React.ReactNode; className?: string} & SxProp &
React.ComponentPropsWithoutRef<'div'>
export type TimelineBadgeProps = {
children?: React.ReactNode
className?: string
} & React.ComponentPropsWithoutRef<'div'>

const TimelineBadge = ({className, ...props}: TimelineBadgeProps) => {
return (
<div className={classes.TimelineBadgeWrapper}>
<BoxWithFallback {...props} className={clsx(className, classes.TimelineBadge)} />
<div {...props} className={clsx(className, classes.TimelineBadge)} />
</div>
)
}
Expand All @@ -61,23 +61,21 @@ TimelineBadge.displayName = 'Timeline.Badge'
export type TimelineBodyProps = {
/** Class name for custom styling */
className?: string
} & SxProp &
React.ComponentPropsWithoutRef<'div'>
} & React.ComponentPropsWithoutRef<'div'>

const TimelineBody = React.forwardRef<HTMLDivElement, TimelineBodyProps>(({className, ...props}, forwardRef) => {
return <BoxWithFallback {...props} className={clsx(className, classes.TimelineBody)} ref={forwardRef} />
return <div {...props} className={clsx(className, classes.TimelineBody)} ref={forwardRef} />
})

TimelineBody.displayName = 'TimelineBody'

export type TimelineBreakProps = {
/** Class name for custom styling */
className?: string
} & SxProp &
React.ComponentPropsWithoutRef<'div'>
} & React.ComponentPropsWithoutRef<'div'>

const TimelineBreak = React.forwardRef<HTMLDivElement, TimelineBreakProps>(({className, ...props}, forwardRef) => {
return <BoxWithFallback {...props} className={clsx(className, classes.TimelineBreak)} ref={forwardRef} />
return <div {...props} className={clsx(className, classes.TimelineBreak)} ref={forwardRef} />
})

TimelineBreak.displayName = 'TimelineBreak'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] =
"type TimelineBadgeProps",
"type TimelineBodyProps",
"type TimelineBreakProps",
"type TimelineItemProps",
"type TimelineItemsProps",
"type TimelineProps",
"ToggleSwitch",
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export type {
TimelineBodyProps,
TimelineBreakProps,
TimelineItemsProps,
TimelineItemProps,
} from './Timeline'
export {default as Token, IssueLabelToken} from './Token'
export type {TokenProps, IssueLabelTokenProps} from './Token'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,11 +399,21 @@ describe('@primer/react', () => {
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
})

test('Timeline.Break supports `sx` prop', () => {
render(<Timeline.Break data-testid="component" sx={{background: 'red'}} />)
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
})

test('Timeline.Item supports `sx` prop', () => {
render(<Timeline.Item data-testid="component" sx={{background: 'red'}} />)
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
})

test('Timeline.Body supports `sx` prop', () => {
render(<Timeline.Body data-testid="component" sx={{background: 'red'}} />)
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
})

test('Token supports `sx` prop', () => {
render(<Token data-testid="component" sx={{background: 'red'}} text="test" />)
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
Expand Down
42 changes: 40 additions & 2 deletions packages/styled-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
SubNav as PrimerSubNav,
type SubNavProps as PrimerSubNavProps,
type SubNavLinkProps as PrimerSubNavLinkProps,
Timeline as PrimerTimeline,
type TimelineProps as PrimerTimelineProps,
type TimelineItemProps as PrimerTimelineItemProps,
type TimelineBadgeProps as PrimerTimelineBadgeProps,
type TimelineBodyProps as PrimerTimelineBodyProps,
type TimelineBreakProps as PrimerTimelineBreakProps,
ToggleSwitch as PrimerToggleSwitch,
type ToggleSwitchProps as PrimerToggleSwitchProps,
type SegmentedControlProps as PrimerSegmentedControlProps,
Expand Down Expand Up @@ -84,13 +90,46 @@ const SubNav = Object.assign(SubNavImpl, {
Link: SubNavLink,
})

type TimelineProps = PrimerTimelineProps & SxProp
type TimelineItemProps = PrimerTimelineItemProps & SxProp
type TimelineBadgeProps = PrimerTimelineBadgeProps & SxProp
type TimelineBodyProps = PrimerTimelineBodyProps & SxProp
type TimelineBreakProps = PrimerTimelineBreakProps & SxProp

const TimelineImpl = forwardRef<HTMLDivElement, TimelineProps>(function Timeline(props, ref) {
return <Box as={PrimerTimeline} ref={ref} {...props} />
})

const TimelineItem = forwardRef<HTMLDivElement, TimelineItemProps>(function TimelineItem(props, ref) {
return <Box as={PrimerTimeline.Item} ref={ref} {...props} />
})

function TimelineBadge(props: TimelineBadgeProps) {
return <Box as={PrimerTimeline.Badge} {...props} />
}

const TimelineBody = forwardRef<HTMLDivElement, TimelineBodyProps>(function TimelineBody(props, ref) {
return <Box as={PrimerTimeline.Body} ref={ref} {...props} />
})

const TimelineBreak = forwardRef<HTMLDivElement, TimelineBreakProps>(function TimelineBreak(props, ref) {
return <Box as={PrimerTimeline.Break} ref={ref} {...props} />
})

const Timeline = Object.assign(TimelineImpl, {
Item: TimelineItem,
Badge: TimelineBadge,
Body: TimelineBody,
Break: TimelineBreak,
})

type ToggleSwitchProps = PrimerToggleSwitchProps & Omit<StyledProps, keyof PrimerToggleSwitchProps>

const ToggleSwitch = forwardRef<HTMLButtonElement, ToggleSwitchProps>(function ToggleSwitch(props, ref) {
return <Box as={PrimerToggleSwitch} ref={ref} {...props} />
})

export {SegmentedControl, StateLabel, SubNav, ToggleSwitch}
export {SegmentedControl, StateLabel, SubNav, Timeline, ToggleSwitch}

Copilot AI Sep 16, 2025

Copy link

Choose a reason for hiding this comment

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

The Timeline export is being moved from line 167 to line 132, but it's still being exported from the main @primer/react package on line 167. This creates a duplicate export which could cause confusion. Consider removing the original Timeline export from line 167 since it's now being re-exported from the styled-react wrapper.

Copilot uses AI. Check for mistakes.

export {
ActionList,
Expand Down Expand Up @@ -125,7 +164,6 @@ export {
Text,
Textarea,
TextInput,
Timeline,
Token,
Tooltip,
Truncate,
Expand Down
Loading