authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 12:22:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 12:22:20-07:00
log1c2f933892cce833823fcfbf3099a51813ba67fb
tree77e27d717a03a699ab29e6fef41558c626deee02
parent4321aa24fd21c2b5360cc58e84de4ccc09c801f9
signature Signed by PGP key B5690EEEBB952194

refactor(web): render menu and popover triggers through Button (#13042)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

9 files changed, 20 insertions(+), 14 deletions(-)

apps/web/src/components/DiffPanel.tsx+2-1
...@@ -672,7 +672,8 @@ export default function DiffPanel({...@@ -672,7 +672,8 @@ export default function DiffPanel({
672 <div className="flex min-w-0 flex-1 items-center gap-3 [-webkit-app-region:no-drag]">672 <div className="flex min-w-0 flex-1 items-center gap-3 [-webkit-app-region:no-drag]">
673 <DropdownMenu>673 <DropdownMenu>
674 <DropdownMenuTrigger674 <DropdownMenuTrigger
675 className="inline-flex h-6 max-w-full items-center gap-1 rounded-md bg-accent px-2 text-xs font-medium text-accent-foreground outline-none transition-colors hover:bg-accent/80 focus-visible:ring-2 focus-visible:ring-ring"675 render={<Button size="xs" variant="secondary" />}
676 className="max-w-full"
676 aria-label={`Diff scope: ${selectedScopeLabel}`}677 aria-label={`Diff scope: ${selectedScopeLabel}`}
677 >678 >
678 <span className="truncate">{selectedScopeLabel}</span>679 <span className="truncate">{selectedScopeLabel}</span>
apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx+3-1
...@@ -21,6 +21,7 @@ import {...@@ -21,6 +21,7 @@ import {
21 ServerUpdateProgress,21 ServerUpdateProgress,
22 ServerUpdatesAction,22 ServerUpdatesAction,
23} from "../ServerUpdateAction";23} from "../ServerUpdateAction";
24import { InlineButton } from "../ui/button";
24import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";25import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
25import type { ComposerBannerStackItem } from "./ComposerBannerStack";26import type { ComposerBannerStackItem } from "./ComposerBannerStack";
26import { ComposerServerUpdateIcon } from "./ComposerServerUpdateStatus";27import { ComposerServerUpdateIcon } from "./ComposerServerUpdateStatus";
...@@ -95,7 +96,8 @@ export function useAutoBalanceUpdateBanner(...@@ -95,7 +96,8 @@ export function useAutoBalanceUpdateBanner(
95 title: (96 title: (
96 <Popover>97 <Popover>
97 <PopoverTrigger98 <PopoverTrigger
98 className="block max-w-full truncate rounded-sm text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"99 render={<InlineButton />}
100 className="block max-w-full truncate"
99 aria-label={`${title}. View machines`}101 aria-label={`${title}. View machines`}
100 >102 >
101 {title}103 {title}
apps/web/src/components/device/DeviceToolVersions.tsx+2-1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import type { ReactNode } from "react";1import type { ReactNode } from "react";
2import type { DeviceToolVersions as ToolVersions } from "@t3tools/contracts";2import type { DeviceToolVersions as ToolVersions } from "@t3tools/contracts";
3import { InlineButton } from "~/components/ui/button";
3import { Popover, PopoverPopup, PopoverTitle, PopoverTrigger } from "~/components/ui/popover";4import { Popover, PopoverPopup, PopoverTitle, PopoverTrigger } from "~/components/ui/popover";
45
5export function DeviceToolVersions({6export function DeviceToolVersions({
...@@ -32,7 +33,7 @@ export function DeviceToolVersions({...@@ -32,7 +33,7 @@ export function DeviceToolVersions({
32 ? `${label}: ${version ? `version ${version}` : selected ? "not installed" : "version unknown"}. Show details`33 ? `${label}: ${version ? `version ${version}` : selected ? "not installed" : "version unknown"}. Show details`
33 : undefined34 : undefined
34 }35 }
35 className="rounded text-xs text-muted-foreground underline decoration-muted-foreground/40 underline-offset-4 hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring"36 render={<InlineButton tone="muted" />}
36 >37 >
37 {kind38 {kind
38 ? version39 ? version
apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+2-1
...@@ -1071,7 +1071,8 @@ function PullRequestCodeTab({...@@ -1071,7 +1071,8 @@ function PullRequestCodeTab({
1071 {orderedCommits.length > 0 ? (1071 {orderedCommits.length > 0 ? (
1072 <DropdownMenu>1072 <DropdownMenu>
1073 <DropdownMenuTrigger1073 <DropdownMenuTrigger
1074 className="inline-flex h-6 min-w-0 max-w-64 items-center gap-1 rounded-md bg-accent px-2 text-xs font-medium text-accent-foreground outline-none transition-colors hover:bg-accent/80 focus-visible:ring-2 focus-visible:ring-ring"1074 render={<Button size="xs" variant="secondary" />}
1075 className="min-w-0 max-w-64"
1075 aria-label={`Diff scope: ${scopeLabel}`}1076 aria-label={`Diff scope: ${scopeLabel}`}
1076 >1077 >
1077 <span className="truncate">{scopeLabel}</span>1078 <span className="truncate">{scopeLabel}</span>
apps/web/src/components/settings/SettingsBreadcrumb.tsx+3-5
...@@ -2,11 +2,11 @@ import { resolveEnvironmentMachineKind } from "@t3tools/contracts";...@@ -2,11 +2,11 @@ import { resolveEnvironmentMachineKind } from "@t3tools/contracts";
2import { LayersIcon } from "lucide-react";2import { LayersIcon } from "lucide-react";
3import type { ReactNode } from "react";3import type { ReactNode } from "react";
44
5import { cn } from "../../lib/utils";
6import type { SidebarProjectSnapshot } from "../../sidebarProjectGrouping";5import type { SidebarProjectSnapshot } from "../../sidebarProjectGrouping";
7import type { EnvironmentPresentation } from "../../state/environments";6import type { EnvironmentPresentation } from "../../state/environments";
8import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";7import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";
9import { ProjectFavicon } from "../ProjectFavicon";8import { ProjectFavicon } from "../ProjectFavicon";
9import { InlineButton } from "../ui/button";
10import {10import {
11 Menu,11 Menu,
12 MenuPopup,12 MenuPopup,
...@@ -111,10 +111,8 @@ function ScopeMenu({...@@ -111,10 +111,8 @@ function ScopeMenu({
111 <Menu>111 <Menu>
112 <MenuTrigger112 <MenuTrigger
113 aria-label={ariaLabel}113 aria-label={ariaLabel}
114 className={cn(114 render={<InlineButton tone={narrowed ? "default" : "muted"} />}
115 "inline-flex min-w-0 max-w-56 cursor-pointer items-center gap-1.5 rounded-sm text-left transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",115 className="min-w-0 max-w-56 gap-1.5"
116 narrowed ? "text-foreground" : "text-muted-foreground hover:text-foreground",
117 )}
118 >116 >
119 {icon}117 {icon}
120 <span className="min-w-0 truncate">{label}</span>118 <span className="min-w-0 truncate">{label}</span>
apps/web/src/components/usage/UsagePage.refresh.test.tsx+1-1
...@@ -47,7 +47,7 @@ vi.mock("./usagePagePreferences", () => ({...@@ -47,7 +47,7 @@ vi.mock("./usagePagePreferences", () => ({
47 readUsagePagePreferences: () => ({ metric: state.metric, windowDays: 30 }),47 readUsagePagePreferences: () => ({ metric: state.metric, windowDays: 30 }),
48 saveUsagePagePreferences: vi.fn(),48 saveUsagePagePreferences: vi.fn(),
49}));49}));
50vi.mock("../ui/button", () => ({ Button: "button" }));50vi.mock("../ui/button", () => ({ Button: "button", InlineButton: "button" }));
51vi.mock("../ui/scroll-area", () => ({ ScrollArea: "div" }));51vi.mock("../ui/scroll-area", () => ({ ScrollArea: "div" }));
52vi.mock("../ui/select", () => ({52vi.mock("../ui/select", () => ({
53 Select: "select",53 Select: "select",
apps/web/src/components/usage/UsagePage.test.tsx+1-1
...@@ -40,7 +40,7 @@ vi.mock("react", async (importOriginal) => {...@@ -40,7 +40,7 @@ vi.mock("react", async (importOriginal) => {
4040
41vi.mock("../../env", () => ({ isElectron: false }));41vi.mock("../../env", () => ({ isElectron: false }));
42vi.mock("../../state/usage", () => ({ useUsage: testState.useUsage }));42vi.mock("../../state/usage", () => ({ useUsage: testState.useUsage }));
43vi.mock("../ui/button", () => ({ Button: "button" }));43vi.mock("../ui/button", () => ({ Button: "button", InlineButton: "button" }));
44vi.mock("../ui/scroll-area", () => ({ ScrollArea: "div" }));44vi.mock("../ui/scroll-area", () => ({ ScrollArea: "div" }));
45vi.mock("../ui/select", () => ({45vi.mock("../ui/select", () => ({
46 Select: "div",46 Select: "div",
apps/web/src/components/usage/UsagePage.tsx+5-2
...@@ -39,7 +39,7 @@ import {...@@ -39,7 +39,7 @@ import {
39 formatUsd,39 formatUsd,
40 makeWindow,40 makeWindow,
41} from "@t3tools/shared/usageFormat";41} from "@t3tools/shared/usageFormat";
42import { Button } from "../ui/button";42import { Button, InlineButton } from "../ui/button";
43import {43import {
44 Menu,44 Menu,
45 MenuCheckboxItem,45 MenuCheckboxItem,
...@@ -737,7 +737,10 @@ function UsageEnvironmentFilter({...@@ -737,7 +737,10 @@ function UsageEnvironmentFilter({
737 return (737 return (
738 <>738 <>
739 <Menu>739 <Menu>
740 <MenuTrigger className="group/usage-environment inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">740 <MenuTrigger
741 render={<InlineButton />}
742 className="group/usage-environment min-w-0 max-w-full gap-1"
743 >
741 <span className="min-w-0 truncate">{label}</span>744 <span className="min-w-0 truncate">{label}</span>
742 <span className="flex size-3.5 shrink-0 items-center justify-center text-muted-foreground">745 <span className="flex size-3.5 shrink-0 items-center justify-center text-muted-foreground">
743 {showUsageStatus && pendingCount > 0 ? (746 {showUsageStatus && pendingCount > 0 ? (
scripts/lint-restyle-ceiling.ts+1-1
...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
10// migrate a file, and delete this script when the rule becomes an error.10// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 717;11export const RESTYLE_CEILING = 668;
1212
13const RULE = "shadcn(no-restyle)";13const RULE = "shadcn(no-restyle)";
1414