authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 12:22:16-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 12:22:16-07:00
logd7736e06b6a4f8211f452908963fd23c4a04b2c4
tree3980323f0e24ea3231c583211a83541213722374
parent4c9fbf1f647c83179af1c2acd78eded04ef07fe8
signature Signed by PGP key B5690EEEBB952194

refactor(web): one minimum width for menus, three widths for popovers (#13035)

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

43 files changed, 69 insertions(+), 98 deletions(-)

apps/web/src/browser/BrowserDeviceToolbar.tsx+1-1
...@@ -197,7 +197,7 @@ export function BrowserDeviceToolbar({...@@ -197,7 +197,7 @@ export function BrowserDeviceToolbar({
197 >197 >
198 <SelectValue />198 <SelectValue />
199 </SelectTrigger>199 </SelectTrigger>
200 <SelectPopup align="start" alignItemWithTrigger={false} className="min-w-64">200 <SelectPopup align="start" alignItemWithTrigger={false}>
201 <SelectItem value={RESPONSIVE_VALUE}>Responsive</SelectItem>201 <SelectItem value={RESPONSIVE_VALUE}>Responsive</SelectItem>
202 <SelectGroup>202 <SelectGroup>
203 <SelectGroupLabel>Standard</SelectGroupLabel>203 <SelectGroupLabel>Standard</SelectGroupLabel>
apps/web/src/components/BranchToolbar.tsx+1-1
...@@ -217,7 +217,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({...@@ -217,7 +217,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
217 {triggerContent}217 {triggerContent}
218 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />218 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />
219 </MenuTrigger>219 </MenuTrigger>
220 <MenuPopup align="start" side="top" className="w-64" {...composerFloatingLayerProps}>220 <MenuPopup align="start" side="top" {...composerFloatingLayerProps}>
221 {showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (221 {showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (
222 <>222 <>
223 <MenuGroup>223 <MenuGroup>
apps/web/src/components/DiffPanel.tsx+2-2
...@@ -678,7 +678,7 @@ export default function DiffPanel({...@@ -678,7 +678,7 @@ export default function DiffPanel({
678 <span className="truncate">{selectedScopeLabel}</span>678 <span className="truncate">{selectedScopeLabel}</span>
679 <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" />679 <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" />
680 </DropdownMenuTrigger>680 </DropdownMenuTrigger>
681 <DropdownMenuContent align="start" className="w-60">681 <DropdownMenuContent align="start">
682 <DropdownMenuRadioGroup value={selectedScopeValue} onValueChange={selectScopeValue}>682 <DropdownMenuRadioGroup value={selectedScopeValue} onValueChange={selectScopeValue}>
683 <DropdownMenuRadioItem value="unstaged" closeOnClick>683 <DropdownMenuRadioItem value="unstaged" closeOnClick>
684 <span>Working tree</span>684 <span>Working tree</span>
...@@ -692,7 +692,7 @@ export default function DiffPanel({...@@ -692,7 +692,7 @@ export default function DiffPanel({
692 </DropdownMenuRadioGroup>692 </DropdownMenuRadioGroup>
693 <DropdownMenuSub>693 <DropdownMenuSub>
694 <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger>694 <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger>
695 <DropdownMenuSubContent className="w-64">695 <DropdownMenuSubContent>
696 <DropdownMenuRadioGroup value={selectedTurnValue} onValueChange={selectScopeValue}>696 <DropdownMenuRadioGroup value={selectedTurnValue} onValueChange={selectScopeValue}>
697 {orderedTurnDiffSummaries.map((summary) => {697 {orderedTurnDiffSummaries.map((summary) => {
698 const turnCount =698 const turnCount =
apps/web/src/components/GitActionsControl.tsx+2-4
...@@ -1793,7 +1793,7 @@ export default function GitActionsControl({...@@ -1793,7 +1793,7 @@ export default function GitActionsControl({
1793 <SourceControlIcon className="size-4" />1793 <SourceControlIcon className="size-4" />
1794 <MenuItemLabel>Git actions</MenuItemLabel>1794 <MenuItemLabel>Git actions</MenuItemLabel>
1795 </MenuSubTrigger>1795 </MenuSubTrigger>
1796 <MenuSubPopup className="min-w-32 max-w-[calc(100vw-2rem)]">{gitItems}</MenuSubPopup>1796 <MenuSubPopup>{gitItems}</MenuSubPopup>
1797 </MenuSub>1797 </MenuSub>
1798 </>1798 </>
1799 )1799 )
...@@ -1859,9 +1859,7 @@ export default function GitActionsControl({...@@ -1859,9 +1859,7 @@ export default function GitActionsControl({
1859 >1859 >
1860 <ChevronDownIcon aria-hidden="true" className="size-4" />1860 <ChevronDownIcon aria-hidden="true" className="size-4" />
1861 </MenuTrigger>1861 </MenuTrigger>
1862 <MenuPopup align="end" className="w-full">1862 <MenuPopup align="end">{gitItems}</MenuPopup>
1863 {gitItems}
1864 </MenuPopup>
1865 </Menu>1863 </Menu>
1866 </Group>1864 </Group>
1867 )}1865 )}
apps/web/src/components/LegacySidebar.tsx+1-1
...@@ -2747,7 +2747,7 @@ function ProjectSortMenu({...@@ -2747,7 +2747,7 @@ function ProjectSortMenu({
2747 </TooltipTrigger>2747 </TooltipTrigger>
2748 <TooltipPopup side="right">Sidebar options</TooltipPopup>2748 <TooltipPopup side="right">Sidebar options</TooltipPopup>
2749 </Tooltip>2749 </Tooltip>
2750 <MenuPopup align="end" side="bottom" className="min-w-52">2750 <MenuPopup align="end" side="bottom">
2751 <MenuGroup>2751 <MenuGroup>
2752 <div className="px-2 py-1 sm:text-xs font-medium text-muted-foreground">2752 <div className="px-2 py-1 sm:text-xs font-medium text-muted-foreground">
2753 Sort projects2753 Sort projects
apps/web/src/components/ProjectScriptsControl.tsx+1-3
...@@ -258,9 +258,7 @@ export default function ProjectScriptsControl({...@@ -258,9 +258,7 @@ export default function ProjectScriptsControl({
258 <ScriptIcon icon="play" className="size-4" />258 <ScriptIcon icon="play" className="size-4" />
259 <MenuItemLabel>Project actions</MenuItemLabel>259 <MenuItemLabel>Project actions</MenuItemLabel>
260 </MenuSubTrigger>260 </MenuSubTrigger>
261 <MenuSubPopup className="min-w-32 max-w-[calc(100vw-2rem)]">261 <MenuSubPopup>{scriptItems}</MenuSubPopup>
262 {scriptItems}
263 </MenuSubPopup>
264 </MenuSub>262 </MenuSub>
265 ) : (263 ) : (
266 <MenuItem264 <MenuItem
apps/web/src/components/RightPanelTabs.tsx+2-8
...@@ -567,12 +567,7 @@ function RightPanelEmptyState(props: {...@@ -567,12 +567,7 @@ function RightPanelEmptyState(props: {
567 >567 >
568 <ChevronDown className="size-3.5" />568 <ChevronDown className="size-3.5" />
569 </MenuTrigger>569 </MenuTrigger>
570 <MenuPopup570 <MenuPopup align="end" side="bottom" sideOffset={6} className="max-w-56">
571 align="end"
572 side="bottom"
573 sideOffset={6}
574 className="min-w-40 max-w-56"
575 >
576 {props.browserProfiles.map((profile) => (571 {props.browserProfiles.map((profile) => (
577 <MenuItem572 <MenuItem
578 key={profile.id}573 key={profile.id}
...@@ -1273,7 +1268,6 @@ export function RightPanelTabs(props: RightPanelTabsProps) {...@@ -1273,7 +1268,6 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
1273 align="start"1268 align="start"
1274 side="bottom"1269 side="bottom"
1275 sideOffset={6}1270 sideOffset={6}
1276 className="min-w-44"
1277 onKeyDownCapture={handleAddSurfaceMenuKeyDown}1271 onKeyDownCapture={handleAddSurfaceMenuKeyDown}
1278 >1272 >
1279 {addSurfaceActions.map((action) => {1273 {addSurfaceActions.map((action) => {
...@@ -1314,7 +1308,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {...@@ -1314,7 +1308,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
1314 and run to 48 characters, which would otherwise widen1308 and run to 48 characters, which would otherwise widen
1315 the popup to fit-content and wrap.1309 the popup to fit-content and wrap.
1316 */}1310 */}
1317 <MenuSubPopup className="min-w-40 max-w-56">1311 <MenuSubPopup className="max-w-56">
1318 {browserProfiles.map((profile) => (1312 {browserProfiles.map((profile) => (
1319 <MenuItem1313 <MenuItem
1320 key={profile.id}1314 key={profile.id}
apps/web/src/components/Sidebar.tsx+1-1
...@@ -475,7 +475,7 @@ function SnoozePopoverButton(props: {...@@ -475,7 +475,7 @@ function SnoozePopoverButton(props: {
475 </TooltipTrigger>475 </TooltipTrigger>
476 <TooltipPopup>Snooze thread</TooltipPopup>476 <TooltipPopup>Snooze thread</TooltipPopup>
477 </Tooltip>477 </Tooltip>
478 <PopoverPopup side="bottom" align="end" className="w-56" viewportClassName="p-1">478 <PopoverPopup side="bottom" align="end" width="sm" viewportClassName="p-1">
479 {presets.map((preset) => (479 {presets.map((preset) => (
480 <button480 <button
481 key={preset.id}481 key={preset.id}
apps/web/src/components/chat/AssistantCitationChip.tsx+1-1
...@@ -186,7 +186,7 @@ export function AssistantCitationChip({...@@ -186,7 +186,7 @@ export function AssistantCitationChip({
186 return false;186 return false;
187 }}187 }}
188 aria-label="Edit citation comment"188 aria-label="Edit citation comment"
189 className="w-72 max-w-[calc(100vw-1rem)]"189 width="md"
190 viewportClassName="p-3"190 viewportClassName="p-3"
191 onPointerDown={(event) => event.stopPropagation()}191 onPointerDown={(event) => event.stopPropagation()}
192 >192 >
apps/web/src/components/chat/ChatHeader.tsx-1
...@@ -516,7 +516,6 @@ export const ChatHeader = memo(function ChatHeader({...@@ -516,7 +516,6 @@ export const ChatHeader = memo(function ChatHeader({
516 keepMounted516 keepMounted
517 aria-label="Header actions"517 aria-label="Header actions"
518 align="end"518 align="end"
519 className="min-w-56 max-w-[calc(100vw-2rem)]"
520 finalFocus={actionsCollapsed ? undefined : false}519 finalFocus={actionsCollapsed ? undefined : false}
521 >520 >
522 <div ref={mountMenuActions} className="contents" />521 <div ref={mountMenuActions} className="contents" />
apps/web/src/components/chat/ComposerPendingApprovalActions.tsx+1-6
...@@ -73,12 +73,7 @@ export const ComposerPendingApprovalActions = memo(function ComposerPendingAppro...@@ -73,12 +73,7 @@ export const ComposerPendingApprovalActions = memo(function ComposerPendingAppro
73 >73 >
74 <EllipsisIcon />74 <EllipsisIcon />
75 </MenuTrigger>75 </MenuTrigger>
76 <MenuPopup76 <MenuPopup {...composerFloatingLayerProps} side="top" align="end">
77 {...composerFloatingLayerProps}
78 side="top"
79 align="end"
80 className="w-56 max-w-[calc(100vw-2rem)]"
81 >
82 {moreOptions.map((option) => {77 {moreOptions.map((option) => {
83 const item = (78 const item = (
84 <MenuItem79 <MenuItem
apps/web/src/components/chat/ContextWindowMeter.tsx+2-1
...@@ -89,7 +89,8 @@ export function ContextWindowMeter(props: {...@@ -89,7 +89,8 @@ export function ContextWindowMeter(props: {
89 side="top"89 side="top"
90 align="end"90 align="end"
91 viewportClassName="p-0"91 viewportClassName="p-0"
92 className="w-64 max-w-none text-left whitespace-normal"92 width="sm"
93 className="text-left whitespace-normal"
93 >94 >
94 <div className="flex flex-col gap-2 p-[var(--floating-content-inset)]">95 <div className="flex flex-col gap-2 p-[var(--floating-content-inset)]">
95 <div className="flex items-center justify-between gap-3">96 <div className="flex items-center justify-between gap-3">
apps/web/src/components/chat/DraftHeroHeadline.tsx+1-1
...@@ -149,7 +149,7 @@ export function DraftHeroHeadline({...@@ -149,7 +149,7 @@ export function DraftHeroHeadline({
149 <TooltipPopup side="top">{activeProjectDisplayName}</TooltipPopup>149 <TooltipPopup side="top">{activeProjectDisplayName}</TooltipPopup>
150 ) : null}150 ) : null}
151 </Tooltip>151 </Tooltip>
152 <MenuPopup align="center" className="max-h-80 min-w-40! w-max max-w-64 overflow-y-auto">152 <MenuPopup align="center" className="max-h-80 overflow-y-auto">
153 <MenuRadioGroup153 <MenuRadioGroup
154 value={activeProjectKey}154 value={activeProjectKey}
155 onValueChange={(value) => {155 onValueChange={(value) => {
apps/web/src/components/chat/MessagesTimeline.tsx+2-1
...@@ -2571,7 +2571,8 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap...@@ -2571,7 +2571,8 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap
2571 <PopoverPopup2571 <PopoverPopup
2572 side="bottom"2572 side="bottom"
2573 align="end"2573 align="end"
2574 className="surface-glass! w-[28rem] max-w-[calc(100vw-2rem)]"2574 width="lg"
2575 className="surface-glass!"
2575 viewportClassName="py-3 [--viewport-inline-padding:--spacing(3)]"2576 viewportClassName="py-3 [--viewport-inline-padding:--spacing(3)]"
2576 >2577 >
2577 <WorktreeSetupCard2578 <WorktreeSetupCard
apps/web/src/components/chat/OpenInPicker.tsx+1-1
...@@ -343,7 +343,7 @@ export const OpenInPicker = memo(function OpenInPicker({...@@ -343,7 +343,7 @@ export const OpenInPicker = memo(function OpenInPicker({
343 <SquareArrowOutUpRightIcon className="size-4" />343 <SquareArrowOutUpRightIcon className="size-4" />
344 <MenuItemLabel>Open in…</MenuItemLabel>344 <MenuItemLabel>Open in…</MenuItemLabel>
345 </MenuSubTrigger>345 </MenuSubTrigger>
346 <MenuSubPopup className="min-w-32 max-w-[calc(100vw-2rem)]">{editorItems}</MenuSubPopup>346 <MenuSubPopup>{editorItems}</MenuSubPopup>
347 </MenuSub>347 </MenuSub>
348 </>348 </>
349 );349 );
apps/web/src/components/chat/SnapShotAttachmentDetails.tsx+1-1
...@@ -166,7 +166,7 @@ export function SnapShotContentsButton({...@@ -166,7 +166,7 @@ export function SnapShotContentsButton({
166 <PopoverPopup166 <PopoverPopup
167 side={side}167 side={side}
168 align="center"168 align="center"
169 className="w-[min(24rem,calc(100vw-2rem))]"169 width="md"
170 viewportClassName="max-h-[min(28rem,70vh)]"170 viewportClassName="max-h-[min(28rem,70vh)]"
171 >171 >
172 <div className="space-y-2">172 <div className="space-y-2">
apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx+1-1
...@@ -100,7 +100,7 @@ export function useAutoBalanceUpdateBanner(...@@ -100,7 +100,7 @@ export function useAutoBalanceUpdateBanner(
100 >100 >
101 {title}101 {title}
102 </PopoverTrigger>102 </PopoverTrigger>
103 <PopoverPopup side="top" align="start" className="w-80 max-w-[calc(100vw-2rem)]">103 <PopoverPopup side="top" align="start" width="md">
104 <div className="space-y-3 text-xs">104 <div className="space-y-3 text-xs">
105 {machines.map((machine) => (105 {machines.map((machine) => (
106 <div key={machine.environmentId} className="space-y-1">106 <div key={machine.environmentId} className="space-y-1">
apps/web/src/components/contextChipParts.tsx+2-1
...@@ -102,7 +102,8 @@ export function ContextChipPopover(props: {...@@ -102,7 +102,8 @@ export function ContextChipPopover(props: {
102 </PopoverTrigger>102 </PopoverTrigger>
103 <PopoverPopup103 <PopoverPopup
104 side="top"104 side="top"
105 className={cn("w-[min(36rem,calc(100vw-2rem))]", props.popupClassName)}105 width="lg"
106 className={props.popupClassName}
106 viewportClassName={cn("overflow-x-auto p-2", props.viewportClassName)}107 viewportClassName={cn("overflow-x-auto p-2", props.viewportClassName)}
107 >108 >
108 <PopoverTitle className="sr-only">{props.accessibleLabel}</PopoverTitle>109 <PopoverTitle className="sr-only">{props.accessibleLabel}</PopoverTitle>
apps/web/src/components/device/DeviceToolVersions.tsx+1-1
...@@ -44,7 +44,7 @@ export function DeviceToolVersions({...@@ -44,7 +44,7 @@ export function DeviceToolVersions({
44 ? "Versions unavailable"44 ? "Versions unavailable"
45 : "Versions"}45 : "Versions"}
46 </PopoverTrigger>46 </PopoverTrigger>
47 <PopoverPopup align="end" className="w-80">47 <PopoverPopup align="end" width="md">
48 <PopoverTitle>{kind ? label : "Device tools"}</PopoverTitle>48 <PopoverTitle>{kind ? label : "Device tools"}</PopoverTitle>
49 {tools ? (49 {tools ? (
50 <div className="mt-4 divide-y divide-border/50">50 <div className="mt-4 divide-y divide-border/50">
apps/web/src/components/files/FileBreadcrumbs.tsx-1
...@@ -104,7 +104,6 @@ function BreadcrumbMenuContent(props: {...@@ -104,7 +104,6 @@ function BreadcrumbMenuContent(props: {
104 <MenuPopup104 <MenuPopup
105 align="start"105 align="start"
106 side="bottom"106 side="bottom"
107 className="w-max min-w-32 max-w-[min(19rem,var(--available-width))]"
108 onKeyDown={(event) => {107 onKeyDown={(event) => {
109 if (event.key !== "ArrowLeft" || !canGoBack || parentPath === null) return;108 if (event.key !== "ArrowLeft" || !canGoBack || parentPath === null) return;
110 event.preventDefault();109 event.preventDefault();
apps/web/src/components/preview/PreviewMoreMenu.tsx+2-2
...@@ -108,7 +108,7 @@ export function PreviewMoreMenu({...@@ -108,7 +108,7 @@ export function PreviewMoreMenu({
108 </TooltipTrigger>108 </TooltipTrigger>
109 <TooltipPopup>More</TooltipPopup>109 <TooltipPopup>More</TooltipPopup>
110 </Tooltip>110 </Tooltip>
111 <MenuPopup align="end" sideOffset={6} className="min-w-56">111 <MenuPopup align="end" sideOffset={6}>
112 <MenuItem onClick={callTab(bridge.hardReload)} disabled={tabDisabled}>112 <MenuItem onClick={callTab(bridge.hardReload)} disabled={tabDisabled}>
113 Hard reload113 Hard reload
114 </MenuItem>114 </MenuItem>
...@@ -125,7 +125,7 @@ export function PreviewMoreMenu({...@@ -125,7 +125,7 @@ export function PreviewMoreMenu({
125 </MenuItem>125 </MenuItem>
126 <MenuSub>126 <MenuSub>
127 <MenuSubTrigger disabled={tabDisabled}>Appearance</MenuSubTrigger>127 <MenuSubTrigger disabled={tabDisabled}>Appearance</MenuSubTrigger>
128 <MenuSubPopup className="min-w-32">128 <MenuSubPopup>
129 <MenuRadioGroup129 <MenuRadioGroup
130 value={colorScheme}130 value={colorScheme}
131 onValueChange={(value) => {131 onValueChange={(value) => {
apps/web/src/components/pullRequest/PullRequestChecksPopover.tsx+1-1
...@@ -147,7 +147,7 @@ export function PullRequestChecksPopover({...@@ -147,7 +147,7 @@ export function PullRequestChecksPopover({
147 >147 >
148 <presentation.Icon aria-hidden className={cn("size-3.5", presentation.toneClassName)} />148 <presentation.Icon aria-hidden className={cn("size-3.5", presentation.toneClassName)} />
149 </PopoverTrigger>149 </PopoverTrigger>
150 <PopoverPopup align="start" className="w-80 max-w-full" side="bottom">150 <PopoverPopup align="start" width="md" side="bottom">
151 <p className="mb-2 font-medium text-sm">{presentation.label}</p>151 <p className="mb-2 font-medium text-sm">{presentation.label}</p>
152 {summary === null ? null : <p className="mb-2 text-muted-foreground text-xs">{summary}</p>}152 {summary === null ? null : <p className="mb-2 text-muted-foreground text-xs">{summary}</p>}
153 {stale ? (153 {stale ? (
apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+1-1
...@@ -1077,7 +1077,7 @@ function PullRequestCodeTab({...@@ -1077,7 +1077,7 @@ function PullRequestCodeTab({
1077 <span className="truncate">{scopeLabel}</span>1077 <span className="truncate">{scopeLabel}</span>
1078 <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" />1078 <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" />
1079 </DropdownMenuTrigger>1079 </DropdownMenuTrigger>
1080 <DropdownMenuContent align="start" className="w-80">1080 <DropdownMenuContent align="start">
1081 <DropdownMenuRadioGroup1081 <DropdownMenuRadioGroup
1082 value={commit ?? "all"}1082 value={commit ?? "all"}
1083 onValueChange={(value) => onSelectedCommitChange(value === "all" ? null : value)}1083 onValueChange={(value) => onSelectedCommitChange(value === "all" ? null : value)}
apps/web/src/components/pullRequest/PullRequestComposer.tsx+1-1
...@@ -110,7 +110,7 @@ export function PullRequestComposer({...@@ -110,7 +110,7 @@ export function PullRequestComposer({
110 side="top"110 side="top"
111 align="end"111 align="end"
112 sideOffset={8}112 sideOffset={8}
113 className="w-96 max-w-[calc(100vw-2rem)]"113 width="lg"
114 initialFocus={mode === "review" ? reviewRef : commentRef}114 initialFocus={mode === "review" ? reviewRef : commentRef}
115 aria-label="Pull request composer"115 aria-label="Pull request composer"
116 >116 >
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+2-2
...@@ -1569,7 +1569,7 @@ export function PullRequestDetailPanel({...@@ -1569,7 +1569,7 @@ export function PullRequestDetailPanel({
1569 />1569 />
1570 <TooltipPopup>Check out this pull request</TooltipPopup>1570 <TooltipPopup>Check out this pull request</TooltipPopup>
1571 </Tooltip>1571 </Tooltip>
1572 <MenuPopup align="end" side="bottom" className="min-w-72">1572 <MenuPopup align="end" side="bottom">
1573 <MenuItem onClick={() => startCheckout("worktree")}>1573 <MenuItem onClick={() => startCheckout("worktree")}>
1574 <GitBranchIcon className="mt-1 size-3.5 shrink-0 self-start" />1574 <GitBranchIcon className="mt-1 size-3.5 shrink-0 self-start" />
1575 <span className="flex min-w-0 flex-col">1575 <span className="flex min-w-0 flex-col">
...@@ -2020,7 +2020,7 @@ export function PullRequestDetailPanel({...@@ -2020,7 +2020,7 @@ export function PullRequestDetailPanel({
2020 {refreshing ? "Refreshing pull request" : "More pull request actions"}2020 {refreshing ? "Refreshing pull request" : "More pull request actions"}
2021 </TooltipPopup>2021 </TooltipPopup>
2022 </Tooltip>2022 </Tooltip>
2023 <MenuPopup align="end" side="bottom" className="min-w-72">2023 <MenuPopup align="end" side="bottom">
2024 <PullRequestThreadLinks2024 <PullRequestThreadLinks
2025 display="menu-item"2025 display="menu-item"
2026 environmentId={environmentId}2026 environmentId={environmentId}
apps/web/src/components/pullRequest/PullRequestListFilters.tsx+4-4
...@@ -232,7 +232,7 @@ function PullRequestFilterRadioSubmenu<Value extends string>({...@@ -232,7 +232,7 @@ function PullRequestFilterRadioSubmenu<Value extends string>({
232 {current.label}232 {current.label}
233 </span>233 </span>
234 </MenuSubTrigger>234 </MenuSubTrigger>
235 <MenuSubPopup className="min-w-56">235 <MenuSubPopup>
236 <PullRequestFilterRadioGroup236 <PullRequestFilterRadioGroup
237 label={label}237 label={label}
238 value={value}238 value={value}
...@@ -277,7 +277,7 @@ function PullRequestAuthorFilter({...@@ -277,7 +277,7 @@ function PullRequestAuthorFilter({
277 {value ?? "Anyone"}277 {value ?? "Anyone"}
278 </span>278 </span>
279 </MenuSubTrigger>279 </MenuSubTrigger>
280 <MenuSubPopup className="w-80">280 <MenuSubPopup>
281 <div className="p-1 pb-2">281 <div className="p-1 pb-2">
282 <InputGroup>282 <InputGroup>
283 <InputGroupAddon>283 <InputGroupAddon>
...@@ -346,7 +346,7 @@ function PullRequestLabelFilter({...@@ -346,7 +346,7 @@ function PullRequestLabelFilter({
346 {value.length === 0 ? "Any" : `${value.length} selected`}346 {value.length === 0 ? "Any" : `${value.length} selected`}
347 </span>347 </span>
348 </MenuSubTrigger>348 </MenuSubTrigger>
349 <MenuSubPopup className="w-72">349 <MenuSubPopup>
350 {visible.length === 0 ? (350 {visible.length === 0 ? (
351 <MenuItem disabled>No labels in this view</MenuItem>351 <MenuItem disabled>No labels in this view</MenuItem>
352 ) : (352 ) : (
...@@ -509,7 +509,7 @@ export function PullRequestFiltersMenu({...@@ -509,7 +509,7 @@ export function PullRequestFiltersMenu({
509 </span>509 </span>
510 ) : null}510 ) : null}
511 </MenuTrigger>511 </MenuTrigger>
512 <MenuPopup align="end" side="bottom" className="w-56">512 <MenuPopup align="end" side="bottom">
513 <PullRequestFilterRadioSubmenu513 <PullRequestFilterRadioSubmenu
514 label="State"514 label="State"
515 value={state}515 value={state}
apps/web/src/components/pullRequest/PullRequestReactions.tsx+1-1
...@@ -134,7 +134,7 @@ export function PullRequestReactionBar({...@@ -134,7 +134,7 @@ export function PullRequestReactionBar({
134 >134 >
135 <SmilePlusIcon aria-hidden className="size-3.5" />135 <SmilePlusIcon aria-hidden className="size-3.5" />
136 </PopoverTrigger>136 </PopoverTrigger>
137 <PopoverPopup align="start" className="w-auto" side="top" viewportClassName="py-2">137 <PopoverPopup align="start" side="top" viewportClassName="py-2">
138 <div className="flex items-center gap-0.5">138 <div className="flex items-center gap-0.5">
139 {PULL_REQUEST_REACTION_ORDER.map((content) => {139 {PULL_REQUEST_REACTION_ORDER.map((content) => {
140 const reacted =140 const reacted =
apps/web/src/components/pullRequest/PullRequestStackMenu.tsx+1-1
...@@ -145,7 +145,7 @@ export function PullRequestStackMenu({...@@ -145,7 +145,7 @@ export function PullRequestStackMenu({
145 {notice ? ` · ${notice}` : null}145 {notice ? ` · ${notice}` : null}
146 </TooltipPopup>146 </TooltipPopup>
147 </Tooltip>147 </Tooltip>
148 <MenuPopup align="start" className="w-96 max-w-[calc(100vw-2rem)]">148 <MenuPopup align="start">
149 <MenuGroup>149 <MenuGroup>
150 <PullRequestStackHeader number={stack.number} notice={notice} stale={!!onRetry} />150 <PullRequestStackHeader number={stack.number} notice={notice} stale={!!onRetry} />
151 {onRetry ? <MenuItem onClick={onRetry}>Retry stack refresh</MenuItem> : null}151 {onRetry ? <MenuItem onClick={onRetry}>Retry stack refresh</MenuItem> : null}
apps/web/src/components/pullRequest/PullRequestStackPopover.tsx-1
...@@ -85,7 +85,6 @@ export function PullRequestStackPopover({...@@ -85,7 +85,6 @@ export function PullRequestStackPopover({
85 </Tooltip>85 </Tooltip>
86 <MenuPopup86 <MenuPopup
87 align="start"87 align="start"
88 className="w-96 max-w-[calc(100vw-2rem)]"
89 onClick={(event) => event.stopPropagation()}88 onClick={(event) => event.stopPropagation()}
90 onKeyDown={(event) => event.stopPropagation()}89 onKeyDown={(event) => event.stopPropagation()}
91 >90 >
apps/web/src/components/settings/ConnectionsSettings.tsx+2-2
...@@ -1631,7 +1631,7 @@ function SavedBackendListRow({...@@ -1631,7 +1631,7 @@ function SavedBackendListRow({
1631 >1631 >
1632 <EllipsisIcon className="size-3.5" />1632 <EllipsisIcon className="size-3.5" />
1633 </MenuTrigger>1633 </MenuTrigger>
1634 <MenuPopup align="end" className="min-w-52">1634 <MenuPopup align="end">
1635 <EnvironmentIconMenu1635 <EnvironmentIconMenu
1636 environmentId={environmentId}1636 environmentId={environmentId}
1637 serverConfig={environment.serverConfig}1637 serverConfig={environment.serverConfig}
...@@ -3295,7 +3295,7 @@ export function ConnectionsSettings() {...@@ -3295,7 +3295,7 @@ export function ConnectionsSettings() {
3295 >3295 >
3296 <EllipsisIcon className="size-3.5" />3296 <EllipsisIcon className="size-3.5" />
3297 </MenuTrigger>3297 </MenuTrigger>
3298 <MenuPopup align="end" className="min-w-52">3298 <MenuPopup align="end">
3299 <EnvironmentIconMenu3299 <EnvironmentIconMenu
3300 environmentId={primaryEnvironmentId}3300 environmentId={primaryEnvironmentId}
3301 serverConfig={primaryServerConfig}3301 serverConfig={primaryServerConfig}
apps/web/src/components/settings/EnvironmentIconPicker.tsx+1-1
...@@ -99,7 +99,7 @@ export function EnvironmentIconMenu({...@@ -99,7 +99,7 @@ export function EnvironmentIconMenu({
99 <EnvironmentMachineIcon kind={resolved} />99 <EnvironmentMachineIcon kind={resolved} />
100 Icon100 Icon
101 </MenuSubTrigger>101 </MenuSubTrigger>
102 <MenuSubPopup className="min-w-44">102 <MenuSubPopup>
103 {lock !== null ? (103 {lock !== null ? (
104 <>104 <>
105 <MenuItem disabled className="whitespace-normal text-xs">105 <MenuItem disabled className="whitespace-normal text-xs">
apps/web/src/components/settings/IntegrationsSettings.tsx+3-3
...@@ -304,7 +304,7 @@ function BrowserViewportSetting({ disabled }: { readonly disabled: boolean }) {...@@ -304,7 +304,7 @@ function BrowserViewportSetting({ disabled }: { readonly disabled: boolean }) {
304 >304 >
305 <SelectValue>{viewportSelectLabel(viewport)}</SelectValue>305 <SelectValue>{viewportSelectLabel(viewport)}</SelectValue>
306 </SelectTrigger>306 </SelectTrigger>
307 <SelectPopup align="end" alignItemWithTrigger={false} className="min-w-64">307 <SelectPopup align="end" alignItemWithTrigger={false}>
308 <SelectItem value={FILL_VALUE}>Fill panel</SelectItem>308 <SelectItem value={FILL_VALUE}>Fill panel</SelectItem>
309 <SelectItem value={RESPONSIVE_VALUE}>Responsive</SelectItem>309 <SelectItem value={RESPONSIVE_VALUE}>Responsive</SelectItem>
310 <SelectGroup>310 <SelectGroup>
...@@ -1183,7 +1183,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {...@@ -1183,7 +1183,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
1183 <PlusIcon />1183 <PlusIcon />
1184 Add profile1184 Add profile
1185 </MenuTrigger>1185 </MenuTrigger>
1186 <MenuPopup align="end" className="min-w-56">1186 <MenuPopup align="end">
1187 <MenuItem1187 <MenuItem
1188 disabled={!settingsHydrated || atProfileLimit}1188 disabled={!settingsHydrated || atProfileLimit}
1189 onClick={() => createProfile("New profile")}1189 onClick={() => createProfile("New profile")}
...@@ -1301,7 +1301,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {...@@ -1301,7 +1301,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
1301 >1301 >
1302 <MoreVertical />1302 <MoreVertical />
1303 </MenuTrigger>1303 </MenuTrigger>
1304 <MenuPopup align="end" className="min-w-44">1304 <MenuPopup align="end">
1305 <MenuItem1305 <MenuItem
1306 disabled={!settingsHydrated || isDefault}1306 disabled={!settingsHydrated || isDefault}
1307 onClick={() => {1307 onClick={() => {
apps/web/src/components/settings/KeybindingsSettings.tsx+4-18
...@@ -324,12 +324,7 @@ function WhenVariableSelect({...@@ -324,12 +324,7 @@ function WhenVariableSelect({
324 <UnknownWhenVariableWarning identifiers={unknownIdentifiers} focusable={false} />324 <UnknownWhenVariableWarning identifiers={unknownIdentifiers} focusable={false} />
325 ) : null}325 ) : null}
326 </SelectTrigger>326 </SelectTrigger>
327 <SelectContent327 <SelectContent alignItemWithTrigger={false} matchTriggerWidth={false} className="max-h-72">
328 alignItemWithTrigger={false}
329 matchTriggerWidth={false}
330 popupClassName="w-fit"
331 className="max-h-72 w-fit min-w-44"
332 >
333 {options.map((option) => (328 {options.map((option) => (
334 <SelectItem key={option} value={option} className="min-h-7 w-full py-1 text-[12px]">329 <SelectItem key={option} value={option} className="min-h-7 w-full py-1 text-[12px]">
335 <span className="truncate">{option}</span>330 <span className="truncate">{option}</span>
...@@ -534,12 +529,7 @@ function WhenExpressionNodeEditor({...@@ -534,12 +529,7 @@ function WhenExpressionNodeEditor({
534 <SelectTrigger size="compact" className="w-24">529 <SelectTrigger size="compact" className="w-24">
535 <SelectValue />530 <SelectValue />
536 </SelectTrigger>531 </SelectTrigger>
537 <SelectContent532 <SelectContent alignItemWithTrigger={false} matchTriggerWidth={false}>
538 alignItemWithTrigger={false}
539 matchTriggerWidth={false}
540 popupClassName="w-fit"
541 className="w-fit min-w-24"
542 >
543 <SelectItem value="and" className="min-h-7 py-1 text-[12px]">533 <SelectItem value="and" className="min-h-7 py-1 text-[12px]">
544 and534 and
545 </SelectItem>535 </SelectItem>
...@@ -956,7 +946,7 @@ function KeybindingRowMenu({...@@ -956,7 +946,7 @@ function KeybindingRowMenu({
956 >946 >
957 <EllipsisIcon className="size-3.5" />947 <EllipsisIcon className="size-3.5" />
958 </MenuTrigger>948 </MenuTrigger>
959 <MenuPopup align="end" className="min-w-36">949 <MenuPopup align="end">
960 {canReset ? (950 {canReset ? (
961 <MenuItem disabled={isSaving} onClick={() => onReset(row)}>951 <MenuItem disabled={isSaving} onClick={() => onReset(row)}>
962 Reset to default952 Reset to default
...@@ -1153,11 +1143,7 @@ function NewKeybindingCommandSelect({...@@ -1153,11 +1143,7 @@ function NewKeybindingCommandSelect({
1153 <SelectTrigger size="sm" className={className}>1143 <SelectTrigger size="sm" className={className}>
1154 <SelectValue placeholder="Command" />1144 <SelectValue placeholder="Command" />
1155 </SelectTrigger>1145 </SelectTrigger>
1156 <SelectContent1146 <SelectContent alignItemWithTrigger={false} matchTriggerWidth={false} className="max-h-72">
1157 alignItemWithTrigger={false}
1158 matchTriggerWidth={false}
1159 className="max-h-72 w-fit min-w-56"
1160 >
1161 {commandOptions.map((command) => (1147 {commandOptions.map((command) => (
1162 <SelectItem key={command} value={command} className="min-h-7 w-full py-1 text-[12px]">1148 <SelectItem key={command} value={command} className="min-h-7 w-full py-1 text-[12px]">
1163 <span className="truncate">{commandLabel(command)}</span>1149 <span className="truncate">{commandLabel(command)}</span>
apps/web/src/components/settings/ProjectActionsSettings.tsx+1-1
...@@ -156,7 +156,7 @@ export function ProjectActionsSettings() {...@@ -156,7 +156,7 @@ export function ProjectActionsSettings() {
156 Import scripts156 Import scripts
157 <ChevronDownIcon className="size-3.5" />157 <ChevronDownIcon className="size-3.5" />
158 </MenuTrigger>158 </MenuTrigger>
159 <MenuPopup align="end" className="w-72">159 <MenuPopup align="end">
160 <MenuGroup>160 <MenuGroup>
161 <MenuGroupLabel>Import from t3.json</MenuGroupLabel>161 <MenuGroupLabel>Import from t3.json</MenuGroupLabel>
162 <p className="px-2 pb-2 text-pretty text-sm text-muted-foreground">162 <p className="px-2 pb-2 text-pretty text-sm text-muted-foreground">
apps/web/src/components/settings/ProviderInstanceCard.tsx+1-5
...@@ -708,11 +708,7 @@ export function ProviderInstanceCard({...@@ -708,11 +708,7 @@ export function ProviderInstanceCard({
708 </Button>708 </Button>
709 }709 }
710 />710 />
711 <PopoverPopup711 <PopoverPopup side="bottom" align="end" width="md">
712 side="bottom"
713 align="end"
714 className="w-[min(21rem,calc(100vw-1.5rem))] [--popup-width:min(21rem,calc(100vw-1.5rem))]"
715 >
716 <div className="grid min-w-0 gap-3">712 <div className="grid min-w-0 gap-3">
717 <div className="grid gap-0.5">713 <div className="grid gap-0.5">
718 <p className="text-[13px] font-semibold leading-tight text-foreground">714 <p className="text-[13px] font-semibold leading-tight text-foreground">
apps/web/src/components/settings/SettingInheritance.tsx+1-1
...@@ -225,7 +225,7 @@ export function SettingInheritance({...@@ -225,7 +225,7 @@ export function SettingInheritance({
225 </Tooltip>225 </Tooltip>
226 <PopoverPopup226 <PopoverPopup
227 align="start"227 align="start"
228 className="w-72 max-w-[calc(100vw-2rem)]"228 width="md"
229 viewportClassName="p-0 [--viewport-inline-padding:0px]"229 viewportClassName="p-0 [--viewport-inline-padding:0px]"
230 >230 >
231 <div className="divide-y divide-border/60">231 <div className="divide-y divide-border/60">
apps/web/src/components/settings/SettingsBreadcrumb.tsx+1-3
...@@ -119,9 +119,7 @@ function ScopeMenu({...@@ -119,9 +119,7 @@ function ScopeMenu({
119 {icon}119 {icon}
120 <span className="min-w-0 truncate">{label}</span>120 <span className="min-w-0 truncate">{label}</span>
121 </MenuTrigger>121 </MenuTrigger>
122 <MenuPopup align="start" className="w-64 max-w-[calc(100vw-2rem)]">122 <MenuPopup align="start">{children}</MenuPopup>
123 {children}
124 </MenuPopup>
125 </Menu>123 </Menu>
126 );124 );
127}125}
apps/web/src/components/sidebar/SidebarUpdatePill.tsx+1-1
...@@ -393,7 +393,7 @@ function SidebarUpdateControl() {...@@ -393,7 +393,7 @@ function SidebarUpdateControl() {
393 <PopoverPopup393 <PopoverPopup
394 align="center"394 align="center"
395 aria-label="Nightly update release notes"395 aria-label="Nightly update release notes"
396 className="max-w-none text-balance shadow-xl shadow-black/25"396 className="text-balance shadow-xl shadow-black/25"
397 initialFocus={false}397 initialFocus={false}
398 onKeyDownCapture={(event) => {398 onKeyDownCapture={(event) => {
399 if (399 if (
apps/web/src/components/ui/menu.tsx+2-8
...@@ -35,13 +35,6 @@ function MenuPopup({...@@ -35,13 +35,6 @@ function MenuPopup({
35 anchor?: MenuPrimitive.Positioner.Props["anchor"];35 anchor?: MenuPrimitive.Positioner.Props["anchor"];
36 keepMounted?: boolean;36 keepMounted?: boolean;
37}) {37}) {
38 const hasExplicitWidthClass =
39 typeof className === "string" &&
40 className.split(/\s+/).some((classToken) => {
41 const utility = classToken.split(":").at(-1) ?? classToken;
42 return /^(?:min-|max-)?w-/.test(utility);
43 });
44
45 return (38 return (
46 <MenuPrimitive.Portal keepMounted={keepMounted}>39 <MenuPrimitive.Portal keepMounted={keepMounted}>
47 <MenuPrimitive.Positioner40 <MenuPrimitive.Positioner
...@@ -60,7 +53,8 @@ function MenuPopup({...@@ -60,7 +53,8 @@ function MenuPopup({
60 // the Review panel header). Drag hit-testing ignores z-index, so53 // the Review panel header). Drag hit-testing ignores z-index, so
61 // the topmost row would stay unhoverable without this opt-out.54 // the topmost row would stay unhoverable without this opt-out.
62 "[-webkit-app-region:no-drag]",55 "[-webkit-app-region:no-drag]",
63 !hasExplicitWidthClass && "min-w-32",56 // Menus size to their content from one minimum, never past the viewport.
57 "min-w-[min(10rem,calc(100vw-2rem))] max-w-[calc(100vw-2rem)]",
64 className,58 className,
65 )}59 )}
66 data-slot="menu-popup"60 data-slot="menu-popup"
apps/web/src/components/ui/popover.tsx+12
...@@ -16,10 +16,20 @@ function PopoverTrigger({ className, children, ...props }: PopoverPrimitive.Trig...@@ -16,10 +16,20 @@ function PopoverTrigger({ className, children, ...props }: PopoverPrimitive.Trig
16 );16 );
17}17}
1818
19// Popovers hold prose and forms, so a width is fixed rather than a minimum,
20// and every width is capped to the viewport.
21const popoverPopupWidthClassName = {
22 auto: "",
23 sm: "w-64",
24 md: "w-80",
25 lg: "w-96",
26} as const;
27
19function PopoverPopup({28function PopoverPopup({
20 children,29 children,
21 className,30 className,
22 viewportClassName,31 viewportClassName,
32 width = "auto",
23 side = "bottom",33 side = "bottom",
24 align = "center",34 align = "center",
25 sideOffset = 4,35 sideOffset = 4,
...@@ -37,6 +47,7 @@ function PopoverPopup({...@@ -37,6 +47,7 @@ function PopoverPopup({
37 tooltipStyle?: boolean;47 tooltipStyle?: boolean;
38 keepMounted?: PopoverPrimitive.Portal.Props["keepMounted"];48 keepMounted?: PopoverPrimitive.Portal.Props["keepMounted"];
39 anchor?: PopoverPrimitive.Positioner.Props["anchor"];49 anchor?: PopoverPrimitive.Positioner.Props["anchor"];
50 width?: keyof typeof popoverPopupWidthClassName;
40}) {51}) {
41 // Viewport rekeys its children when the active trigger clears on close. Persistent52 // Viewport rekeys its children when the active trigger clears on close. Persistent
42 // single-trigger forms need a stable container to retain drafts and submit guards.53 // single-trigger forms need a stable container to retain drafts and submit guards.
...@@ -59,6 +70,7 @@ function PopoverPopup({...@@ -59,6 +70,7 @@ function PopoverPopup({
59 "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]",70 "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]",
60 !tooltipStyle &&71 !tooltipStyle &&
61 "shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]",72 "shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]",
73 width !== "auto" && ["max-w-[calc(100vw-2rem)]", popoverPopupWidthClassName[width]],
62 className,74 className,
63 )}75 )}
64 data-slot="popover-popup"76 data-slot="popover-popup"
apps/web/src/components/usage/UsagePage.tsx+1-1
...@@ -762,7 +762,7 @@ function UsageEnvironmentFilter({...@@ -762,7 +762,7 @@ function UsageEnvironmentFilter({
762 )}762 )}
763 </span>763 </span>
764 </MenuTrigger>764 </MenuTrigger>
765 <MenuPopup align="start" className="w-80 max-w-[calc(100vw-2rem)]">765 <MenuPopup align="start">
766 <MenuCheckboxItem766 <MenuCheckboxItem
767 checked={allSelected}767 checked={allSelected}
768 closeOnClick={false}768 closeOnClick={false}
apps/web/src/components/usage/UsagePriceOverrides.tsx+1-1
...@@ -269,7 +269,7 @@ export function UsagePriceOverrides({...@@ -269,7 +269,7 @@ export function UsagePriceOverrides({
269 </span>269 </span>
270 <ChevronDownIcon className="size-3.5 shrink-0" aria-hidden />270 <ChevronDownIcon className="size-3.5 shrink-0" aria-hidden />
271 </MenuTrigger>271 </MenuTrigger>
272 <MenuPopup align="start" className="w-80 max-w-[calc(100vw-2rem)]">272 <MenuPopup align="start">
273 <MenuCheckboxItem273 <MenuCheckboxItem
274 checked={selectedIds === null}274 checked={selectedIds === null}
275 closeOnClick={false}275 closeOnClick={false}
apps/web/src/routes/_chat.pull-requests.tsx+1-1
...@@ -2259,7 +2259,7 @@ function CompactFilterMenu<Value extends string>({...@@ -2259,7 +2259,7 @@ function CompactFilterMenu<Value extends string>({
2259 </>2259 </>
2260 )}2260 )}
2261 </MenuTrigger>2261 </MenuTrigger>
2262 <MenuPopup align="start" side="bottom" className="min-w-40">2262 <MenuPopup align="start" side="bottom">
2263 <MenuRadioGroup value={value} onValueChange={(next) => onChange(next as Value)}>2263 <MenuRadioGroup value={value} onValueChange={(next) => onChange(next as Value)}>
2264 {options.map((option) => {2264 {options.map((option) => {
2265 const item = (2265 const item = (