diff --git a/webui2/src/components/bugs/CommentBox.tsx b/webui2/src/components/bugs/CommentBox.tsx index 4a078592d30a870af816a9f2ba0b18b6e36176af..6cea3b68b0b23a782b7824f0ce5fe6994e0c41cc 100644 --- a/webui2/src/components/bugs/CommentBox.tsx +++ b/webui2/src/components/bugs/CommentBox.tsx @@ -74,7 +74,7 @@ export function CommentBox({ bugPrefix, bugStatus, ref_ }: CommentBoxProps) { return ( - + diff --git a/webui2/src/components/bugs/IssueRow.stories.tsx b/webui2/src/components/bugs/IssueRow.stories.tsx index 01a3a066262ba3d4d29731525cda06b490520759..c136fdebf396e2cc2f686225dd54dbf83c9e45c1 100644 --- a/webui2/src/components/bugs/IssueRow.stories.tsx +++ b/webui2/src/components/bugs/IssueRow.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { formatDistanceToNow } from "date-fns"; +import type { BugSummaryFragment } from "@/__generated__/graphql"; import { Status } from "@/__generated__/graphql"; import { withRouter } from "@/../.storybook/decorators"; @@ -15,109 +16,91 @@ const meta = { export default meta; type Story = StoryObj; -const ago = formatDistanceToNow(new Date(Date.now() - 3600 * 1000), { addSuffix: true }); +// Mock data shaped like BugSummaryFragment from GraphQL +const openBug: BugSummaryFragment = { + id: "abc123", + humanId: "a1b2c3", + status: Status.Open, + title: "Fix login page crash on empty email", + labels: [ + { name: "bug", color: { R: 252, G: 41, B: 41 } }, + { name: "priority", color: { R: 255, G: 152, B: 0 } }, + ], + author: { id: "u1", humanId: "user1", displayName: "Jane Doe", avatarUrl: null }, + createdAt: new Date(Date.now() - 3600 * 1000).toISOString(), + comments: { totalCount: 3 }, +}; -export const OpenIssue: Story = { - args: { children: null }, - render: () => ( +const closedBug: BugSummaryFragment = { + id: "def456", + humanId: "d4e5f6", + status: Status.Closed, + title: "Add dark mode support", + labels: [{ name: "enhancement", color: { R: 163, G: 230, B: 53 } }], + author: { id: "u2", humanId: "user2", displayName: "Bob Smith", avatarUrl: null }, + createdAt: new Date(Date.now() - 86400 * 1000).toISOString(), + comments: { totalCount: 12 }, +}; + +const noLabelsBug: BugSummaryFragment = { + id: "ghi789", + humanId: "g7h8i9", + status: Status.Open, + title: "Simple issue with no labels", + labels: [], + author: { id: "u3", humanId: "user3", displayName: "Alice Wu", avatarUrl: null }, + createdAt: new Date(Date.now() - 7200 * 1000).toISOString(), + comments: { totalCount: 0 }, +}; + +function BugRow({ bug }: { bug: BugSummaryFragment }) { + const ago = formatDistanceToNow(new Date(bug.createdAt), { addSuffix: true }); + return ( - +
- Fix login page crash on empty email + {bug.title} - - + {bug.labels.map((l) => ( + + ))} - #a1b2c3 opened {ago} by Jane Doe + #{bug.humanId} opened {ago} by{" "} + + {bug.author.displayName} +
- +
- ), + ); +} + +export const OpenIssue: Story = { + args: { children: null }, + render: () => , }; export const ClosedIssue: Story = { args: { children: null }, - render: () => ( - - -
- - - Add dark mode support - - - - #d4e5f6 opened {ago} -
- -
- ), + render: () => , }; export const NoLabelsNoComments: Story = { args: { children: null }, - render: () => ( - - -
- - - Simple issue with no labels - - - #abc123 opened {ago} by Bob -
- -
- ), + render: () => , }; export const List: Story = { args: { children: null }, render: () => (
- - -
- - - Fix login page crash on empty email - - - - #a1b2c3 opened {ago} by Jane Doe -
- -
- - -
- - - Add dark mode support - - - - #d4e5f6 opened {ago} by Bob -
- -
- - -
- - - Update dependencies - - - #g7h8i9 opened {ago} by Alice -
- -
+ + +
), }; diff --git a/webui2/src/components/bugs/LabelBadge.stories.tsx b/webui2/src/components/bugs/LabelBadge.stories.tsx index 48c73811230c34401697841f7e5b913f9f251cfe..7813b02d0a28e95e07df1191253870b23881f9fb 100644 --- a/webui2/src/components/bugs/LabelBadge.stories.tsx +++ b/webui2/src/components/bugs/LabelBadge.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { fn } from "storybook/test"; +import type { LabelFieldsFragment } from "@/__generated__/graphql"; + import { LabelBadge } from "./LabelBadge"; const meta = { @@ -10,45 +12,39 @@ const meta = { export default meta; type Story = StoryObj; +// Mock data shaped like LabelFieldsFragment from GraphQL +const bug: LabelFieldsFragment = { name: "bug", color: { R: 252, G: 41, B: 41 } }; +const enhancement: LabelFieldsFragment = { name: "enhancement", color: { R: 163, G: 230, B: 53 } }; +const documentation: LabelFieldsFragment = { name: "documentation", color: { R: 30, G: 80, B: 160 } }; +const helpWanted: LabelFieldsFragment = { name: "help wanted", color: { R: 0, G: 150, B: 136 } }; +const wontfix: LabelFieldsFragment = { name: "wontfix", color: { R: 200, G: 200, B: 200 } }; +const priority: LabelFieldsFragment = { name: "priority", color: { R: 255, G: 152, B: 0 } }; + +const allLabels = [bug, enhancement, documentation, helpWanted, wontfix, priority]; + export const Default: Story = { - args: { - name: "bug", - color: { R: 252, G: 41, B: 41 }, - }, + args: bug, }; export const LightBackground: Story = { - args: { - name: "enhancement", - color: { R: 163, G: 230, B: 53 }, - }, + args: enhancement, }; export const DarkBackground: Story = { - args: { - name: "documentation", - color: { R: 30, G: 80, B: 160 }, - }, + args: documentation, }; export const Clickable: Story = { - args: { - name: "feature", - color: { R: 100, G: 200, B: 150 }, - onClick: fn(), - }, + args: { ...helpWanted, onClick: fn() }, }; export const AllColors: Story = { - args: { name: "", color: { R: 0, G: 0, B: 0 } }, + args: bug, render: () => (
- - - - - - + {allLabels.map((label) => ( + + ))}
), }; diff --git a/webui2/src/components/bugs/LabelBadge.tsx b/webui2/src/components/bugs/LabelBadge.tsx index 8a5e0c47d4f0549ca8b9d8d1647e857844128879..ecf01dc2c615855d16255098625c59fde04313fd 100644 --- a/webui2/src/components/bugs/LabelBadge.tsx +++ b/webui2/src/components/bugs/LabelBadge.tsx @@ -1,11 +1,11 @@ import { createLink, type LinkComponent } from "@tanstack/react-router"; import * as React from "react"; -interface LabelBadgeProps { - name: string; - color: { R: number; G: number; B: number }; +import type { LabelFieldsFragment } from "@/__generated__/graphql"; + +type LabelBadgeProps = LabelFieldsFragment & { className?: string; -} +}; function contrastColor(r: number, g: number, b: number): string { // Perceived luminance — pick black or white text for readability @@ -15,44 +15,46 @@ function contrastColor(r: number, g: number, b: number): string { // Coloured label pill. Always renders as a . // Use LabelBadgeLink for a clickable variant that navigates. -const LabelBadge = React.forwardRef, "color">>( - ({ name, color, className, ...props }, ref) => { +const LabelBadge = React.forwardRef< + HTMLSpanElement, + LabelBadgeProps & Omit, "color"> +>(({ name, color, className, ...props }, ref) => { + const bg = `rgb(${color.R},${color.G},${color.B})`; + const text = contrastColor(color.R, color.G, color.B); + + return ( + + {name} + + ); +}); +LabelBadge.displayName = "LabelBadge"; + +// LabelBadge as a TanStack Router link — renders as with label styling. +const CreatedLabelBadgeLink = createLink( + React.forwardRef< + HTMLAnchorElement, + LabelBadgeProps & Omit, "color"> + >(({ name, color, className, ...props }, ref) => { const bg = `rgb(${color.R},${color.G},${color.B})`; const text = contrastColor(color.R, color.G, color.B); return ( - {name} - + ); - }, -); -LabelBadge.displayName = "LabelBadge"; - -// LabelBadge as a TanStack Router link — renders as with label styling. -const CreatedLabelBadgeLink = createLink( - React.forwardRef, "color">>( - ({ name, color, className, ...props }, ref) => { - const bg = `rgb(${color.R},${color.G},${color.B})`; - const text = contrastColor(color.R, color.G, color.B); - - return ( - - {name} - - ); - }, - ), + }), ); const LabelBadgeLink: LinkComponent = (props) => { @@ -60,3 +62,4 @@ const LabelBadgeLink: LinkComponent = (props) => { }; export { LabelBadge, LabelBadgeLink }; +export type { LabelBadgeProps }; diff --git a/webui2/src/components/bugs/Timeline.tsx b/webui2/src/components/bugs/Timeline.tsx index 7ec70ec94c4038c818221a94987cf9fc936aacb5..70006d746cd67b87df17a9f283ecf251993f1d9e 100644 --- a/webui2/src/components/bugs/Timeline.tsx +++ b/webui2/src/components/bugs/Timeline.tsx @@ -95,7 +95,7 @@ function CommentItem({ return ( - +
bug + + priority +

- #a1b2c3 opened + # + a1b2c3 + opened about 1 hour ago - by Jane Doe + by + + + Jane Doe +

-
- #d4e5f6 opened - about 1 hour ago - by Bob + # + d4e5f6 + opened + 1 day ago + by + + + Bob Smith +

+
+ + 12 +
-
- Update dependencies + Simple issue with no labels

- #g7h8i9 opened - about 1 hour ago - by Alice + # + g7h8i9 + opened + about 2 hours ago + by + + + Alice Wu +

-
- - 7 -
@@ -292,7 +335,7 @@ exports[`IssueRow/List matches snapshot 1`] = ` exports[`IssueRow/NoLabelsNoComments matches snapshot 1`] = `
@@ -406,9 +452,12 @@ exports[`IssueRow/OpenIssue matches snapshot 1`] = `

- #a1b2c3 opened + # + a1b2c3 + opened about 1 hour ago - by + by + - feature + help wanted

`; diff --git a/webui2/src/components/ui/comment-card.stories.tsx b/webui2/src/components/ui/comment-card.stories.tsx index 03b0490d1362eeb8f666250b2b3d47b4619bf3ea..8af3d4c46b158cf91748a02866a267a62c3e7e4f 100644 --- a/webui2/src/components/ui/comment-card.stories.tsx +++ b/webui2/src/components/ui/comment-card.stories.tsx @@ -1,5 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { IdentitySummaryFragment } from "@/__generated__/graphql"; + import * as CommentCard from "./comment-card"; const meta = { @@ -9,14 +11,36 @@ const meta = { export default meta; type Story = StoryObj; +// Mock data shaped like IdentitySummaryFragment +const jane: IdentitySummaryFragment = { + id: "1", + humanId: "jane1", + displayName: "Jane Doe", + avatarUrl: null, +}; + +const bob: IdentitySummaryFragment = { + id: "2", + humanId: "bob1", + displayName: "Bob Smith", + avatarUrl: "https://github.com/shadcn.png", +}; + +const alice: IdentitySummaryFragment = { + id: "3", + humanId: "alice1", + displayName: "Alice Wu", + avatarUrl: null, +}; + export const Default: Story = { args: { children: null }, render: () => ( - + - Jane Doe + {jane.displayName} 2 hours ago @@ -31,10 +55,10 @@ export const WithEditButton: Story = { args: { children: null }, render: () => ( - + - Bob Smith + {bob.displayName} 1 day ago edited