diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.module.css b/packages/react/src/Timeline/Timeline.issues.features.stories.module.css new file mode 100644 index 00000000000..c7e69869f15 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.issues.features.stories.module.css @@ -0,0 +1,175 @@ +.RealisticTimeline { + /* GitHub renders the timeline at most 1012px wide in product surfaces. */ + max-width: 1012px; +} + +.LinkWithBoldStyle { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin-right: var(--base-size-4); +} + +.LinkWithBoldStyle:hover { + color: var(--fgColor-accent); +} + +.InlineAvatar { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +.Timestamp { + text-decoration: underline; +} + +.CommitSha { + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); +} + +/* Story-only scaffolding: each variant is wrapped in its own
with a + small caption heading ABOVE the event, so the event row itself (badge + + inline avatar + body) renders exactly as it would in product — the caption + never sits inside Timeline.Body. Variants stay scannable like a Figma + component set. Not part of the faithful event. */ +.Variant { + margin-bottom: var(--base-size-24); +} + +.VariantLabel { + margin: 0 0 var(--base-size-4); + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-muted); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +/* IssueLink-style inline reference: state octicon + bold title + muted number. */ +.IssueLink { + color: var(--fgColor-default); + text-decoration: none; +} + +.IssueLink:hover { + color: var(--fgColor-accent); + text-decoration: underline; +} + +.IssueLinkIcon { + color: var(--fgColor-done); + vertical-align: middle; + margin-right: var(--base-size-4); +} + +.IssueLinkTitle { + font-weight: var(--base-text-weight-semibold); +} + +.IssueLinkNumber { + color: var(--fgColor-muted); +} + +/* Issue-version ProjectV2 reference (github-ui `ProjectV2.tsx` + `ClosedEvent.tsx`): + an inline default-colored TableIcon octicon followed by a default-color link + (`.ProjectRefLink`). The underline comes from the Link `inline` prop (always-on + underline, which also satisfies the high-contrast a11y rule). Mirrors live + `.projectLink`/`.stateReasonLink` (both `color: var(--fgColor-default)`). NOTE: + the PR (ERB) surface renders this differently — see the EventProject group. */ +.ProjectRefIcon { + vertical-align: middle; + margin-right: var(--base-size-4); +} + +.ProjectRefLink { + color: var(--fgColor-default); +} + +/* Open (green) state icon variant for IssueLink references that point at an + open issue/PR (e.g. the canonical issue in a "marked as duplicate of" row). */ +.IssueLinkIconOpen { + color: var(--fgColor-open); + vertical-align: middle; + margin-right: var(--base-size-4); +} + +/* Inline pull-request state icon rendered in the body text (References group), + mirroring github-ui's `sourceIcon('PullRequest', ...)` open-state coloring. */ +.PrStateIcon { + color: var(--fgColor-open); + vertical-align: middle; + margin: 0 var(--base-size-4); +} + +/* Open (green) badge icon, used when the badge icon itself is a PR state icon + (References "removed a link to a pull request" → `leadingIcon={PullStateIcon}`). */ +.BadgeIconOpen { + color: var(--fgColor-open); +} + +/* Wrapper around a colored issue-type Token so it sits inline with the copy, + matching github-ui's `IssueTypeEvent.module.css` `issueTypeTokenWrapper`. */ +.TokenWrapper { + display: inline-flex; + vertical-align: middle; + margin: 0 var(--base-size-4); +} + +/* Commit reference (github-ui's `ReferencedEventInner`): NOT a bordered card on + live GitHub — `.referencedCommitContainer` is a plain flex column (`gap: 2px`) + with the monospace message on the left and the SHA on the right. Mirror that: + no border, no card background, no row dividers. */ +.CommitRefBox { + display: flex; + flex-direction: column; + gap: var(--base-size-4); + margin-top: var(--base-size-8); +} + +.CommitRefRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--base-size-8); +} + +.CommitRefMessage { + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); +} + +.CommitRefOid { + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + color: var(--fgColor-muted); +} + +/* Secondary reference list (github-ui Hierarchy/Dependency events render the + linked issues below the main copy). On live GitHub these are PLAIN, lightly + indented rows — state octicon + bold title + muted #number per line — with no + surrounding border, no card background, and no row dividers. Single vs + multiple differ only by item count + singular/plural leading copy. */ +.RefList { + margin-top: var(--base-size-8); + padding-left: var(--base-size-4); + list-style: none; +} + +.RefListItem + .RefListItem { + margin-top: var(--base-size-4); +} + +/* IssueField event: the field name reads as semibold inline text, the value as + an inline link (github-ui's `IssueFieldEvent` `issueFieldTokenWrapper`). */ +.FieldName { + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + margin: 0 var(--base-size-4); +} + +.FieldValue { + margin-left: var(--base-size-4); +} diff --git a/packages/react/src/Timeline/Timeline.issues.features.stories.tsx b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx new file mode 100644 index 00000000000..9998062f195 --- /dev/null +++ b/packages/react/src/Timeline/Timeline.issues.features.stories.tsx @@ -0,0 +1,2538 @@ +import type {Meta} from '@storybook/react-vite' +import type React from 'react' +import type {ComponentProps} from '../utils/types' +import {FeatureFlags} from '../FeatureFlags' +import Timeline from './Timeline' +import { + BlockedIcon, + CalendarIcon, + CheckCircleIcon, + CircleSlashIcon, + CommentDiscussionIcon, + CrossReferenceIcon, + DuplicateIcon, + GitCommitIcon, + GitPullRequestIcon, + IssueClosedIcon, + IssueDraftIcon, + IssueOpenedIcon, + IssueReopenedIcon, + IssueTrackedByIcon, + IssueTracksIcon, + LinkExternalIcon, + LockIcon, + MilestoneIcon, + NumberIcon, + PencilIcon, + PersonIcon, + PinIcon, + SingleSelectIcon, + TableIcon, + TagIcon, + TrashIcon, + TypographyIcon, + UnlockIcon, +} from '@primer/octicons-react' +import Avatar from '../Avatar' +import {Button} from '../Button' +import Label from '../Label' +import Link from '../Link' +import Octicon from '../Octicon' +import RelativeTime from '../RelativeTime' +import Token from '../Token' +import classes from './Timeline.issues.features.stories.module.css' + +/** + * Issue Timeline event examples (Phase 2 of github/primer#6663). + * + * These stories recreate GitHub's live issue-timeline events using the Primer + * `Timeline` compositional slots, sourced from the `timeline-audit` Figma audit + * (`issue-timeline-events-for-figma.md`) and verified against the live React + * implementation in `github/github-ui` (`packages/timeline-items`). + * + * SCOPE: These are Storybook-only examples by design. They are intentionally + * NOT wired into components-json / the primer.style docs page (do NOT add this + * file to `Timeline.docs.json` or `build.ts`). Individual timeline events are not + * consumer-facing components — the primer.style Timeline page reflects the base + * `Timeline` component's own stories, and any docs-site representation is a + * Phase 3 consideration via base-component story changes, out of scope here. + * + * FUTURE FILTERING (taxonomy still open — github/primer#6663): category + * `data-*` attributes (e.g. `data-event-category="closed"`) will attach to each + * `Timeline.Item` below so stories can be filtered/grouped by event family. We + * intentionally do NOT add them yet to avoid baking in a taxonomy. + * + * SLOT USAGE (Phase 1 slots — establishes the convention for downstream groups): + * - `Timeline.Avatar` (gutter slot, #6677): the 40px LEFT-GUTTER avatar. Reserved + * for comment-style events. Badge-row events like Closed do NOT use it — the + * live github-ui `ClosedEvent` renders through `TimelineRow`, which places the + * actor's small (20px) avatar INLINE in the body (`EventActor` inline-avatar + * mode), not in the gutter. We mirror that here: avatar inline in `Timeline.Body`. + * - `Timeline.Actions` (right-controls slot, #6678): for buttons / SHAs / status + * pills on the right edge. Closed has no right controls, so it is omitted here. + * Downstream groups that DO have right controls (e.g. Duplicates' "Marked as + * duplicate" has a button) should add it as a sibling of `Timeline.Body`: + * + * + * {...} + * {...} + * + * + * + * + */ + +const MONALISA_AVATAR = 'https://avatars.githubusercontent.com/u/583231?v=4' + +// Inline 20px avatar + bold username link, matching github-ui's `EventActor` +// (packages/timeline-items/components/row/EventActor.tsx) inline-avatar mode. +const Actor = () => ( + <> + + + monalisa + + +) + +// Muted underlined relative timestamp, mirroring github-ui's `Ago` deep-link. +const Time = ({date}: {date: string}) => ( + + + +) + +export default { + title: 'Components/Timeline/Events/Issues', + component: Timeline, + subcomponents: { + 'Timeline.Item': Timeline.Item, + 'Timeline.Avatar': Timeline.Avatar, + 'Timeline.Badge': Timeline.Badge, + 'Timeline.Body': Timeline.Body, + 'Timeline.Break': Timeline.Break, + 'Timeline.Actions': Timeline.Actions, + }, + decorators: [ + // File-scoped: render every story in the future-state list semantics + // (`
    `/`
  1. `). The `primer_react_timeline_list_semantics` flag is merged + // on main; this opts these stories into the DOM the timeline will ship. + Story => ( + + + + ), + ], +} as Meta> + +/** + * The Closed event group — `IssueTimeline.eventClosed` (audit § 2). + * + * All seven variants are stacked in a single `` so they can be + * scanned like a Figma component set. Badge icon and color are dynamically + * derived by `useIssueState({ state: 'CLOSED', stateReason })` in the live code: + * completed/PR/commit/project/no-reason -> CheckCircleIcon on `done` (purple); + * not-planned/duplicate -> CircleSlashIcon on `neutral` (gray). + */ +export const EventClosed = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Closed as completed */} +
    +

    Closed as completed

    + + + + + + + + {'closed this as '} + + completed + {' '} + + + +
    + + {/* Not planned — neutral (gray) badge. Timeline.Badge has no `neutral` + variant, so drive the documented `--timelineBadge-bgColor` hook inline + (portable for docs copy-paste; matches production `TimelineRow`). */} +
    +

    Closed as not planned

    + + + + + + + + {'closed this as '} + + not planned + {' '} + + + +
    + + {/* Closed via pull request */} +
    +

    Closed via pull request

    + + + + + + + + {'closed this as '} + + completed + + {' in '} + + #123 + {' '} + + + +
    + + {/* Closed via commit */} +
    +

    Closed via commit

    + + + + + + + + {'closed this as '} + + completed + + {' in '} + + abc1234 + {' '} + + + +
    + + {/* Closed via project (ProjectV2 status change). github-ui composes + the closer link as TableIcon + project title; there is no Primer + equivalent for github-ui's `ProjectV2` closer link. */} +
    +

    Closed via project

    + + + + + + + + {'closed this as '} + + completed + + {' by moving to Done in '} + + + Roadmap + {' '} + + + +
    + + {/* Closed as duplicate — neutral (gray) badge (see note above). github-ui + renders an `IssueLink` (state icon + title + #number + hovercard); + composed here from Primer primitives. */} +
    +

    Closed as duplicate

    + + + + + + + + {'closed this as a '} + + duplicate + + {' of '} + + + Fix the flaky avatar test{' '} + #42 + {' '} + + + +
    + + {/* Closed with no state reason */} +
    +

    Closed (no reason)

    + + + + + + + + {'closed this '} + + + +
    +
    +) + +/** + * The Issue-state event group (audit § 3). + * + * Six state-change variants. Badge icon/color verified against the live + * github-ui components (not the audit's icon column): Reopened maps to + * `useIssueState({ state: 'OPEN' })` -> `open` (green) badge; the rest are + * structural events with a default (muted) badge. + */ +export const EventState = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Reopened — open (green) badge via useIssueState (ReopenedEvent.tsx) */} +
    +

    Reopened

    + + + + + + + + {'reopened this '} + + + +
    + + {/* Transferred — github-ui's TransferredEvent renders the source repo as a + plain inline Link (the audit shows it bold; live code is canonical). */} +
    +

    Transferred

    + + + + + + + + {'transferred this issue from '} + + octo-org/legacy-repo + {' '} + + + +
    + + {/* Pinned */} +
    +

    Pinned

    + + + + + + + + {'pinned this issue '} + + + +
    + + {/* Unpinned */} +
    +

    Unpinned

    + + + + + + + + {'unpinned this issue '} + + + +
    + + {/* Converted to discussion — github-ui's ConvertedToDiscussionEvent links + the resulting discussion by number. */} +
    +

    Converted to discussion

    + + + + + + + + {'converted this issue into a discussion '} + + #123 + {' '} + + + +
    + + {/* Converted from draft */} +
    +

    Converted from draft

    + + + + + + + + {'converted this from a draft issue '} + + + +
    +
    +) + +/** + * The References event group (audit § 5). + * + * Cross-reference events. All use a default (muted) badge except where the + * badge icon is itself a PR state icon. The inline PR state icon mirrors + * github-ui's `sourceIcon('PullRequest', isDraft, isInMergeQueue)`; commit + * references compose a simplified `ReferencedEventInner` card. + */ +export const EventReferences = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Linked pull request — ConnectedEvent.tsx (CrossReferenceIcon badge, + open PR state icon inline before the PR title). */} +
    +

    Linked pull request

    + + + + + + + + {'linked a pull request that will close this issue '} + + + Add retry logic to the uploader + + #42 + + +
    + + {/* Unlinked pull request — DisconnectedEvent.tsx uses the PR state icon AS + the badge icon (leadingIcon={PullStateIcon}), so the badge octicon is + green (open) on a default badge. Verified the live render path is + active (in TIMELINE_ITEMS[__typename], current useIssueState hook, + story fixture is an OPEN PR) — the Figma audit's gray cross-reference + icon is behind the live code here. */} +
    +

    Unlinked pull request

    + + + + + + + + {'removed a link to a pull request '} + + + Add retry logic to the uploader + + #42 + + +
    + + {/* Single commit reference — ReferencedEvent.tsx. The timestamp renders + inline (showAgoTimestamp={false}) and the commit card is sub-content. */} +
    +

    Single commit reference

    + + + + + + + + {'added a commit that references this issue '} + + + +
    + + {/* Multiple commit references — same event, pluralized copy + N cards. */} +
    +

    Multiple commit references

    + + + + + + + + {'added 3 commits that reference this issue '} + + + +
    +
    +) + +/** + * The Duplicates event group (audit § 6). + * + * All four variants use a default (muted) `DuplicateIcon` badge. This is the + * first group with a right-controls case: github-ui's `MarkedAsDuplicateEvent` + * renders an "Undo" button via `TimelineRow.Trailing` when the viewer can undo. + * We map that to the `Timeline.Actions` slot (sibling of `Timeline.Body`). + */ +export const EventDuplicates = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Marked this as a duplicate of . Right-controls "Undo" button + (viewerCanUndo) → Timeline.Actions. The canonical issue is open, so its + IssueLink uses the open (green) state icon. */} +
    +

    Marked as duplicate

    + + + + + + + + {'marked this as a duplicate of '} + + + Upload fails on large avatars{' '} + #42 + {' '} + + + + + + +
    + + {/* Marked as a duplicate of this issue — no right controls. */} +
    +

    Marked as canonical

    + + + + + + + + {'marked '} + + + Retry uploads on transient errors{' '} + #43 + + {' as a duplicate of this issue '} + + + +
    + + {/* Unmarked this as a duplicate of . */} +
    +

    Unmarked as duplicate

    + + + + + + + + {'unmarked this as a duplicate of '} + + + Upload fails on large avatars{' '} + #42 + {' '} + + + +
    + + {/* Unmarked as a duplicate of this issue. */} +
    +

    Unmarked as canonical

    + + + + + + + + {'unmarked '} + + + Retry uploads on transient errors{' '} + #43 + + {' as a duplicate of this issue '} + + + +
    +
    +) + +/** + * The Moderation event group (audit § 7). + * + * Blocks use a default `BlockedIcon` badge; comment pin/unpin use `PinIcon`. + * github-ui's UserBlockedEvent renders the blocked user as a bold profile link + * (no avatar) via `ProfileReference`. + */ +export const EventModeration = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* User blocked (permanent) */} +
    +

    User blocked

    + + + + + + + + {'blocked '} + + six7 + {' '} + + + +
    + + {/* User temporarily blocked */} +
    +

    User temporarily blocked

    + + + + + + + + {'temporarily blocked '} + + six7 + {' '} + + + +
    + + {/* Comment pinned — IssueCommentPinnedEvent.tsx links the pinned comment. */} +
    +

    Comment pinned

    + + + + + + + + {'pinned a '} + + comment + {' '} + + + +
    + + {/* Comment unpinned */} +
    +

    Comment unpinned

    + + + + + + + + {'unpinned a '} + + comment + {' '} + + + +
    +
    +) + +/** + * The Issue-types event group (audit § 10). + * + * All three variants use a default `IssueOpenedIcon` badge. github-ui composes + * a colored `IssueTypeToken` (Primer `Token` with named-color styling) that + * links to the type-filtered issue list; approximated here with Primer + * functional color tokens. + */ +export const EventIssueTypes = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Type added */} +
    +

    Issue type added

    + + + + + + + + {'added the '} + + + + {' issue type '} + + + +
    + + {/* Type removed */} +
    +

    Issue type removed

    + + + + + + + + {'removed the '} + + + + {' issue type '} + + + +
    + + {/* Type changed */} +
    +

    Issue type changed

    + + + + + + + + {'changed the issue type from '} + + + + {' to '} + + + {' '} + + + +
    +
    +) + +/** + * The Issue-hierarchy event group (audit § 8). + * + * Sub-issue events use `IssueTracksIcon`; parent-issue events use + * `IssueTrackedByIcon`. All default (muted) badge. github-ui renders the + * linked issues in a bordered list (`TimelineRow.Secondary`) below the copy. + * + * SINGLE vs MULTIPLE: live code branches on `itemsToRender.length` — the + * leading copy switches singular/plural ("added a sub-issue" -> "added + * sub-issues") AND the secondary list grows from 1 to N `IssueLink` rows. + * (SubIssueAddedEvent.tsx: `LABELS.timeline.subIssueAdded[length === 1 ? + * 'single' : 'multiple']` + `itemsToRender.map(...)`.) + */ +export const EventIssueHierarchy = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Sub-issue added (single) */} +
    +

    Sub-issue added (single)

    + + + + + + + + {'added a sub-issue '} + + + +
    + + {/* Sub-issue added (multiple) — plural copy + N reference rows */} +
    +

    Sub-issues added (multiple)

    + + + + + + + + {'added sub-issues '} + + + +
    + + {/* Sub-issue removed (single) */} +
    +

    Sub-issue removed (single)

    + + + + + + + + {'removed a sub-issue '} + + + +
    + + {/* Sub-issues removed (multiple) */} +
    +

    Sub-issues removed (multiple)

    + + + + + + + + {'removed sub-issues '} + + + +
    + + {/* Parent issue added (single) */} +
    +

    Parent issue added (single)

    + + + + + + + + {'added a parent issue '} + + + +
    + + {/* Parent issues added (multiple) */} +
    +

    Parent issues added (multiple)

    + + + + + + + + {'added parent issues '} + + + +
    + + {/* Parent issue removed (single) */} +
    +

    Parent issue removed (single)

    + + + + + + + + {'removed a parent issue '} + + + +
    + + {/* Parent issues removed (multiple) */} +
    +

    Parent issues removed (multiple)

    + + + + + + + + {'removed parent issues '} + + + +
    +
    +) + +/** + * The Dependencies event group (audit § 9). + * + * Blocked-by and blocking events both use `BlockedIcon` (default badge). The + * dependent issues render in a bordered secondary list, like the hierarchy + * group. + * + * SINGLE vs MULTIPLE: live code branches on `itemsToRender.length`. Singular + * copy has no count ("marked this as blocked"); plural copy adds a count + * ("marked this as blocked by 2 issues") via `LABELS.timeline.blockedByAdded + * .multiple(count)`, and the secondary list grows from 1 to N rows. + */ +export const EventDependencies = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Blocked by (single) */} +
    +

    Blocked by (single)

    + + + + + + + + {'marked this as blocked '} + + + +
    + + {/* Blocked by (multiple) — count in copy + N rows */} +
    +

    Blocked by (multiple)

    + + + + + + + + {'marked this as blocked by 2 issues '} + + + +
    + + {/* Blocked by removed (single) */} +
    +

    Blocked by removed (single)

    + + + + + + + + {'unmarked this as blocked '} + + + +
    + + {/* Blocked by removed (multiple) */} +
    +

    Blocked by removed (multiple)

    + + + + + + + + {'unmarked this as blocked by 2 issues '} + + + +
    + + {/* Blocking (single) */} +
    +

    Blocking (single)

    + + + + + + + + {'marked this as blocking '} + + + +
    + + {/* Blocking (multiple) */} +
    +

    Blocking (multiple)

    + + + + + + + + {'marked this as blocking 2 issues '} + + + +
    + + {/* Blocking removed (single) */} +
    +

    Blocking removed (single)

    + + + + + + + + {'unmarked this as blocking '} + + + +
    + + {/* Blocking removed (multiple) */} +
    +

    Blocking removed (multiple)

    + + + + + + + + {'unmarked this as blocking 2 issues '} + + + +
    +
    +) + +/** + * The Issue-fields event group (audit § 11). + * + * Custom issue-field updates. The badge icon is per field TYPE, resolved by + * github-ui's `getFieldTypeOcticon`: text -> `TypographyIcon`, number -> + * `NumberIcon`, date -> `CalendarIcon`, single-select -> `SingleSelectIcon`. + * All default (muted) badge. Copy: set -> "set {field} to {value}", changed -> + * "changed {field} to {value}", cleared -> "cleared {field}" (no value). + * Single-select values render as a colored token; date as a formatted date. + * + * The final three variants are ROLLUPS (`RolledupIssueFieldEvent`): multiple + * field updates collapse into one row — "updated {…}", "removed {…}", or both + * joined by "and also". Rollup rows use the default-type (`TypographyIcon`) + * badge. (Audit notes the IssueField rollup window is 1 hour; fixtures model + * only the rendered variants, not the timing.) + */ +export const EventIssueFields = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Set text field */} +
    +

    Set · text

    + + + + + + + + {'set '} + Team + {' to '} + + Identity + {' '} + + + +
    + + {/* Set number field */} +
    +

    Set · number

    + + + + + + + + {'set '} + Story Points + {' to '} + + 5 + {' '} + + + +
    + + {/* Set date field */} +
    +

    Set · date

    + + + + + + + + {'set '} + Target Date + {' to '} + + Aug 1, 2022 + {' '} + + + +
    + + {/* Set single-select field — value is a colored token */} +
    +

    Set · single select

    + + + + + + + + {'set '} + Priority + {' to '} + {/* github-ui renders an `IssueFieldSingleSelectValueToken` (colored + Primer Token) for select values; approximated with functional + color tokens. */} + + + {' '} + + + +
    + + {/* Changed text field */} +
    +

    Changed · text

    + + + + + + + + {'changed '} + Team + {' to '} + + Platform + {' '} + + + +
    + + {/* Changed number field */} +
    +

    Changed · number

    + + + + + + + + {'changed '} + Story Points + {' to '} + + 8 + {' '} + + + +
    + + {/* Changed date field */} +
    +

    Changed · date

    + + + + + + + + {'changed '} + Target Date + {' to '} + + Aug 15, 2022 + {' '} + + + +
    + + {/* Changed single-select field */} +
    +

    Changed · single select

    + + + + + + + + {'changed '} + Priority + {' to '} + + + {' '} + + + +
    + + {/* Cleared text field — no value */} +
    +

    Cleared · text

    + + + + + + + + {'cleared '} + Team + + +
    + + {/* Cleared number field */} +
    +

    Cleared · number

    + + + + + + + + {'cleared '} + Story Points + + +
    + + {/* Cleared date field */} +
    +

    Cleared · date

    + + + + + + + + {'cleared '} + Target Date + + +
    + + {/* Cleared single-select field */} +
    +

    Cleared · single select

    + + + + + + + + {'cleared '} + Priority + + +
    + + {/* Rollup: updated only — multiple field updates collapsed into one row */} +
    +

    Rollup · updated

    + + + + + + + + {'updated '} + Team + + Platform + + {' and '} + Story Points + + 8 + {' '} + + + +
    + + {/* Rollup: removed only */} +
    +

    Rollup · removed

    + + + + + + + + {'removed '} + Team + {' and '} + Priority + + +
    + + {/* Rollup: updated and also removed — combined row joined by "and also" */} +
    +

    Rollup · updated and removed

    + + + + + + + + {'updated '} + Team + + Platform + + {', and also removed '} + Priority + + +
    +
    +) + +/** + * The Project event group — shared timeline events (ProjectV2), Issue version. + * + * Sourced from the live React `timeline-items` components, which are the ISSUE + * implementation of these "shared" events: `AddedToProjectV2Event.tsx`, + * `RemovedFromProjectV2Event.tsx`, `ProjectV2ItemStatusChangedEvent.tsx`, and + * the shared `ProjectV2.tsx` sub-component. All three use a `TableIcon` badge. + * + * Issue-version `{ProjectV2}` reference (live `ProjectV2.tsx`): an inline + * default-colored `TableIcon` octicon, then a `` with REGULAR + * weight and `color: var(--fgColor-default)` (NOT bold, NOT accent-blue). The + * `inline` prop supplies the always-on underline. Status text is PLAIN TEXT + * (live `ProjectV2ItemStatusChangedEvent.tsx` renders `status`/`previousStatus` + * as bare strings, not bold). + * + * PR-SURFACE DIVERGENCE (build the PR version from ERB later, NOT from this): + * The PR (ERB) path renders these events DIFFERENTLY — + * - Project link: `app/views/issues/events/_memex_project_link.html.erb` uses + * `` — i.e. a BOLD project name, NO inline + * `TableIcon`, and hover-only underline. + * - Status: `_project_item_status_changed_event.html.erb` wraps the status in + * `` (BOLD). + * So: Issue = inline icon + regular-weight always-underlined link + plain-text + * status; PR = bold link, no icon, bold status. Whoever builds the PR surface + * must use the ERB spec above, not this Issue composition. + */ +export const EventProject = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Added to project */} +
    +

    Added to project

    + + + + + + + + {'added this to '} + {/* Issue-version ProjectV2 reference (github-ui `ProjectV2.tsx`). */} + + + Roadmap + {' '} + + + +
    + + {/* Removed from project */} +
    +

    Removed from project

    + + + + + + + + {'removed this from '} + + + Roadmap + {' '} + + + +
    + + {/* Project status changed. Two forms per live + `ProjectV2ItemStatusChangedEvent.tsx`: with no previous status, + "moved this to {status} in {project}"; with a previous status, + "moved this from {previousStatus} to {status} in {project}". Status + strings are PLAIN TEXT (not bold). Both forms shown under one caption. */} +
    +

    Project status changed

    + + + + + + + + {'moved this to In Progress in '} + + + Roadmap + {' '} + + + + + + + + + {'moved this from Todo to In Progress in '} + + + Roadmap + {' '} + + + +
    +
    +) + +/** + * The Labels event group — shared timeline events (Issue version). + * + * Sourced from live `LabeledEvent.tsx` / `UnlabeledEvent.tsx` (badge `TagIcon`). + * Copy is just "added {label}" / "removed {label}" (live `LABELS.timeline.added` + * / `removed`, then the `Label` pill — no "the"/"label" filler words). The + * rolled-up form (`RolledupLabeledEvent`) joins them: "added {…} and removed {…}". + * + * Labels render as colored pills; the color comes from the label in live code + * (`@github-ui/label-token` `LabelToken`). We compose the closest Primer + * equivalent with `Token` + the label's semantic color tokens (same pattern as + * the IssueTypes group). + * + * PR ERB source: `app/views/issues/events/_labeled_event.html.erb` — verify on + * the PR build (label pill markup is shared, copy is the same). + */ +export const EventLabels = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Label added */} +
    +

    Label added

    + + + + + + + + {'added '} + + + {' '} + + + +
    + + {/* Label removed */} +
    +

    Label removed

    + + + + + + + + {'removed '} + + + {' '} + + + +
    + + {/* Added + removed (rollup) — RolledupLabeledEvent joins both renderings + with "and" between them. */} +
    +

    Labels added and removed

    + + + + + + + + {'added '} + + + + {' and removed '} + + + {' '} + + + +
    +
    +) + +/** + * The Title event group — shared timeline event (Issue version). + * + * Sourced from live `RenamedTitleEvent.tsx` (badge `PencilIcon`). Copy is + * "changed the title {old} {new}" where the OLD title is struck through (``, + * default color) and the NEW title is plain (default color, no underline). NOTE + * audit-vs-live DRIFT: the audit phrases this "changed the title from {old} to + * {new}", but live renders NO "from"/"to" words — just strikethrough old then + * new (`LABELS.timeline.renamedTitle` = "changed the title"). + * + * PR ERB source: `app/views/issues/events/_renamed_event.html.erb` — verify on + * the PR build. + */ +export const EventTitle = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Title changed */} +
    +

    Title changed

    + + + + + + + + {'changed the title '} + Fix the uplaod bug Fix the upload bug + + +
    +
    +) + +/** + * The Milestones event group — shared timeline events (Issue version). + * + * Sourced from live `MilestonedEvent.tsx` / `DemilestonedEvent.tsx` (badge + * `MilestoneIcon`). Copy: "added this to the {milestone} milestone" / + * "removed this from the {milestone} milestone" (`LABELS.timeline.addedToMilestone` + * / `removedFromMilestone` + the milestone link + `milestone`). The milestone + * link is a regular-weight, default-color `` (live `.milestoneLink` + * = `color: var(--fgColor-default)`); the `inline` prop gives the always-on + * underline (also satisfies the high-contrast a11y rule). + * + * PR ERB source: `app/views/issues/events/_milestoned_event.html.erb` — verify + * on the PR build. + */ +export const EventMilestones = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Added to milestone */} +
    +

    Added to milestone

    + + + + + + + + {'added this to the '} + + v2.0 + + {' milestone '} + + + +
    + + {/* Removed from milestone */} +
    +

    Removed from milestone

    + + + + + + + + {'removed this from the '} + + v2.0 + + {' milestone '} + + + +
    +
    +) + +/** + * The Assignments event group — shared timeline events (Issue version). + * + * Sourced from live `AssignedEvent.tsx` / `UnassignedEvent.tsx` (badge + * `PersonIcon`). Copy: self → "self-assigned this" / "removed their assignment" + * (no actor-name prefix); other → "assigned {user}" / "unassigned {user}"; + * multiple → joined with "and". The assignee is a BOLD text link with NO avatar + * in the React Issue impl (`AssignmentEventAssignee` → `ProfileReference` inside + * a ``, no avatar element). + * + * PR/Dependabot DIVERGENCE: Dependabot's assignment events render the assignee + * via a different ActorComponent that DOES include an inline avatar (avatar + + * name), unlike this Issue impl (bold name only). Whoever builds the + * Dependabot/PR surface must use that ActorComponent, not this composition. + */ +export const EventAssignments = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Self-assigned */} +
    +

    Self-assigned

    + + + + + + + + {'self-assigned this '} + + + +
    + + {/* Assigned someone else — assignee is a bold link, no avatar. */} +
    +

    Assigned

    + + + + + + + + {'assigned '} + + hubot + {' '} + + + +
    + + {/* Assigned multiple — joined with "and". */} +
    +

    Assigned multiple

    + + + + + + + + {'assigned '} + + hubot + + {' and '} + + octocat + {' '} + + + +
    + + {/* Self-unassigned */} +
    +

    Self-unassigned

    + + + + + + + + {'removed their assignment '} + + + +
    + + {/* Unassigned someone else */} +
    +

    Unassigned

    + + + + + + + + {'unassigned '} + + hubot + {' '} + + + +
    + + {/* Unassigned multiple — joined with "and". */} +
    +

    Unassigned multiple

    + + + + + + + + {'unassigned '} + + hubot + + {' and '} + + octocat + {' '} + + + +
    +
    +) + +/** + * The Lock/Unlock event group — shared timeline events (Issue version). + * + * Sourced from live `LockedEvent.tsx` / `UnlockedEvent.tsx`. Locked uses the + * `LockIcon` badge; UNLOCKED uses the `UnlockIcon` badge (badge DRIFT vs the + * single "LockIcon" family note — live `UnlockedEvent` passes + * `leadingIcon={UnlockIcon}`). Locked copy: "locked as {reason} and limited + * conversation to collaborators" (reason from `VALUES.lockedReasonStrings`: + * off topic / resolved / spam / too heated); with no reason: "locked and limited + * conversation to collaborators". Unlocked copy: "unlocked this conversation". + * + * PR ERB source: `app/views/issues/events/_locked_event.html.erb` — verify on + * the PR build. + */ +export const EventLockUnlock = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Locked with reason — one row per reason (off topic / resolved / spam / + too heated). */} +
    +

    Locked (with reason)

    + + + + + + + + {'locked as off topic and limited conversation to collaborators '} + + + + + + + + + {'locked as resolved and limited conversation to collaborators '} + + + + + + + + + {'locked as spam and limited conversation to collaborators '} + + + + + + + + + {'locked as too heated and limited conversation to collaborators '} + + + +
    + + {/* Locked (no reason) */} +
    +

    Locked (no reason)

    + + + + + + + + {'locked and limited conversation to collaborators '} + + + +
    + + {/* Unlocked — UnlockIcon badge (not LockIcon). */} +
    +

    Unlocked

    + + + + + + + + {'unlocked this conversation '} + + + +
    +
    +) + +/** + * The Comment-deleted event group — shared timeline event (Issue version). + * + * Sourced from live `CommentDeletedEvent.tsx` (badge `TrashIcon`). Copy: + * "deleted a comment from {user}" (`LABELS.timeline.deletedACommentFrom` + the + * deleted comment author as an inline `` wrapping a `ProfileReference`). + * The author link uses the `inline` prop (always-on underline / high-contrast). + * + * PR ERB source: `app/views/issues/events/_comment_deleted_event.html.erb` — + * verify on the PR build. + */ +export const EventCommentDeleted = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Comment deleted */} +
    +

    Comment deleted

    + + + + + + + + {'deleted a comment from '} + + octocat + {' '} + + + +
    +
    +) + +/** + * The Cross-references event group — shared timeline events (Issue version). + * + * Sourced from live `CrossReferencedEvent.tsx` + `IssueLink.tsx` (badge + * `LinkExternalIcon`). The body message is "mentioned this" (then timestamp); + * the closing-PR form is "linked a pull request that will close this issue"; + * rolled-up forms read "mentioned this in {n} issues / pull requests". The + * referenced source is rendered in a Secondary slot as an `IssueLink` row: + * a state octicon (from `useIssueState().sourceIcon` — open issue green + * `IssueOpenedIcon`, open PR green `GitPullRequestIcon`) + the title + the + * abbreviated #number reference. We reuse the plain (borderless) `.RefList`. + * + * PR ERB source: `app/views/issues/events/_cross_referenced_event.html.erb` — + * verify on the PR build. + */ +export const EventCrossReferences = () => ( +
    { + if ((e.target as HTMLElement).closest('a')) e.preventDefault() + }} + > + {/* Mentioned from an issue */} +
    +

    Mentioned in an issue

    + + + + + + + + {'mentioned this '} + + + +
    + + {/* Mentioned from a pull request */} +
    +

    Mentioned in a pull request

    + + + + + + + + {'mentioned this '} + + + +
    + + {/* Linked a closing pull request */} +
    +

    Linked a closing pull request

    + + + + + + + + {'linked a pull request that will close this issue '} + + + +
    +
    +)