From 8e12889238f03e4200bf98f1715e704540985f10 Mon Sep 17 00:00:00 2001 From: Jan Maarten <83665577+janmaarten-a11y@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:22:30 -0700 Subject: [PATCH] Timeline: add shared internal story helpers (UserActor, EventSubRow, permalink time) Add three shared, story-only Timeline helpers so all Timeline surface stories can import them instead of copy-pasting local copies: - UserActor: avatar + actor name cluster composed from InlineAvatar + BoldLink, with bot ([bot] suffix stripped, unlinked span + "bot" Label) and system-actor (icon glyph) shapes. - EventSubRow: canonical muted "icon + small text" sub-row. - MutedTime: extended with an optional href for permalink timestamps. Storybook-only; ships no runtime code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/timelineStoryHelpers.module.css | 38 +++++++++ .../internal/timelineStoryHelpers.stories.tsx | 30 ++++++- .../internal/timelineStoryHelpers.tsx | 83 ++++++++++++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css b/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css index 9d1e3f82156..4783e9d3b8b 100644 --- a/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css +++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.module.css @@ -37,3 +37,41 @@ .MutedTime { color: var(--fgColor-muted); } + +/* Underline treatment when MutedTime renders as a permalink to the event. */ +.MutedTimeLink { + text-decoration: underline; +} + +/* Span-branch actor name (UserActor without an href, and bot names). No hover-accent: + live-GitHub bold actor text doesn't hover-color — only real links (BoldLink) do. */ +.BoldName { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +/* Trailing "bot" Label spacing in UserActor. */ +.BotLabel { + margin-left: var(--base-size-4); +} + +/* Glyph rendered by UserActor for system actors (in place of an avatar). */ +.ActorIcon { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Muted "icon + small text" sub-row rendered below an event body. */ +.EventSubRow { + display: flex; + align-items: center; + margin-top: var(--base-size-4); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); +} + +.EventSubRowIcon { + margin-right: var(--base-size-4); + color: var(--fgColor-muted); +} diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx b/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx index cb08804ffe6..08b7cbdd894 100644 --- a/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx +++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.stories.tsx @@ -1,5 +1,15 @@ import type {Meta} from '@storybook/react-vite' -import {BoldLink, Examples, InlineAvatar, MutedTime, RealisticTimeline, VariantSection} from './timelineStoryHelpers' +import {MarkGithubIcon, NoteIcon} from '@primer/octicons-react' +import { + BoldLink, + EventSubRow, + Examples, + InlineAvatar, + MutedTime, + RealisticTimeline, + UserActor, + VariantSection, +} from './timelineStoryHelpers' /** * Reference stories for the internal, story-only Timeline helpers @@ -53,6 +63,24 @@ export const Helpers = () => ( time treatment.

+ + +

+ linked+muted, unlinked 16px, and a system actor{' '} + rendering a glyph instead of an avatar. +

+
+ + + Example note sub-row + + + +

+ Updated as a permalink, beside a plain{' '} + with no href. +

+
) diff --git a/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx b/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx index 0097949f625..4ef9b2ac899 100644 --- a/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx +++ b/packages/react/src/Timeline/internal/timelineStoryHelpers.tsx @@ -1,7 +1,9 @@ import type React from 'react' import {clsx} from 'clsx' import Avatar from '../../Avatar' +import Label from '../../Label' import Link from '../../Link' +import Octicon from '../../Octicon' import RelativeTime from '../../RelativeTime' import classes from './timelineStoryHelpers.module.css' @@ -64,7 +66,82 @@ export const InlineAvatar = ({className, size = 20, alt = '', ...props}: React.C ) -// TODO(github/primer#6828): remove when Timeline provides a muted relative-time treatment -export const MutedTime = ({date}: {date: Date}) => ( - +export const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' + +/** + * An actor "avatar + name" cluster, composed from the shared `InlineAvatar` + `BoldLink` + * helpers so every Timeline surface renders actors identically. + * + * The bot shape mirrors live-GitHub `shared.tsx`: a `[bot]` suffix is stripped from the + * display name, the name renders as an unlinked ``, and a secondary `bot` `Label` + * follows it. The optional `icon` prop renders a glyph instead of an avatar for system + * actors (e.g. Secret scanning's GitHubActor: ``). + * + * TODO(github/primer#6827): remove when Primer ships an inline (in-text) avatar treatment. + */ +export const UserActor = ({ + login = 'monalisa', + src = MONALISA_AVATAR, + size = 20, + href, + muted = false, + icon, +}: { + login?: string + src?: string + size?: number + href?: string + muted?: boolean + icon?: React.ElementType +}) => { + const isBot = login.endsWith('[bot]') + const display = isBot ? login.replace(/\[bot\]$/i, '') : login + return ( + <> + {icon ? : } + {href && !isBot ? ( + + {display} + + ) : ( + {display} + )} + {isBot && ( + + )} + + ) +} + +/** + * A muted "icon + small text" sub-row rendered below an event body (e.g. a commit + * reference or a note). The four current surfaces drift between flex/margin-top-4 and + * block/margin-top-8 layouts — that variance is incidental, so this canonicalizes to a + * single flex-centered layout. Only the icon and its size vary. + */ +export const EventSubRow = ({ + icon, + iconSize = 16, + children, +}: { + icon: React.ElementType + iconSize?: number + children: React.ReactNode +}) => ( +
+ + {children} +
) + +// TODO(github/primer#6828): remove when Timeline provides a muted relative-time treatment +export const MutedTime = ({date, href}: {date: Date; href?: string}) => + href ? ( + + + + ) : ( + + )