Skip to content

Commit b4d727d

Browse files
committed
feat(ui): converts all remaining native title attrs to Tooltip
1 parent 7144e95 commit b4d727d

File tree

9 files changed

+129
-107
lines changed

9 files changed

+129
-107
lines changed

src/app/components/dashboard/IgnoreBadge.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createSignal, For, Show } from "solid-js";
22
import type { IgnoredItem } from "../../stores/view";
3+
import { Tooltip } from "../shared/Tooltip";
34

45
interface IgnoreBadgeProps {
56
items: IgnoredItem[];
@@ -84,9 +85,11 @@ export default function IgnoreBadge(props: IgnoreBadgeProps) {
8485
<p class="text-xs text-base-content/60 truncate">
8586
{item.repo}
8687
</p>
87-
<p class="text-sm text-base-content truncate" title={item.title}>
88-
{item.title}
89-
</p>
88+
<Tooltip content={item.title}>
89+
<p class="text-sm text-base-content truncate">
90+
{item.title}
91+
</p>
92+
</Tooltip>
9093
<p class="text-xs text-base-content/40">
9194
Ignored {formatDate(item.ignoredAt)}
9295
</p>

src/app/components/dashboard/ItemRow.tsx

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isSafeGitHubUrl } from "../../lib/url";
33
import { relativeTime, shortRelativeTime, formatCount } from "../../lib/format";
44
import { expandEmoji } from "../../lib/emoji";
55
import { labelColorClass } from "../../lib/label-colors";
6+
import { Tooltip } from "../shared/Tooltip";
67

78
export interface ItemRowProps {
89
repo: string;
@@ -78,14 +79,15 @@ export default function ItemRow(props: ItemRowProps) {
7879

7980
{/* Repo badge */}
8081
<Show when={!props.hideRepo}>
81-
<span
82-
class={`shrink-0 inline-flex items-center rounded-full font-mono font-medium
83-
bg-primary/10 text-primary
84-
${isCompact() ? "text-xs px-2 py-0.5" : "text-xs px-2.5 py-1"}`}
85-
title={props.repo}
86-
>
87-
{props.repo}
88-
</span>
82+
<Tooltip content={props.repo} class="relative z-10">
83+
<span
84+
class={`shrink-0 inline-flex items-center rounded-full font-mono font-medium
85+
bg-primary/10 text-primary
86+
${isCompact() ? "text-xs px-2 py-0.5" : "text-xs px-2.5 py-1"}`}
87+
>
88+
{props.repo}
89+
</span>
90+
</Tooltip>
8991
</Show>
9092

9193
{/* Main content */}
@@ -127,22 +129,24 @@ export default function ItemRow(props: ItemRowProps) {
127129
<div class="relative z-10">{props.surfacedByBadge}</div>
128130
</Show>
129131
<span class="inline-flex items-center gap-1 whitespace-nowrap">
130-
<time
131-
datetime={props.createdAt}
132-
title={staticDateInfo().createdTitle}
133-
aria-label={dateDisplay().createdLabel}
134-
>
135-
{dateDisplay().created}
136-
</time>
137-
<Show when={shouldShowUpdated()}>
138-
<span aria-hidden="true">{"\u00B7"}</span>
132+
<Tooltip content={staticDateInfo().createdTitle} class="relative z-10">
139133
<time
140-
datetime={props.updatedAt}
141-
title={staticDateInfo().updatedTitle}
142-
aria-label={dateDisplay().updatedLabel}
134+
datetime={props.createdAt}
135+
aria-label={dateDisplay().createdLabel}
143136
>
144-
{dateDisplay().updated}
137+
{dateDisplay().created}
145138
</time>
139+
</Tooltip>
140+
<Show when={shouldShowUpdated()}>
141+
<span aria-hidden="true">{"\u00B7"}</span>
142+
<Tooltip content={staticDateInfo().updatedTitle} class="relative z-10">
143+
<time
144+
datetime={props.updatedAt}
145+
aria-label={dateDisplay().updatedLabel}
146+
>
147+
{dateDisplay().updated}
148+
</time>
149+
</Tooltip>
146150
</Show>
147151
</span>
148152
<Show when={props.isPolling}>
@@ -169,33 +173,34 @@ export default function ItemRow(props: ItemRowProps) {
169173
</div>
170174

171175
{/* Ignore button — visible on hover */}
172-
<button
173-
data-ignore-btn
174-
onClick={() => props.onIgnore()}
175-
class={`relative z-10 shrink-0 self-center rounded p-1
176-
text-base-content/30
177-
hover:text-error
178-
opacity-0 group-hover:opacity-100 focus:opacity-100
179-
transition-opacity focus:outline-none focus:ring-2 focus:ring-error`}
180-
title="Ignore this item"
181-
aria-label={`Ignore #${props.number} ${props.title}`}
182-
>
183-
{/* Eye-slash icon */}
184-
<svg
185-
xmlns="http://www.w3.org/2000/svg"
186-
class={isCompact() ? "h-3.5 w-3.5" : "h-4 w-4"}
187-
viewBox="0 0 20 20"
188-
fill="currentColor"
189-
aria-hidden="true"
176+
<Tooltip content="Ignore" class="relative z-10">
177+
<button
178+
data-ignore-btn
179+
onClick={() => props.onIgnore()}
180+
class={`shrink-0 self-center rounded p-1
181+
text-base-content/30
182+
hover:text-error
183+
opacity-0 group-hover:opacity-100 focus:opacity-100
184+
transition-opacity focus:outline-none focus:ring-2 focus:ring-error`}
185+
aria-label={`Ignore #${props.number} ${props.title}`}
190186
>
191-
<path
192-
fill-rule="evenodd"
193-
d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z"
194-
clip-rule="evenodd"
195-
/>
196-
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
197-
</svg>
198-
</button>
187+
{/* Eye-slash icon */}
188+
<svg
189+
xmlns="http://www.w3.org/2000/svg"
190+
class={isCompact() ? "h-3.5 w-3.5" : "h-4 w-4"}
191+
viewBox="0 0 20 20"
192+
fill="currentColor"
193+
aria-hidden="true"
194+
>
195+
<path
196+
fill-rule="evenodd"
197+
d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z"
198+
clip-rule="evenodd"
199+
/>
200+
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
201+
</svg>
202+
</button>
203+
</Tooltip>
199204
</div>
200205
);
201206
}

src/app/components/dashboard/PullRequestsTab.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,13 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
608608
</span>
609609
</Show>
610610
<Show when={pr.enriched !== false && pr.reviewerLogins.length > 0}>
611-
<span class="text-xs text-base-content/60" title={pr.reviewerLogins.join(", ")}>
612-
Reviewers: {pr.reviewerLogins.slice(0, 5).join(", ")}
613-
{pr.reviewerLogins.length > 5 && ` +${pr.reviewerLogins.length - 5} more`}
614-
{pr.totalReviewCount > pr.reviewerLogins.length && ` (${pr.totalReviewCount} total)`}
615-
</span>
611+
<Tooltip content={pr.reviewerLogins.join(", ")} focusable>
612+
<span class="text-xs text-base-content/60">
613+
Reviewers: {pr.reviewerLogins.slice(0, 5).join(", ")}
614+
{pr.reviewerLogins.length > 5 && ` +${pr.reviewerLogins.length - 5} more`}
615+
{pr.totalReviewCount > pr.reviewerLogins.length && ` (${pr.totalReviewCount} total)`}
616+
</span>
617+
</Tooltip>
616618
</Show>
617619
</div>
618620
</ItemRow>

src/app/components/dashboard/WorkflowRunRow.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { WorkflowRun } from "../../services/api";
33
import type { Config } from "../../stores/config";
44
import { isSafeGitHubUrl } from "../../lib/url";
55
import { relativeTime, formatDuration } from "../../lib/format";
6+
import { Tooltip } from "../shared/Tooltip";
67

78
interface WorkflowRunRowProps {
89
run: WorkflowRun;
@@ -182,39 +183,41 @@ export default function WorkflowRunRow(props: WorkflowRunRowProps) {
182183
{durationLabel(props.run)}
183184
</span>
184185

185-
<time
186-
class="text-xs text-base-content/40 shrink-0"
187-
datetime={props.run.createdAt}
188-
title={createdTitle()}
189-
>
190-
{timeLabel()}
191-
</time>
186+
<Tooltip content={createdTitle()}>
187+
<time
188+
class="text-xs text-base-content/40 shrink-0"
189+
datetime={props.run.createdAt}
190+
>
191+
{timeLabel()}
192+
</time>
193+
</Tooltip>
192194

193195
<Show when={props.isPolling}>
194196
<span class="loading loading-spinner loading-xs text-base-content/40" />
195197
</Show>
196198

197-
<button
198-
onClick={() => props.onIgnore(props.run)}
199-
class="shrink-0 rounded p-1 text-base-content/30 hover:text-error opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity focus:outline-none focus:ring-2 focus:ring-error"
200-
title="Ignore this run"
201-
aria-label={`Ignore run ${props.run.name}`}
202-
>
203-
<svg
204-
xmlns="http://www.w3.org/2000/svg"
205-
class="h-4 w-4"
206-
viewBox="0 0 20 20"
207-
fill="currentColor"
208-
aria-hidden="true"
199+
<Tooltip content="Ignore">
200+
<button
201+
onClick={() => props.onIgnore(props.run)}
202+
class="shrink-0 rounded p-1 text-base-content/30 hover:text-error opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity focus:outline-none focus:ring-2 focus:ring-error"
203+
aria-label={`Ignore run ${props.run.name}`}
209204
>
210-
<path
211-
fill-rule="evenodd"
212-
d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z"
213-
clip-rule="evenodd"
214-
/>
215-
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
216-
</svg>
217-
</button>
205+
<svg
206+
xmlns="http://www.w3.org/2000/svg"
207+
class="h-4 w-4"
208+
viewBox="0 0 20 20"
209+
fill="currentColor"
210+
aria-hidden="true"
211+
>
212+
<path
213+
fill-rule="evenodd"
214+
d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z"
215+
clip-rule="evenodd"
216+
/>
217+
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
218+
</svg>
219+
</button>
220+
</Tooltip>
218221
</div>
219222
);
220223
}

src/app/components/dashboard/WorkflowSummaryCard.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createMemo, For, Show } from "solid-js";
22
import type { WorkflowRun } from "../../services/api";
33
import WorkflowRunRow from "./WorkflowRunRow";
4+
import { Tooltip } from "../shared/Tooltip";
45

56
interface WorkflowSummaryCardProps {
67
workflowName: string;
@@ -81,19 +82,25 @@ export default function WorkflowSummaryCard(props: WorkflowSummaryCardProps) {
8182
</span>
8283
<div class="flex items-center gap-1.5 shrink-0">
8384
<Show when={counts().success > 0}>
84-
<span class="text-xs font-medium text-success" title={`${counts().success} successful`} aria-label={`${counts().success} successful`}>
85-
{counts().success}
86-
</span>
85+
<Tooltip content={`${counts().success} successful`} focusable>
86+
<span class="text-xs font-medium text-success" aria-label={`${counts().success} successful`}>
87+
{counts().success}
88+
</span>
89+
</Tooltip>
8790
</Show>
8891
<Show when={counts().failure > 0}>
89-
<span class="text-xs font-medium text-error" title={`${counts().failure} failed`} aria-label={`${counts().failure} failed`}>
90-
{counts().failure}
91-
</span>
92+
<Tooltip content={`${counts().failure} failed`} focusable>
93+
<span class="text-xs font-medium text-error" aria-label={`${counts().failure} failed`}>
94+
{counts().failure}
95+
</span>
96+
</Tooltip>
9297
</Show>
9398
<Show when={counts().running > 0}>
94-
<span class="text-xs font-medium text-warning" title={`${counts().running} running`} aria-label={`${counts().running} running`}>
95-
{counts().running}
96-
</span>
99+
<Tooltip content={`${counts().running} running`} focusable>
100+
<span class="text-xs font-medium text-warning" aria-label={`${counts().running} running`}>
101+
{counts().running}
102+
</span>
103+
</Tooltip>
97104
</Show>
98105
</div>
99106
</div>
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ExternalLinkIcon from "./ExternalLinkIcon";
2+
import { Tooltip } from "./Tooltip";
23

34
const sectionLabels = {
45
issues: "issues",
@@ -13,15 +14,16 @@ export default function RepoGitHubLink(props: {
1314
const label = () => sectionLabels[props.section];
1415

1516
return (
16-
<a
17-
href={`https://github.com/${props.repoFullName}/${props.section}`}
18-
target="_blank"
19-
rel="noopener noreferrer"
20-
class="opacity-0 group-hover/repo-header:opacity-100 focus-visible:opacity-100 transition-opacity text-base-content/40 hover:text-primary px-1"
21-
title={`Open ${props.repoFullName} ${label()} on GitHub`}
22-
aria-label={`Open ${props.repoFullName} ${label()} on GitHub`}
23-
>
24-
<ExternalLinkIcon />
25-
</a>
17+
<Tooltip content={`Open ${props.repoFullName} ${label()} on GitHub`}>
18+
<a
19+
href={`https://github.com/${props.repoFullName}/${props.section}`}
20+
target="_blank"
21+
rel="noopener noreferrer"
22+
class="opacity-0 group-hover/repo-header:opacity-100 focus-visible:opacity-100 transition-opacity text-base-content/40 hover:text-primary px-1"
23+
aria-label={`Open ${props.repoFullName} ${label()} on GitHub`}
24+
>
25+
<ExternalLinkIcon />
26+
</a>
27+
</Tooltip>
2628
);
2729
}

src/app/components/shared/Tooltip.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface TooltipProps {
1010
content: string;
1111
placement?: "top" | "bottom" | "left" | "right";
1212
focusable?: boolean;
13+
class?: string;
1314
children: JSX.Element;
1415
}
1516

@@ -42,7 +43,7 @@ export function Tooltip(props: TooltipProps) {
4243
>
4344
<KobalteTooltip.Trigger
4445
as="span"
45-
class="inline-flex items-center"
46+
class={`inline-flex items-center${props.class ? ` ${props.class}` : ""}`}
4647
tabindex={props.focusable ? "0" : undefined}
4748
onPointerEnter={() => {
4849
clearTimeout(hoverTimer);

tests/components/ItemRow.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ describe("ItemRow", () => {
104104
it("ignore button has relative z-10 to sit above overlay link", () => {
105105
render(() => <ItemRow {...defaultProps} />);
106106
const ignoreBtn = screen.getByLabelText(/Ignore #42/i);
107-
expect(ignoreBtn.className).toContain("relative");
108-
expect(ignoreBtn.className).toContain("z-10");
107+
// The Tooltip wrapper span carries relative z-10; the button itself is inside it
108+
const tooltipTrigger = ignoreBtn.closest("span.relative");
109+
expect(tooltipTrigger).not.toBeNull();
110+
expect(tooltipTrigger!.className).toContain("z-10");
109111
});
110112

111113
it("applies compact padding in compact density", () => {
@@ -180,9 +182,7 @@ describe("ItemRow", () => {
180182
const created = container.querySelector(`time[datetime="${defaultProps.createdAt}"]`);
181183
const updated = container.querySelector(`time[datetime="${defaultProps.updatedAt}"]`);
182184
expect(created!.textContent).toBe("2h");
183-
expect(created!.getAttribute("title")).toBe(`Created: ${new Date(defaultProps.createdAt).toLocaleString()}`);
184185
expect(updated!.textContent).toBe("30m");
185-
expect(updated!.getAttribute("title")).toBe(`Updated: ${new Date(defaultProps.updatedAt).toLocaleString()}`);
186186
// Middle dot separator is a <span> with aria-hidden
187187
const dot = container.querySelector('span[aria-hidden="true"]');
188188
expect(dot).not.toBeNull();

tests/components/WorkflowRunRow.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ describe("WorkflowRunRow", () => {
3636
const timeEl = container.querySelector("time");
3737
expect(timeEl).not.toBeNull();
3838
expect(timeEl!.getAttribute("datetime")).toBe(createdAt);
39-
expect(timeEl!.getAttribute("title")).toBe(`Created: ${new Date(createdAt).toLocaleString()}`);
4039
expect(timeEl!.textContent).toMatch(/2 hours? ago/);
4140
});
4241

0 commit comments

Comments
 (0)