From 8ab5fd864eda8617a357fc88dae6579612328015 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Wed, 15 Jul 2026 13:32:44 -0700 Subject: [PATCH 1/6] Add Timeline.Actions narrow-viewport layout dev stories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add three dev stories under Components/Timeline/Dev/Actions Responsive that explore layout options for Timeline.Actions when the surrounding Timeline.Item is below 480px inline size, tracked in github/primer#6693: 1. Wrap below Body — Item switches to a 2-column grid so Actions land in the same column as Body, left-aligned with the body text. 2. Stack vertically — Actions become a right-aligned column with an enforced 8px gap from Body. 3. Baseline — current inline behavior, kept for side-by-side comparison. Each story uses a container-query-driven layout inside a resizable wrapper so Storybook viewport controls (or the drag handle) can drive the transition. No production Timeline code is touched. --- ....actions-responsive.dev.stories.module.css | 138 ++++++++++++ ...imeline.actions-responsive.dev.stories.tsx | 199 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css create mode 100644 packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css b/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css new file mode 100644 index 00000000000..f53e2c7eb6d --- /dev/null +++ b/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css @@ -0,0 +1,138 @@ +/* + Prototype styles for Timeline.Actions responsive behavior on narrow viewports. + Tracks: https://github.com/github/primer/issues/6693 + + Each story renders inside `.ResizableContainer` — drag the bottom-right corner + to resize the timeline horizontally and observe how the prototype option + responds. The container establishes `container-type: inline-size` on each + Timeline.Item via `.ItemContainer` so container queries can target item width. +*/ + +.ResizableContainer { + /* Fill whatever width Storybook's viewport / canvas gives us so the + container query below reacts to viewport changes (xsmall/small/medium/ + large in the Storybook viewport addon). The `resize: horizontal` handle + lets a reviewer additionally drag the width down independently. */ + box-sizing: border-box; + width: 100%; + min-width: 240px; + max-width: 1012px; + padding: var(--base-size-16); + overflow: auto; + resize: horizontal; + border: var(--borderWidth-thin) solid var(--borderColor-default); + border-radius: var(--borderRadius-medium); +} + +/* + Establishes an inline-size container per Timeline.Item so the option CSS + below can react to each item's own width. Uses an ANONYMOUS container query + (matches the CommentCard pattern in this repo) — named container queries + don't reliably work here because CSS Modules scopes `container-name` on the + declaration but leaves the identifier inside `@container` unscoped, so the + two never match. Anonymous queries bind to the nearest ancestor with + `container-type` set, which is exactly what we want. +*/ +.ItemContainer { + container-type: inline-size; +} + +/* --- Option 1: wrap Actions below Body when the item is narrow --- */ + +/* + At narrow widths switch the Item to a 2-column grid so Actions can align to + the Body's left edge regardless of whether Badge and Body share a row or + Body wraps under the Badge. The flex-based approach with `flex-wrap: wrap` + couldn't guarantee alignment because Body's wrap behavior depended on + content width — grid gives us a stable, predictable layout. +*/ +@container (width < 480px) { + .Option1Item { + display: grid; + grid-template-columns: auto 1fr; + grid-template-areas: + 'badge body' + '. actions'; + column-gap: var(--base-size-8); + row-gap: var(--base-size-8); + } + + .Option1Item > [class*='TimelineBadgeWrapper'] { + grid-area: badge; + } + + .Option1Item > [class*='TimelineBody'] { + grid-area: body; + } + + .Option1Actions { + grid-area: actions; + margin-left: 0; + justify-content: flex-start; + min-height: 0; + } +} + +/* --- Option 2: stack Actions vertically when the item is narrow --- */ + +@container (width < 480px) { + .Option2Actions { + /* Stack action children vertically inside the slot. */ + flex-direction: column; + align-items: flex-end; + /* Column layout doesn't need to match badge height. */ + min-height: 0; + /* Force a visible gap between Body text and the stacked Actions column + when Body has enough content to reach Actions' left edge. Overrides + the base `margin-left: auto`, which collapses to 0 when Body fills + the row. */ + margin-left: var(--base-size-8); + } +} + +/* --- Shared story styling (mirrors Timeline.features.stories.module.css) --- */ + +.Stack { + display: flex; + flex-direction: column; + gap: var(--base-size-16); +} + +.OptionCaption { + padding: var(--base-size-8) var(--base-size-12); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); + background-color: var(--bgColor-muted); + border-radius: var(--borderRadius-medium); +} + +.LinkWithBoldStyle { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +.CommitSha { + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +.ShaLink { + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + text-decoration: underline; +} + +.SignatureLabelVerified { + color: var(--fgColor-success); +} + +.IconSuccess { + color: var(--fgColor-success); +} + +.Timestamp { + color: var(--fgColor-muted); +} diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx b/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx new file mode 100644 index 00000000000..a9496005870 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx @@ -0,0 +1,199 @@ +/* + Prototypes for `Timeline.Actions` responsive/wrap behavior on narrow viewports. + Tracks: https://github.com/github/primer/issues/6693 + + Each story wraps the Timeline in a horizontally-resizable container. Drag the + bottom-right corner of the container to shrink/grow the Timeline width and + observe how the prototype option responds. + + Options mirror the three approaches enumerated in the issue: + 1. Wrap Actions below Body on narrow widths (container query on Timeline.Item). + 2. Stack Actions vertically when the row is narrow. + 3. Stay inline, do nothing (current behavior baseline). +*/ + +import type {Meta} from '@storybook/react-vite' +import type {ComponentProps, ReactNode} from 'react' +import Timeline from './Timeline' +import {CheckIcon, GitCommitIcon, GitMergeIcon, RepoPushIcon} from '@primer/octicons-react' +import Link from '../Link' +import {Button} from '../Button' +import Label from '../Label' +import BranchName from '../BranchName' +import Octicon from '../Octicon' +import classes from './Timeline.actions-responsive.dev.stories.module.css' + +export default { + title: 'Components/Timeline/Dev/Actions Responsive', + component: Timeline, + subcomponents: { + 'Timeline.Item': Timeline.Item, + 'Timeline.Body': Timeline.Body, + 'Timeline.Actions': Timeline.Actions, + }, +} as Meta> + +/** + * Wraps each `Timeline.Item` in a `.ItemContainer` element that establishes an + * inline-size container so prototype CSS can query the item's own width. + * Options 1 and 2 rely on this; Option 3 (baseline) also uses it so the DOM + * shape is identical across stories and the only variable is the CSS. + */ +function ResizableTimeline({children}: {children: ReactNode}) { + return ( +
{ + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {children} +
+ ) +} + +/** + * Renders the same set of realistic Timeline items used to stress-test each + * option. Cases cover: two-button actions, single-button + long body, a + * condensed multi-element row, and a worst-case long body + long actions. + */ +function ItemCases({itemClassName, actionsClassName}: {itemClassName?: string; actionsClassName?: string}) { + return ( + <> + {/* Case A: two-button actions, medium body. */} +
+ + + + + + + Monalisa + + merged via the queue into main with commit{' '} + + 01e49tb + {' '} + + just now + + + + + + + +
+ + {/* Case B: single-button actions, longer body. */} +
+ + + + + + + Monalisa + + force-pushed the main branch from{' '} + + 01e49tb + {' '} + to{' '} + + 02f50uc + {' '} + + 2 hours ago + + + + + + +
+ + {/* Case C: condensed row with multiple small elements. */} +
+ + + + + + + Update README.md with much longer commit subject to force wrapping + + + + + + + 3fbdc0 + + + +
+ + {/* Case D: torture test — long body + long, multi-element actions. */} +
+ + + + + + + Monalisa + + merged commit{' '} + + 01e49tb + {' '} + into release/2026-11 with a longer summary that mentions several + additional details about the change + + + + + + + +
+ + ) +} + +export const Option1WrapBelow = () => ( +
+

+ Option 1 — Actions wrap onto a new row below Body when the item is below 480px, left-aligned with the body + content. Drag the container edge to see the transition. +

+ + + +
+) + +export const Option2StackVertical = () => ( +
+

+ Option 2 — Actions stack vertically (column) inside their slot when the item is below 480px. Row layout is + preserved above the breakpoint. +

+ + + +
+) + +export const Option3StayInline = () => ( +
+

+ Option 3 — Baseline. Current behavior: Actions stay on the right edge and Body wraps first. Included for + side-by-side comparison. +

+ + + +
+) From f43ef6071e725c80ab10c6a1a4b88daa8592c106 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Wed, 15 Jul 2026 14:41:31 -0700 Subject: [PATCH 2/6] Timeline.Actions: promote responsive stories to features so they deploy The initial commit used *.dev.stories.* which is excluded from the deployed Storybook build (packages/react/.storybook/main.ts). Rename to *.features.stories.* and move the story title from 'Components/Timeline/Dev/Actions Responsive' to 'Components/Timeline/Features/Actions Responsive' so reviewers can open the three prototypes on the PR preview URL. --- ...> Timeline.actions-responsive.features.stories.module.css} | 0 ...s.tsx => Timeline.actions-responsive.features.stories.tsx} | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/react/src/Timeline/{Timeline.actions-responsive.dev.stories.module.css => Timeline.actions-responsive.features.stories.module.css} (100%) rename packages/react/src/Timeline/{Timeline.actions-responsive.dev.stories.tsx => Timeline.actions-responsive.features.stories.tsx} (98%) diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css similarity index 100% rename from packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.module.css rename to packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx similarity index 98% rename from packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx rename to packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx index a9496005870..5850fb33d5f 100644 --- a/packages/react/src/Timeline/Timeline.actions-responsive.dev.stories.tsx +++ b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx @@ -21,10 +21,10 @@ import {Button} from '../Button' import Label from '../Label' import BranchName from '../BranchName' import Octicon from '../Octicon' -import classes from './Timeline.actions-responsive.dev.stories.module.css' +import classes from './Timeline.actions-responsive.features.stories.module.css' export default { - title: 'Components/Timeline/Dev/Actions Responsive', + title: 'Components/Timeline/Features/Actions Responsive', component: Timeline, subcomponents: { 'Timeline.Item': Timeline.Item, From 950afdc146e3b898cdfa3dfc795f1ae35605a81e Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Thu, 16 Jul 2026 09:42:53 -0700 Subject: [PATCH 3/6] Timeline.Actions: add PR-commit-row cases to responsive prototypes Adds two cases to the shared ItemCases block so each responsive option also demonstrates layouts where Timeline.Actions holds a verification cluster (Label + icon + SHA link) rather than buttons \u2014 the pattern used on PR commit-added events (github/primer#6693). E. Long commit subject (\u201cApply suggestions from code review\u201d) + Verified label + check icon + SHA. F. Same shape with a \u201cPartially verified\u201d label + X icon + SHA to stress the SHA cluster with a wider Actions slot. Adds .SignatureLabelPartial and .IconDanger helper classes for the partially-verified variant styling. --- ...ons-responsive.features.stories.module.css | 8 +++ ...ne.actions-responsive.features.stories.tsx | 53 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css index f53e2c7eb6d..c516fae29f8 100644 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css @@ -129,10 +129,18 @@ color: var(--fgColor-success); } +.SignatureLabelPartial { + color: var(--fgColor-attention); +} + .IconSuccess { color: var(--fgColor-success); } +.IconDanger { + color: var(--fgColor-danger); +} + .Timestamp { color: var(--fgColor-muted); } diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx index 5850fb33d5f..71f55cbdd67 100644 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx @@ -15,7 +15,7 @@ import type {Meta} from '@storybook/react-vite' import type {ComponentProps, ReactNode} from 'react' import Timeline from './Timeline' -import {CheckIcon, GitCommitIcon, GitMergeIcon, RepoPushIcon} from '@primer/octicons-react' +import {CheckIcon, GitCommitIcon, GitMergeIcon, RepoPushIcon, XIcon} from '@primer/octicons-react' import Link from '../Link' import {Button} from '../Button' import Label from '../Label' @@ -55,7 +55,9 @@ function ResizableTimeline({children}: {children: ReactNode}) { /** * Renders the same set of realistic Timeline items used to stress-test each * option. Cases cover: two-button actions, single-button + long body, a - * condensed multi-element row, and a worst-case long body + long actions. + * condensed multi-element row, a worst-case long body + long actions, and + * PR-style commit rows where the Actions slot is a verification cluster + * (Label + icon + SHA link) rather than buttons. */ function ItemCases({itemClassName, actionsClassName}: {itemClassName?: string; actionsClassName?: string}) { return ( @@ -158,6 +160,53 @@ function ItemCases({itemClassName, actionsClassName}: {itemClassName?: string; a + + {/* Case E: PR commit row — realistic long commit subject with a + `Verified` signature cluster (Label + check icon + SHA link) in + Actions instead of buttons. Mirrors the pattern used on PR + commit-added events (see github/primer#6693 discussion). */} +
+ + + + + + + Apply suggestions from code review + + + + + + + 3fbdc0 + + + +
+ + {/* Case F: PR commit row — `Partially verified` variant with a longer + label and an X icon. Stresses the SHA cluster with a wider Actions + slot to see how each option handles the extra width. */} +
+ + + + + + + Initial commit + + + + + + + 3fbdc0 + + + +
) } From ca1699033063bf645ca3754bbfc5ea229eb17ab4 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Thu, 16 Jul 2026 09:45:37 -0700 Subject: [PATCH 4/6] Timeline.Actions: add cross-reference case to responsive prototypes Adds Case G to ItemCases so each responsive option also demonstrates the third Timeline.Actions pattern Jan called out alongside Buttons and SHA clusters (github/primer#6693): an optional octicon (LockIcon for private references) paired with a StateLabel showing the referenced PR's state. Mirrors the GitHub PR cross-reference row ("mentioned this pull request in #NNNN") with LockIcon + StateLabel status=pullOpened as Actions content. Confirms the grid switch in Option 1 also aligns the StateLabel cluster under the reference text at narrow widths. --- ...ons-responsive.features.stories.module.css | 5 +++ ...ne.actions-responsive.features.stories.tsx | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css index c516fae29f8..54e61384710 100644 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css @@ -141,6 +141,11 @@ color: var(--fgColor-danger); } +.CrossReferenceMeta { + color: var(--fgColor-muted); + flex-shrink: 0; +} + .Timestamp { color: var(--fgColor-muted); } diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx index 71f55cbdd67..e8cd873a40c 100644 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx @@ -15,12 +15,21 @@ import type {Meta} from '@storybook/react-vite' import type {ComponentProps, ReactNode} from 'react' import Timeline from './Timeline' -import {CheckIcon, GitCommitIcon, GitMergeIcon, RepoPushIcon, XIcon} from '@primer/octicons-react' +import { + CheckIcon, + CrossReferenceIcon, + GitCommitIcon, + GitMergeIcon, + LockIcon, + RepoPushIcon, + XIcon, +} from '@primer/octicons-react' import Link from '../Link' import {Button} from '../Button' import Label from '../Label' import BranchName from '../BranchName' import Octicon from '../Octicon' +import StateLabel from '../StateLabel' import classes from './Timeline.actions-responsive.features.stories.module.css' export default { @@ -207,6 +216,33 @@ function ItemCases({itemClassName, actionsClassName}: {itemClassName?: string; a + + {/* Case G: PR cross-reference — the third Actions pattern Jan + identified alongside Buttons and SHA clusters (github/primer#6693). + An optional octicon (LockIcon for private references) paired with a + StateLabel showing the referenced PR's state. */} +
+ + + + + + + Monalisa + + mentioned this pull request in{' '} + + Fix positioning of Autocomplete overlay menu primer/react#7431 + + + + + + Open + + + +
) } From bae1840a1f3a8e126bbb8c96ae2b1bb00b20ae33 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Thu, 16 Jul 2026 11:25:08 -0700 Subject: [PATCH 5/6] actions on narrow --- .../timeline-actions-narrow-viewport.md | 5 + ...ons-responsive.features.stories.module.css | 151 ---------- ...ne.actions-responsive.features.stories.tsx | 284 ------------------ .../Timeline.features.stories.module.css | 53 ++-- .../Timeline/Timeline.features.stories.tsx | 34 +-- .../react/src/Timeline/Timeline.module.css | 41 +++ 6 files changed, 96 insertions(+), 472 deletions(-) create mode 100644 .changeset/timeline-actions-narrow-viewport.md delete mode 100644 packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css delete mode 100644 packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx diff --git a/.changeset/timeline-actions-narrow-viewport.md b/.changeset/timeline-actions-narrow-viewport.md new file mode 100644 index 00000000000..9adf82e3dc6 --- /dev/null +++ b/.changeset/timeline-actions-narrow-viewport.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Timeline: `Timeline.Actions` now wraps onto a new row below `Timeline.Body`, left-aligned with the body content, when `Timeline.Item` is narrower than 480px. diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css deleted file mode 100644 index 54e61384710..00000000000 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.module.css +++ /dev/null @@ -1,151 +0,0 @@ -/* - Prototype styles for Timeline.Actions responsive behavior on narrow viewports. - Tracks: https://github.com/github/primer/issues/6693 - - Each story renders inside `.ResizableContainer` — drag the bottom-right corner - to resize the timeline horizontally and observe how the prototype option - responds. The container establishes `container-type: inline-size` on each - Timeline.Item via `.ItemContainer` so container queries can target item width. -*/ - -.ResizableContainer { - /* Fill whatever width Storybook's viewport / canvas gives us so the - container query below reacts to viewport changes (xsmall/small/medium/ - large in the Storybook viewport addon). The `resize: horizontal` handle - lets a reviewer additionally drag the width down independently. */ - box-sizing: border-box; - width: 100%; - min-width: 240px; - max-width: 1012px; - padding: var(--base-size-16); - overflow: auto; - resize: horizontal; - border: var(--borderWidth-thin) solid var(--borderColor-default); - border-radius: var(--borderRadius-medium); -} - -/* - Establishes an inline-size container per Timeline.Item so the option CSS - below can react to each item's own width. Uses an ANONYMOUS container query - (matches the CommentCard pattern in this repo) — named container queries - don't reliably work here because CSS Modules scopes `container-name` on the - declaration but leaves the identifier inside `@container` unscoped, so the - two never match. Anonymous queries bind to the nearest ancestor with - `container-type` set, which is exactly what we want. -*/ -.ItemContainer { - container-type: inline-size; -} - -/* --- Option 1: wrap Actions below Body when the item is narrow --- */ - -/* - At narrow widths switch the Item to a 2-column grid so Actions can align to - the Body's left edge regardless of whether Badge and Body share a row or - Body wraps under the Badge. The flex-based approach with `flex-wrap: wrap` - couldn't guarantee alignment because Body's wrap behavior depended on - content width — grid gives us a stable, predictable layout. -*/ -@container (width < 480px) { - .Option1Item { - display: grid; - grid-template-columns: auto 1fr; - grid-template-areas: - 'badge body' - '. actions'; - column-gap: var(--base-size-8); - row-gap: var(--base-size-8); - } - - .Option1Item > [class*='TimelineBadgeWrapper'] { - grid-area: badge; - } - - .Option1Item > [class*='TimelineBody'] { - grid-area: body; - } - - .Option1Actions { - grid-area: actions; - margin-left: 0; - justify-content: flex-start; - min-height: 0; - } -} - -/* --- Option 2: stack Actions vertically when the item is narrow --- */ - -@container (width < 480px) { - .Option2Actions { - /* Stack action children vertically inside the slot. */ - flex-direction: column; - align-items: flex-end; - /* Column layout doesn't need to match badge height. */ - min-height: 0; - /* Force a visible gap between Body text and the stacked Actions column - when Body has enough content to reach Actions' left edge. Overrides - the base `margin-left: auto`, which collapses to 0 when Body fills - the row. */ - margin-left: var(--base-size-8); - } -} - -/* --- Shared story styling (mirrors Timeline.features.stories.module.css) --- */ - -.Stack { - display: flex; - flex-direction: column; - gap: var(--base-size-16); -} - -.OptionCaption { - padding: var(--base-size-8) var(--base-size-12); - font-size: var(--text-body-size-small); - color: var(--fgColor-muted); - background-color: var(--bgColor-muted); - border-radius: var(--borderRadius-medium); -} - -.LinkWithBoldStyle { - font-weight: var(--base-text-weight-semibold); - color: var(--fgColor-default); - margin-right: var(--base-size-4); -} - -.CommitSha { - font-family: var(--fontStack-monospace); - font-size: var(--text-body-size-small); - font-weight: var(--base-text-weight-semibold); - color: var(--fgColor-default); -} - -.ShaLink { - font-family: var(--fontStack-monospace); - font-size: var(--text-body-size-small); - text-decoration: underline; -} - -.SignatureLabelVerified { - color: var(--fgColor-success); -} - -.SignatureLabelPartial { - color: var(--fgColor-attention); -} - -.IconSuccess { - color: var(--fgColor-success); -} - -.IconDanger { - color: var(--fgColor-danger); -} - -.CrossReferenceMeta { - color: var(--fgColor-muted); - flex-shrink: 0; -} - -.Timestamp { - color: var(--fgColor-muted); -} diff --git a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx b/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx deleted file mode 100644 index e8cd873a40c..00000000000 --- a/packages/react/src/Timeline/Timeline.actions-responsive.features.stories.tsx +++ /dev/null @@ -1,284 +0,0 @@ -/* - Prototypes for `Timeline.Actions` responsive/wrap behavior on narrow viewports. - Tracks: https://github.com/github/primer/issues/6693 - - Each story wraps the Timeline in a horizontally-resizable container. Drag the - bottom-right corner of the container to shrink/grow the Timeline width and - observe how the prototype option responds. - - Options mirror the three approaches enumerated in the issue: - 1. Wrap Actions below Body on narrow widths (container query on Timeline.Item). - 2. Stack Actions vertically when the row is narrow. - 3. Stay inline, do nothing (current behavior baseline). -*/ - -import type {Meta} from '@storybook/react-vite' -import type {ComponentProps, ReactNode} from 'react' -import Timeline from './Timeline' -import { - CheckIcon, - CrossReferenceIcon, - GitCommitIcon, - GitMergeIcon, - LockIcon, - RepoPushIcon, - XIcon, -} from '@primer/octicons-react' -import Link from '../Link' -import {Button} from '../Button' -import Label from '../Label' -import BranchName from '../BranchName' -import Octicon from '../Octicon' -import StateLabel from '../StateLabel' -import classes from './Timeline.actions-responsive.features.stories.module.css' - -export default { - title: 'Components/Timeline/Features/Actions Responsive', - component: Timeline, - subcomponents: { - 'Timeline.Item': Timeline.Item, - 'Timeline.Body': Timeline.Body, - 'Timeline.Actions': Timeline.Actions, - }, -} as Meta> - -/** - * Wraps each `Timeline.Item` in a `.ItemContainer` element that establishes an - * inline-size container so prototype CSS can query the item's own width. - * Options 1 and 2 rely on this; Option 3 (baseline) also uses it so the DOM - * shape is identical across stories and the only variable is the CSS. - */ -function ResizableTimeline({children}: {children: ReactNode}) { - return ( -
{ - if ((e.target as HTMLElement).closest('a')) e.preventDefault() - }} - > - {children} -
- ) -} - -/** - * Renders the same set of realistic Timeline items used to stress-test each - * option. Cases cover: two-button actions, single-button + long body, a - * condensed multi-element row, a worst-case long body + long actions, and - * PR-style commit rows where the Actions slot is a verification cluster - * (Label + icon + SHA link) rather than buttons. - */ -function ItemCases({itemClassName, actionsClassName}: {itemClassName?: string; actionsClassName?: string}) { - return ( - <> - {/* Case A: two-button actions, medium body. */} -
- - - - - - - Monalisa - - merged via the queue into main with commit{' '} - - 01e49tb - {' '} - - just now - - - - - - - -
- - {/* Case B: single-button actions, longer body. */} -
- - - - - - - Monalisa - - force-pushed the main branch from{' '} - - 01e49tb - {' '} - to{' '} - - 02f50uc - {' '} - - 2 hours ago - - - - - - -
- - {/* Case C: condensed row with multiple small elements. */} -
- - - - - - - Update README.md with much longer commit subject to force wrapping - - - - - - - 3fbdc0 - - - -
- - {/* Case D: torture test — long body + long, multi-element actions. */} -
- - - - - - - Monalisa - - merged commit{' '} - - 01e49tb - {' '} - into release/2026-11 with a longer summary that mentions several - additional details about the change - - - - - - - -
- - {/* Case E: PR commit row — realistic long commit subject with a - `Verified` signature cluster (Label + check icon + SHA link) in - Actions instead of buttons. Mirrors the pattern used on PR - commit-added events (see github/primer#6693 discussion). */} -
- - - - - - - Apply suggestions from code review - - - - - - - 3fbdc0 - - - -
- - {/* Case F: PR commit row — `Partially verified` variant with a longer - label and an X icon. Stresses the SHA cluster with a wider Actions - slot to see how each option handles the extra width. */} -
- - - - - - - Initial commit - - - - - - - 3fbdc0 - - - -
- - {/* Case G: PR cross-reference — the third Actions pattern Jan - identified alongside Buttons and SHA clusters (github/primer#6693). - An optional octicon (LockIcon for private references) paired with a - StateLabel showing the referenced PR's state. */} -
- - - - - - - Monalisa - - mentioned this pull request in{' '} - - Fix positioning of Autocomplete overlay menu primer/react#7431 - - - - - - Open - - - -
- - ) -} - -export const Option1WrapBelow = () => ( -
-

- Option 1 — Actions wrap onto a new row below Body when the item is below 480px, left-aligned with the body - content. Drag the container edge to see the transition. -

- - - -
-) - -export const Option2StackVertical = () => ( -
-

- Option 2 — Actions stack vertically (column) inside their slot when the item is below 480px. Row layout is - preserved above the breakpoint. -

- - - -
-) - -export const Option3StayInline = () => ( -
-

- Option 3 — Baseline. Current behavior: Actions stay on the right edge and Body wraps first. Included for - side-by-side comparison. -

- - - -
-) diff --git a/packages/react/src/Timeline/Timeline.features.stories.module.css b/packages/react/src/Timeline/Timeline.features.stories.module.css index 475b9abf9db..88be846c004 100644 --- a/packages/react/src/Timeline/Timeline.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.features.stories.module.css @@ -49,19 +49,34 @@ color: var(--fgColor-muted); } +.InlineAvatar { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* + Mirrors github.com's `.d-flex.flex-column.flex-md-row` on the linked-issue/PR + reference row: title on top, lock icon and state pill stacked below at narrow; + laid out horizontally with the title claiming remaining space at md+. +*/ .CrossReferenceRow { display: flex; + flex-direction: row; align-items: flex-start; gap: var(--base-size-12); margin-top: var(--base-size-4); } -.CrossReferenceContent { - flex: 1; - min-width: 0; +@container (width < 480px) { + .CrossReferenceRow { + flex-direction: column; + gap: var(--base-size-4); + } } .CrossReferenceTitle { + flex: 1; + min-width: 0; font-size: var(--text-title-size-small); line-height: var(--text-title-lineHeight-small); } @@ -87,18 +102,6 @@ color: var(--fgColor-accent); } -.CrossReferenceMeta { - color: var(--fgColor-muted); - flex-shrink: 0; -} - -.CrossReferenceActions { - display: flex; - align-items: center; - gap: var(--base-size-12); - flex-shrink: 0; -} - .CrossReferenceTaskline { display: flex; align-items: center; @@ -108,9 +111,23 @@ color: var(--fgColor-muted); } -.InlineAvatar { - vertical-align: middle; - margin-right: var(--base-size-4); +.CrossReferenceMeta { + color: var(--fgColor-muted); + flex-shrink: 0; +} + +/* + Hides Timeline.Actions children (like the "Verified"/"Unverified" signature + labels on commit rows) when the surrounding Timeline is below 480px inline + size. Mirrors github.com's `.d-md-inline-block .d-none` responsive hide, so + after the Actions row wraps beneath Body only the compact icon + SHA cluster + remains. Container query resolves against `.Timeline` (which sets + `container-type: inline-size` in the shipped component CSS). +*/ +@container (width < 480px) { + .HideAtNarrow { + display: none; + } } .Timestamp { diff --git a/packages/react/src/Timeline/Timeline.features.stories.tsx b/packages/react/src/Timeline/Timeline.features.stories.tsx index 806f7b42e5c..9c930722b09 100644 --- a/packages/react/src/Timeline/Timeline.features.stories.tsx +++ b/packages/react/src/Timeline/Timeline.features.stories.tsx @@ -280,7 +280,7 @@ export const WithActions = () => ( - + 3fbdc0 @@ -297,7 +297,7 @@ export const WithActions = () => ( - + 3fbdc0 @@ -322,24 +322,20 @@ export const WithActions = () => ( just now
-
-
- - Fix positioning of Autocomplete overlay menu{' '} - primer/react#7431 - -
-
- - 17 tasks -
-
-
- - - Open - +
+ + Fix positioning of Autocomplete overlay menu{' '} + primer/react#7431 +
+ + + Open + +
+
+ + 17 tasks
diff --git a/packages/react/src/Timeline/Timeline.module.css b/packages/react/src/Timeline/Timeline.module.css index bbedaee66a7..cc9da65f278 100644 --- a/packages/react/src/Timeline/Timeline.module.css +++ b/packages/react/src/Timeline/Timeline.module.css @@ -4,6 +4,12 @@ list-style: none; padding: 0; margin: 0; + /* Establish an inline-size container so descendants (Timeline.Item and + its Badge / Body / Actions slots) can respond to the timeline's own + width via the narrow-viewport rules at the end of this file. Uses + container queries rather than viewport media queries so Timeline + works correctly inside narrow side panels and split views. */ + container-type: inline-size; &:where([data-clip-sidebar='start']), &:where([data-clip-sidebar='both']) { @@ -194,3 +200,38 @@ gap: var(--base-size-8); flex-shrink: 0; } + +/* Below ~480px inline size (mobile viewports, narrow side panels, etc.), + switch each Timeline.Item to a 2-column grid so Timeline.Actions wraps + onto its own row directly under Timeline.Body, left-aligned with the + body content. The badge stays in the left column across both rows so + the vertical rule and gutter behavior are unchanged; items without an + Actions slot leave the second grid row empty and collapse to 0 height. + Tracked in github/primer#6693. */ +@container (width < 480px) { + .TimelineItem { + display: grid; + grid-template-columns: auto 1fr; + grid-template-areas: + 'badge body' + '. actions'; + } + + .TimelineBadgeWrapper { + grid-area: badge; + } + + .TimelineBody { + grid-area: body; + } + + .TimelineItemActions { + grid-area: actions; + /* Reset the flex-row auto-push behavior; the wrapped row already sits + under Body and doesn't need to match the badge's row height. */ + margin-top: var(--base-size-8); + margin-left: 0; + justify-content: flex-start; + min-height: 0; + } +} From 126c7639bb9b9389bfdbe00e65630e0659d88324 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Thu, 16 Jul 2026 12:59:38 -0700 Subject: [PATCH 6/6] comments --- .../Timeline.features.stories.module.css | 18 +++++----------- .../react/src/Timeline/Timeline.module.css | 21 +++++++------------ 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/packages/react/src/Timeline/Timeline.features.stories.module.css b/packages/react/src/Timeline/Timeline.features.stories.module.css index 88be846c004..d1337a60e6b 100644 --- a/packages/react/src/Timeline/Timeline.features.stories.module.css +++ b/packages/react/src/Timeline/Timeline.features.stories.module.css @@ -54,11 +54,8 @@ margin-right: var(--base-size-4); } -/* - Mirrors github.com's `.d-flex.flex-column.flex-md-row` on the linked-issue/PR - reference row: title on top, lock icon and state pill stacked below at narrow; - laid out horizontally with the title claiming remaining space at md+. -*/ +/* Title on top, lock icon and state pill stacked below at narrow; + inline row with the title claiming remaining space above 480px. */ .CrossReferenceRow { display: flex; flex-direction: row; @@ -116,14 +113,9 @@ flex-shrink: 0; } -/* - Hides Timeline.Actions children (like the "Verified"/"Unverified" signature - labels on commit rows) when the surrounding Timeline is below 480px inline - size. Mirrors github.com's `.d-md-inline-block .d-none` responsive hide, so - after the Actions row wraps beneath Body only the compact icon + SHA cluster - remains. Container query resolves against `.Timeline` (which sets - `container-type: inline-size` in the shipped component CSS). -*/ +/* Hides Timeline.Actions children (Verified / Unverified signature labels + on commit rows) when Timeline is narrow, so only the compact icon + SHA + cluster remains after the Actions row wraps beneath Body. */ @container (width < 480px) { .HideAtNarrow { display: none; diff --git a/packages/react/src/Timeline/Timeline.module.css b/packages/react/src/Timeline/Timeline.module.css index cc9da65f278..340516926f5 100644 --- a/packages/react/src/Timeline/Timeline.module.css +++ b/packages/react/src/Timeline/Timeline.module.css @@ -4,11 +4,9 @@ list-style: none; padding: 0; margin: 0; - /* Establish an inline-size container so descendants (Timeline.Item and - its Badge / Body / Actions slots) can respond to the timeline's own - width via the narrow-viewport rules at the end of this file. Uses - container queries rather than viewport media queries so Timeline - works correctly inside narrow side panels and split views. */ + /* Container for the narrow-viewport rules at the end of this file. Uses + an inline-size container so behavior tracks the timeline's own width, + not the viewport (works inside narrow side panels and split views). */ container-type: inline-size; &:where([data-clip-sidebar='start']), @@ -201,13 +199,9 @@ flex-shrink: 0; } -/* Below ~480px inline size (mobile viewports, narrow side panels, etc.), - switch each Timeline.Item to a 2-column grid so Timeline.Actions wraps - onto its own row directly under Timeline.Body, left-aligned with the - body content. The badge stays in the left column across both rows so - the vertical rule and gutter behavior are unchanged; items without an - Actions slot leave the second grid row empty and collapse to 0 height. - Tracked in github/primer#6693. */ +/* Below 480px inline size, wrap Timeline.Actions onto its own row under + Timeline.Body via a 2-column grid. The badge keeps the left column so + the vertical rule stays consistent. */ @container (width < 480px) { .TimelineItem { display: grid; @@ -227,8 +221,7 @@ .TimelineItemActions { grid-area: actions; - /* Reset the flex-row auto-push behavior; the wrapped row already sits - under Body and doesn't need to match the badge's row height. */ + /* Reset flex-row auto-push; the wrapped row sits under Body. */ margin-top: var(--base-size-8); margin-left: 0; justify-content: flex-start;