Skip to content

Replace Timeline Playground with Custom Event playground#7836

Draft
janmaarten-a11y wants to merge 10 commits into
mainfrom
timeline/custom-event-playground
Draft

Replace Timeline Playground with Custom Event playground#7836
janmaarten-a11y wants to merge 10 commits into
mainfrom
timeline/custom-event-playground

Conversation

@janmaarten-a11y
Copy link
Copy Markdown

@janmaarten-a11y janmaarten-a11y commented May 12, 2026

Refs github/primer#6654
Closes github/primer#6662

Phase 1 of the Timeline redesign. Replaces the existing Components/Timeline → Playground story with a recreation of the Figma "Custom event" component (Primer-Web library, node 46191-13560).

This is intentionally story-only — no public API changes. The goal is to nail down the prop surface, layout patterns, and naming conventions before Phase 2 introduces named PR / Issue / Dependabot events. The new playground composes existing public primitives only (Timeline, Timeline.Item, Timeline.Badge, Timeline.Body, Avatar, Link, RelativeTime) and introduces a data-* convention on Timeline.Item (data-event-scope, data-event-type, data-actor-type) that Phase 4 filtering work will build on.

New Timeline Playground (custom event)
A storybook playground showing a custom timeline event and a series of controls for actors, badges, event text, and other optional content. The event says: janmaarten-a11y built a new timeline event playground just now — with Copilot + Figma MCP (as a fake custom app name)
Old Playground
The current Timeline Playground story showing four generic timeline events that say This is a message and have a commit icon. There are only controls for clipSidebar, condensed, and className

Changelog

New

  • Custom Event playground story for Components/Timeline recreating the Figma "Custom event" component. Covers 32 badge octicons, all 9 TimelineBadgeVariant colors, 5 timestamp presets (3 relative + 2 absolute), paired GitHub Actions / Custom App attribution, and conditional controls (avatar URL only when actorType: 'user', app fields only when viaApp is on, custom-app fields only when appPreset: 'Custom App').
  • data-* filtering convention on the rendered Timeline.Item: data-event-scope, data-event-type, data-actor-type. No visual effect today; reserved for Phase 4 filtering and the planned summary-events rollup.
  • Story-local CSS module that reserves a fixed-width left-of-rail gutter so toggling actorSize doesn't shift the timeline horizontally. Mirrors the Rails ViewComponents .TimelineItem-avatar { position: absolute; left: -72px } treatment without modifying the React component.

Changed

  • Replaced the previous Components/Timeline → Playground (the simple clipSidebar + condensed toggle demo) with the new Custom Event playground. The replaced behaviour stays demonstrated in Components/Timeline/Features (ClipSidebar, ClipSidebarStart, ClipSidebarEnd, CondensedItems).

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None — story-only change, no published-package impact. The @primer/react package surface is unchanged. skip changeset label applied.

Testing & Reviewing

Open the new playground at Components/Timeline → Playground and try these:

  1. Actor variants — flip actorType through user / bot / app / copilot. The actorName field auto-fills with a sensible default for each type (monalisa / dependabot / GitHub Actions / Copilot) and stays editable, except for copilot where it's hidden because the name is fixed. actorAvatarSrc only appears for user.
  2. Avatar layout — flip actorSize between small and large. The timeline shouldn't shift horizontally; only the avatar position changes (inline 20px vs 40px in the reserved left gutter).
  3. Timestamp presets — cycle eventTimestamp through all 5:
    • Relative (now)just now
    • Relative (recent day)yesterday / X days ago (live-updating via <RelativeTime>)
    • Relative (3 weeks)3 weeks ago (live-updating via <RelativeTime>)
    • Absolute (today)Today h:mm AM/PM TZ (Intl.DateTimeFormat)
    • Absolute (full timestamp)Mon DD, h:mm AM/PM TZ (Intl.DateTimeFormat)
  4. viaApp attribution — toggle on, switch between GitHub Actions and Custom App presets. Custom App reveals editable name + avatar URL fields. Heads up: this slot is PR-specific in real GitHub usage, which we'll revisit in Phase 2.
  5. showNote — adds a second muted line below the summary. Covers cited-reason patterns (Dependabot dismissal reason, lock reason, merge-queue "due to..." text).
  6. DOM attributes — open DevTools and confirm data-event-scope, data-event-type, data-actor-type, and className apply to the rendered Timeline.Item.

A11y was reviewed via the Storybook a11y panel. One real link-name violation (empty actorName produced an empty link) was caught and fixed with a fallback. Axe also throws intermittent incomplete color-contrast notes on user-typed input — that's axe being unable to predict the final color from dynamic text, not a real issue. The Primer muted tokens are used correctly throughout.

The longer-form description (Figma reference, data-* convention, Phase 1 limitations) lives on the story's Docs tab. The Controls panel is intentionally compact since most controls explain themselves through their label and immediate visual feedback.

Things this PR doesn't tackle

A few items from the parent issue we decided to defer, plus some bigger gaps that surfaced while building. None of these are blockers for this PR.

Deferred from the issue's scope:

  • Preset stories alongside the Playground. The Playground covers every variant via controls already, and Phase 2/3 will reorganize the Timeline section into subfolders (primitives / named events / full timelines). Preset stories will have a natural home there; adding them to the current flat structure now would just need to be moved later.
  • condensed control. Only used on 2nd+ rows of a commit group in real timelines, so it isn't really a custom-event property — and toggling it with actorSize: 'large' looks wrong. Phase 2's named Commit-group event will apply it automatically. The prop is still public on Timeline.Item and shown in Timeline.features.stories.tsx#CondensedItems.
  • subContent (cross-reference cards, etc.). No card primitive exists yet in Primer React. Adding one would either over-engineer the playground or do work that Phase 2's named events should own.

Follow-ups, gaps surfaced during this work, and what we decided to defer are tracked on the parent issue: github/primer#6662.

Merge checklist

  • Added/updated tests — N/A; story-only change. Existing 15 Timeline unit tests still pass; the 312-test Storybook smoke test still passes.
  • Added/updated documentation — JSDoc story description bound to the Playground export, visible on the Docs tab.
  • Added/updated previews (Storybook) — the playground itself.
  • Changes are SSR compatibleno client-only APIs; Date.now() captured via lazy useState for stability across re-renders.
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge
  • (GitHub staff only) Integration tests pass at github/github-ui — no @primer/react package changes; nothing dotcom's Timeline depends on.

Recreates the Figma 'Custom event' component (Primer-Web library, node 46191-13560) as the Timeline Playground story. Composes existing public primitives only (Timeline, Timeline.Item, Timeline.Badge, Timeline.Body, Avatar, Octicon, Link, RelativeTime) — no public API changes.

Storybook controls are grouped into Actor / Badge / Event / Optional content / DOM attributes categories. Highlights:

- Actor: small (20px inline) vs large (40px in left gutter); user / bot / app / copilot types with baked-in canonical names for bot and copilot; user-only avatar URL override

- Badge: 32 octicons + all 9 TimelineBadgeVariant colors

- Event: 5 timestamp presets matching the Figma options (3 relative, 2 absolute) with appropriate render modes (literal / RelativeTime / Intl.DateTimeFormat)

- Optional content: showNote + noteText for second-line cited reasons; viaApp + paired GitHub Actions / Custom App presets for PR-style app attribution

- DOM attributes: className plus data-event-scope and data-event-type for Phase 4 filtering work

Layout: story-local CSS reserves a fixed-width left-of-rail gutter so toggling actorSize doesn't shift the timeline horizontally. Mirrors the Rails ViewComponents .TimelineItem-avatar { left: -72px } treatment without modifying the React component.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

⚠️ No Changeset found

Latest commit: f8e2e95

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot requested a deployment to storybook-preview-7836 May 12, 2026 20:37 Abandoned
@primer
Copy link
Copy Markdown
Contributor

primer Bot commented May 12, 2026

🤖 Lint and formatting issues have been automatically fixed and committed to this PR.

@primer primer Bot had a problem deploying to github-pages May 12, 2026 20:44 Failure
@github-actions github-actions Bot requested a deployment to storybook-preview-7836 May 12, 2026 20:44 Abandoned
@primer
Copy link
Copy Markdown
Contributor

primer Bot commented May 12, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

The build:components.json script only resolves story ids ending in '--default' (in *.stories.tsx), '-features--*' (in *.features.stories.tsx), or '-examples--*' (in *.examples.stories.tsx). The 'components-timeline--playground' id doesn't match any of those patterns and was throwing 'No story named Playground found in Timeline.features.stories.tsx', cascading to ~25 CI job failures (build, lint, type-check, sizes, test, examples, vrt, aat, etc.). Playground stories are not registered in docs.json by convention.
actorName control now hides entirely when actorType is 'copilot' (the name is fixed and not editable). For 'bot', the field stays visible but a useArgs decorator auto-syncs its value to 'dependabot' when the user picks the bot type. Users can still edit from there to pick a different bot identity (e.g. 'renovate[bot]').
Previously the decorator only wrote 'dependabot' when switching to bot, leaving 'dependabot' stuck in the field when switching back to user/app/copilot. Now uses a useRef-tracked previous value to detect any actorType change and write the per-type default (monalisa / dependabot / GitHub Actions / Copilot).
@github-actions github-actions Bot requested a deployment to storybook-preview-7836 May 13, 2026 19:55 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-7836 May 13, 2026 20:06 Inactive
When the user clears the actorName field, the resulting <Link> has no accessible text and fails axe's link-name check. Falls back to 'Unknown actor' for empty/whitespace input. Bot/Copilot baked names already cover those types so the fallback only fires for user/app.
@janmaarten-a11y
Copy link
Copy Markdown
Author

janmaarten-a11y commented May 14, 2026

I went back through the parent issue's checklist and ran the Storybook a11y panel over the Playground. PR is in good shape; all checks are green.

What the a11y review found: one real issue — clearing the actorName field left an empty link, which axe flagged. Fixed in 1b9497363 with an "Unknown actor" fallback so the link always has text. Axe also threw some "incomplete" notes on color contrast when you type into the actor or app name fields, but those are axe being unable to predict the final color when the text is dynamic, not a real problem.

What I'm deferring: the issue asked for 2–3 preset stories alongside the Playground. I think we're better off waiting. The Playground already covers every variant via controls, and Phase 2/3 are going to need a real reorganization of the Timeline section (subfolders for primitives, individual events, and full timelines). Adding presets to the current flat structure now would just create churn when that IA work happens.

Things I noticed for Phase 2:

  • We need a real right-controls slot on Timeline.Item. PR / Issue / Shared events use it constantly (revert button, view details, restore branch, etc.) and there's no clean way to add it without an API change.
  • Same story for an avatar slot — currently faking it with story-local CSS for the large-avatar treatment.
  • Worth revisiting the list semantics (<div> everywhere right now; richer timelines probably want <ol>/<li> or at least role="list").
  • The existing Default story and the Features stories still use the deprecated Octicon wrapper. Easy cleanup to roll into the named-events PR.
  • The condensed badge color reads too dark — worth a token swap.
  • Need to make a call on viaApp: it's PR-specific in real usage, since on Issues and Dependabot timelines the app is the primary actor instead.
  • Comments, review comments, and threaded comments all need their own pass.
  • Event summaries with embedded subcomponents (labels, branch names, commit SHAs) — figured out the right way to model these is per-event, not via a generic templating system in the playground.

Comment thread packages/react/src/Timeline/Timeline.stories.tsx
Comment thread packages/react/src/Timeline/Timeline.stories.tsx Outdated
padding-left: calc(var(--base-size-40) + var(--base-size-16));
}

.LargeActorAvatar {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How many of these are truly story-only since they look like fundamental changes? Should we be representing what things actually render as OOTB and file some follow-up issues to better align the rendering to the expected output rather than overriding locally?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@adierkens 100% agree, and this is the loudest finding of the whole exercise. The fact that we need story-local CSS to fake a left-rail avatar gutter is exactly what tells us the component needs a real avatar slot. Same for the right-controls floats we left out entirely — lots of events have buttons, SHAs, and other content on the right side. The story-local CSS is deliberately scoped to this file to keep the React component untouched in this PR, so the gap stays loud rather than hidden.

That said, they're not strictly new — the large-avatar treatment mirrors how the equivalent Rails ViewComponents already render today, it just never formally made it into the React API. One of the reasons I've kept this PR as a draft is that I've been wondering whether we should land those API changes first, so when this merges it's not story-only. It's a real chicken/egg question and I'd be curious what you think.

I noted these in the wrap-up comment and on the parent issue github/primer#6662, but you're right that we should have actual followup issues. Will be filing these to scope for Phase 2:

  • Avatar slot on Timeline.Item (covers the large-avatar gutter and the Copilot-avatar fallback that the Rails component has already outgrown)
  • Right-controls slot
  • List semantics evaluation
  • Octicon cleanup + BADGE_VARIANTS dedup from your other comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Two a11y fixes from a manual review:

1. Badge icon: dropped aria-label (which was the developer-facing icon-map key like 'git-commit' or 'x-circle') and added aria-hidden='true'. The icon visually reinforces the summary text; announcing it as a separate label is redundant and reads as jargon.

2. Large actor avatar: changed alt='@monalisa' to alt=''. The actor name is already conveyed by the link immediately after, so the avatar is decorative. The small-avatar branch already used alt='' correctly; large now matches.
@github-actions github-actions Bot temporarily deployed to storybook-preview-7836 May 14, 2026 17:49 Inactive
Storybook removes the actorName arg entirely (not just the control) when the conditional argType (if neq copilot) hides it. The render then crashed on args.actorName.trim(). Falls back to 'Unknown actor' when actorName is undefined; the resolvedActorName already prefers the baked Copilot name in that case anyway.
Per review feedback: className is just a DOM passthrough that nobody experimenting in a playground is likely to use. The data-* attrs in the same group have semantic purpose for the planned filtering work; className didn't carry its weight.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants