authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 21:33:01-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 21:33:01-07:00
log361b047f5f559abccacc802cd5ab9992b01473ea
tree9c076940d77bbf97bf45a9efba075736937141f3
parent266d70cc4d887b7459ec6557c430ec7bb92bd3fc
signature Signed by PGP key B5690EEEBB952194

refactor(web): ui components drop their secondary className props (#13193)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

30 files changed, 127 insertions(+), 205 deletions(-)

apps/web/src/components/ChatView.tsx-1
......@@ -10378,7 +10378,6 @@ export default function ChatView(props: ChatViewProps) {
1037810378 <RightPanelSheet
1037910379 animationDurationMs={panelAnimationsActive ? panelAnimationDurationMs : 0}
1038010380 open={rightPanelOpen}
10381 underFloatingPreview={previewMiniPlayerVisible}
1038210381 onClose={closePreviewPanel}
1038310382 >
1038410383 <RightPanelTabs
apps/web/src/components/CommandPalette.tsx-3
......@@ -2966,9 +2966,6 @@ function OpenCommandPaletteDialog(props: {
29662966 })
29672967 : undefined,
29682968 placeholder: inputPlaceholder,
2969 wrapperClassName: isSubmenu
2970 ? "[&_[data-slot=autocomplete-start-addon]]:pointer-events-auto"
2971 : undefined,
29722969 ...(isSubmenu
29732970 ? {
29742971 startAddon: (
apps/web/src/components/DiffPanel.tsx-2
......@@ -775,7 +775,6 @@ export default function DiffPanel({
775775 <ComboboxList className="max-h-64 min-w-0 overflow-x-hidden">
776776 <ComboboxItem
777777 className="w-full min-w-0 grid-cols-[1rem_minmax(0,1fr)]"
778 contentClassName="w-full min-w-0 overflow-hidden"
779778 value={AUTOMATIC_BASE_REF}
780779 >
781780 <span className="block min-w-0 truncate">Automatic</span>
......@@ -788,7 +787,6 @@ export default function DiffPanel({
788787 <ComboboxItem
789788 key={choice.id}
790789 className="w-full min-w-0 grid-cols-[1rem_minmax(0,1fr)]"
791 contentClassName="w-full min-w-0 overflow-hidden"
792790 value={item}
793791 >
794792 <div className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_2rem] items-center overflow-hidden">
apps/web/src/components/RightPanelSheet.tsx+1-11
......@@ -1,16 +1,12 @@
11import { type ReactNode } from "react";
22
3import {
4 RIGHT_PANEL_SHEET_CLASS_NAME,
5 RIGHT_PANEL_SHEET_LAYER_CLASS_NAME,
6} from "../rightPanelLayout";
3import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout";
74import { Sheet, SheetPopup } from "./ui/sheet";
85
96export function RightPanelSheet(props: {
107 animationDurationMs: number;
118 children: ReactNode;
129 open: boolean;
13 underFloatingPreview?: boolean;
1410 onClose: () => void;
1511}) {
1612 return (
......@@ -27,12 +23,6 @@ export function RightPanelSheet(props: {
2723 side="right"
2824 showCloseButton={false}
2925 keepMounted
30 {...(props.underFloatingPreview
31 ? {
32 backdropClassName: RIGHT_PANEL_SHEET_LAYER_CLASS_NAME,
33 viewportClassName: RIGHT_PANEL_SHEET_LAYER_CLASS_NAME,
34 }
35 : {})}
3626 className={RIGHT_PANEL_SHEET_CLASS_NAME}
3727 >
3828 {props.children}
apps/web/src/components/Sidebar.tsx+15-24
......@@ -235,7 +235,7 @@ import {
235235import { SidebarContent, SidebarGroup, useSidebar } from "./ui/sidebar";
236236import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome";
237237import { SidebarHeaderIconButton, SidebarThreadHeader } from "./sidebar/SidebarThreadHeader";
238import { Popover, PopoverPopup, PopoverTrigger } from "./ui/popover";
238import { Menu, MenuItem, MenuPopup, MenuSeparator, MenuShortcut, MenuTrigger } from "./ui/menu";
239239import { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
240240import { MiddleTruncate } from "./ui/middle-truncate";
241241import {
......@@ -440,7 +440,7 @@ function SidebarThreadTooltip({
440440 * Controlled by the row (which also uses the open state to pin its hover
441441 * actions while the menu is up).
442442 */
443function SnoozePopoverButton(props: {
443function SnoozeMenuButton(props: {
444444 open: boolean;
445445 onOpenChange: (open: boolean) => void;
446446 onSnooze: (preset: Pick<SnoozePreset, "snoozedUntil">) => void;
......@@ -454,11 +454,11 @@ function SnoozePopoverButton(props: {
454454 [open, timestampFormat],
455455 );
456456 return (
457 <Popover open={open} onOpenChange={onOpenChange}>
457 <Menu open={open} onOpenChange={onOpenChange}>
458458 <Tooltip>
459459 <TooltipTrigger
460460 render={
461 <PopoverTrigger
461 <MenuTrigger
462462 render={
463463 <button
464464 type="button"
......@@ -475,39 +475,31 @@ function SnoozePopoverButton(props: {
475475 </TooltipTrigger>
476476 <TooltipPopup>Snooze thread</TooltipPopup>
477477 </Tooltip>
478 <PopoverPopup side="bottom" align="end" width="sm" viewportClassName="p-1">
478 <MenuPopup side="bottom" align="end">
479479 {presets.map((preset) => (
480 <button
480 <MenuItem
481481 key={preset.id}
482 type="button"
483482 onClick={(event) => {
484483 event.stopPropagation();
485 onOpenChange(false);
486484 onSnooze(preset);
487485 }}
488 className="flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs text-foreground/90 hover:bg-accent hover:text-foreground"
489486 >
490 <span className="flex-1">{preset.label}</span>
491 <span className="font-mono text-[10px] text-muted-foreground/60 tabular-nums">
492 {preset.whenLabel}
493 </span>
494 </button>
487 {preset.label}
488 <MenuShortcut>{preset.whenLabel}</MenuShortcut>
489 </MenuItem>
495490 ))}
496 <div className="my-1 border-t border-border/60" />
497 <button
498 type="button"
499 className="flex w-full cursor-pointer rounded-md px-2 py-1.5 text-left text-xs text-foreground/90 hover:bg-accent hover:text-foreground"
491 <MenuSeparator />
492 <MenuItem
500493 onClick={async (event) => {
501494 event.stopPropagation();
502 onOpenChange(false);
503495 const choice = await requestCustomSnooze();
504496 if (choice) onSnooze(choice);
505497 }}
506498 >
507499 Custom…
508 </button>
509 </PopoverPopup>
510 </Popover>
500 </MenuItem>
501 </MenuPopup>
502 </Menu>
511503 );
512504}
513505
......@@ -1883,7 +1875,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
18831875 </Tooltip>
18841876 ) : null}
18851877 {showSnoozeButton ? (
1886 <SnoozePopoverButton
1878 <SnoozeMenuButton
18871879 open={snoozeMenuOpen}
18881880 onOpenChange={setSnoozeMenuOpen}
18891881 onSnooze={handleSnoozePreset}
......@@ -4492,7 +4484,6 @@ export default function Sidebar() {
44924484 hideIndicator
44934485 value={item}
44944486 className="font-medium"
4495 contentClassName="flex min-w-0 items-center gap-2"
44964487 onContextMenu={(event) => {
44974488 if (project) handleProjectSettings(event, project);
44984489 }}
apps/web/src/components/chat/AssistantCitationChip.tsx+1-1
......@@ -171,7 +171,7 @@ export function AssistantCitationChip({
171171 }}
172172 aria-label="Edit citation comment"
173173 width="md"
174 viewportClassName="p-3"
174 padding="compact"
175175 onPointerDown={(event) => event.stopPropagation()}
176176 >
177177 <AssistantCitationCommentEditor
apps/web/src/components/chat/ContextWindowMeter.tsx+1-1
......@@ -88,7 +88,7 @@ export function ContextWindowMeter(props: {
8888 tooltipStyle
8989 side="top"
9090 align="end"
91 viewportClassName="p-0"
91 padding="none"
9292 width="sm"
9393 className="text-left whitespace-normal"
9494 >
apps/web/src/components/chat/ExpandedImageDialog.tsx-2
......@@ -179,8 +179,6 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({
179179 variant="media"
180180 showCloseButton={false}
181181 bottomStickOnMobile={false}
182 backdropClassName="z-[60]"
183 viewportClassName="z-[60] grid-rows-1 place-items-center px-4 py-6 [-webkit-app-region:no-drag]"
184182 className="row-start-1 max-h-[92vh] w-[92vw] max-w-[92vw] items-center overflow-visible [--media-width:92vw] [--media-height:min(86vh,calc(100vh-160px))] sm:[--media-width:calc(92vw-96px)]"
185183 onKeyDown={onKeyDown}
186184 initialFocus={closeButtonRef}
apps/web/src/components/chat/MessagesTimeline.tsx+1-1
......@@ -2567,7 +2567,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap
25672567 align="end"
25682568 width="lg"
25692569 className="surface-glass!"
2570 viewportClassName="py-3 [--viewport-inline-padding:--spacing(3)]"
2570 padding="compact"
25712571 >
25722572 <WorktreeSetupCard
25732573 snapshot={snapshot}
apps/web/src/components/chat/ModelListRow.tsx-1
......@@ -52,7 +52,6 @@ export const ModelListRow = memo(function ModelListRow(props: {
5252 index={props.index}
5353 value={modelPickerModelKey(props.instanceId, props.model.slug)}
5454 disabled={Boolean(props.disabledReason)}
55 contentClassName="flex w-full items-center gap-3"
5655 className={cn(
5756 "group relative w-full !min-w-0 max-w-full cursor-pointer rounded-md px-2 py-2 transition-[background-color,box-shadow,color]",
5857 "hover:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))] data-highlighted:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))] data-selected:bg-foreground/[0.08] data-selected:text-foreground data-selected:ring-0 [&[data-highlighted][data-selected]]:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))]",
apps/web/src/components/chat/ModelPickerContent.tsx+57-73
......@@ -8,7 +8,7 @@ import { resolveSelectableModel } from "@t3tools/shared/model";
88import { useAtomValue } from "@effect/atom-react";
99import { LegendList, type LegendListRef } from "@legendapp/list/react";
1010import { memo, useMemo, useState, useCallback, useEffect, useLayoutEffect, useRef } from "react";
11import { ChevronRightIcon, SearchIcon } from "lucide-react";
11import { ChevronRightIcon } from "lucide-react";
1212import { ModelListRow } from "./ModelListRow";
1313import { ModelPickerSidebar } from "./ModelPickerSidebar";
1414import { getProviderStatusMessage, hasProviderSetup } from "./ProviderStatusBanner";
......@@ -22,7 +22,7 @@ import { buildModelPickerSearchText, scoreModelPickerSearch } from "./modelPicke
2222import {
2323 Combobox,
2424 ComboboxEmpty,
25 ComboboxInput,
25 ComboboxSearchInput,
2626 ComboboxItem,
2727 ComboboxListVirtualized,
2828} from "../ui/combobox";
......@@ -882,77 +882,62 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
882882 showSidebar && "border-l border-border/70",
883883 )}
884884 >
885 {/* Search bar */}
886 <div className="px-2 pt-2">
887 <div className="border-b border-border/70 pb-2.5 transition-colors focus-within:border-ring">
888 <ComboboxInput
889 ref={searchInputRef}
890 className="[&_input]:h-6.5 [&_input]:font-sans [&_input]:leading-6.5"
891 inputClassName="rounded-none bg-transparent text-sm"
892 placeholder="Search models..."
893 showTrigger={false}
894 startAddon={
895 <SearchIcon className="-translate-x-0.5 size-4 shrink-0 text-muted-foreground opacity-70" />
896 }
897 value={searchQuery}
898 onChange={(e) => setSearchQuery(e.target.value)}
899 onKeyDown={(e) => {
900 if (
901 showSidebar &&
902 !e.altKey &&
903 !e.ctrlKey &&
904 !e.metaKey &&
905 ((e.key === "ArrowLeft" && !e.shiftKey && searchQuery.length === 0) ||
906 (e.key === "Tab" && e.shiftKey))
907 ) {
908 const sidebar = e.currentTarget
909 .closest("[data-model-picker-content]")
910 ?.querySelector("[data-model-picker-sidebar]");
911 const button =
912 sidebar?.querySelector<HTMLButtonElement>(
913 'button[aria-pressed="true"]:not(:disabled)',
914 ) ?? sidebar?.querySelector<HTMLButtonElement>("button:not(:disabled)");
915 if (button) {
916 e.preventDefault();
917 e.stopPropagation();
918 button.focus();
919 return;
920 }
921 }
922 if (e.key === "Escape") {
923 e.preventDefault();
924 e.stopPropagation();
925 props.onRequestClose?.();
926 return;
927 }
928 if (e.key === "Enter" && highlightedModelKeyRef.current) {
929 (
930 e as typeof e & { preventBaseUIHandler?: () => void }
931 ).preventBaseUIHandler?.();
932 e.preventDefault();
933 e.stopPropagation();
934 const legacyInstanceId = parseModelPickerLegacySectionKey(
935 highlightedModelKeyRef.current,
936 );
937 if (legacyInstanceId) {
938 toggleLegacySection(legacyInstanceId);
939 return;
940 }
941 const model = parseModelPickerModelKey(highlightedModelKeyRef.current);
942 if (model) {
943 handleModelSelect(model.slug, model.instanceId, e.shiftKey);
944 }
945 return;
946 }
885 <ComboboxSearchInput
886 ref={searchInputRef}
887 placeholder="Search models..."
888 value={searchQuery}
889 onChange={(e) => setSearchQuery(e.target.value)}
890 onKeyDown={(e) => {
891 if (
892 showSidebar &&
893 !e.altKey &&
894 !e.ctrlKey &&
895 !e.metaKey &&
896 ((e.key === "ArrowLeft" && !e.shiftKey && searchQuery.length === 0) ||
897 (e.key === "Tab" && e.shiftKey))
898 ) {
899 const sidebar = e.currentTarget
900 .closest("[data-model-picker-content]")
901 ?.querySelector("[data-model-picker-sidebar]");
902 const button =
903 sidebar?.querySelector<HTMLButtonElement>(
904 'button[aria-pressed="true"]:not(:disabled)',
905 ) ?? sidebar?.querySelector<HTMLButtonElement>("button:not(:disabled)");
906 if (button) {
907 e.preventDefault();
947908 e.stopPropagation();
948 }}
949 onMouseDown={(e) => e.stopPropagation()}
950 onTouchStart={(e) => e.stopPropagation()}
951 size="sm"
952 unstyled
953 />
954 </div>
955 </div>
909 button.focus();
910 return;
911 }
912 }
913 if (e.key === "Escape") {
914 e.preventDefault();
915 e.stopPropagation();
916 props.onRequestClose?.();
917 return;
918 }
919 if (e.key === "Enter" && highlightedModelKeyRef.current) {
920 (e as typeof e & { preventBaseUIHandler?: () => void }).preventBaseUIHandler?.();
921 e.preventDefault();
922 e.stopPropagation();
923 const legacyInstanceId = parseModelPickerLegacySectionKey(
924 highlightedModelKeyRef.current,
925 );
926 if (legacyInstanceId) {
927 toggleLegacySection(legacyInstanceId);
928 return;
929 }
930 const model = parseModelPickerModelKey(highlightedModelKeyRef.current);
931 if (model) {
932 handleModelSelect(model.slug, model.instanceId, e.shiftKey);
933 }
934 return;
935 }
936 e.stopPropagation();
937 }}
938 onMouseDown={(e) => e.stopPropagation()}
939 onTouchStart={(e) => e.stopPropagation()}
940 />
956941
957942 {/* Model list */}
958943 <div className="relative min-h-0 flex-1 overflow-hidden pr-px">
......@@ -971,7 +956,6 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
971956 value={modelKey}
972957 aria-expanded={legacySection.isExpanded}
973958 className="group w-full cursor-pointer rounded-md px-2 py-2"
974 contentClassName="flex w-full items-center gap-3"
975959 >
976960 <div className="min-w-0 flex-1 text-left">
977961 <div className="text-xs font-medium leading-snug">Legacy models</div>
apps/web/src/components/chat/ProviderModelPicker.tsx+2-2
......@@ -284,8 +284,8 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
284284 <PopoverPopup
285285 {...(props.isComposerOwned ? composerFloatingLayerProps : {})}
286286 align="start"
287 className="before:hidden [--viewport-inline-padding:0]"
288 viewportClassName="overflow-hidden! rounded-[calc(var(--radius-lg)-1px)] p-0 [clip-path:inset(0_round_calc(var(--radius-lg)-1px))]"
287 className="before:hidden"
288 padding="none"
289289 >
290290 <ModelPickerContent
291291 activeInstanceId={activeInstanceId}
apps/web/src/components/chat/SnapShotAttachmentDetails.tsx+2-7
......@@ -163,13 +163,8 @@ export function SnapShotContentsButton({
163163 </TooltipTrigger>
164164 <TooltipPopup side={side}>{tooltip}</TooltipPopup>
165165 </Tooltip>
166 <PopoverPopup
167 side={side}
168 align="center"
169 width="md"
170 viewportClassName="max-h-[min(28rem,70vh)]"
171 >
172 <div className="space-y-2">
166 <PopoverPopup side={side} align="center" width="md">
167 <div className="max-h-[min(28rem,70vh)] space-y-2 overflow-y-auto">
173168 <PopoverTitle>Accessibility data</PopoverTitle>
174169 {accessibilityDetails ? (
175170 <SnapShotAccessibilityData
apps/web/src/components/chat/TerminalContextInlineChip.tsx-2
......@@ -23,8 +23,6 @@ export function TerminalContextInlineChip(props: TerminalContextInlineChipProps)
2323 icon={<TerminalIcon />}
2424 label={label}
2525 accessibleLabel={`Terminal excerpt, ${label}`}
26 popupClassName="w-[min(40rem,calc(100vw-2rem))]"
27 viewportClassName="overflow-hidden p-2"
2826 >
2927 <div className="overflow-hidden rounded-md border border-border/70 bg-background/80">
3028 <div className="flex items-center gap-2 border-b border-border/70 px-3 py-2">
apps/web/src/components/contextChipParts.tsx+1-9
......@@ -8,7 +8,6 @@ import {
88 type ReactNode,
99} from "react";
1010
11import { cn } from "~/lib/utils";
1211import { PULL_REQUEST_STATE_PRESENTATION } from "~/components/pullRequest/pullRequestIcons";
1312import type { PullRequestContextDisplayState } from "~/lib/composerContextRecords";
1413import { PierreEntryIcon } from "./chat/PierreEntryIcon";
......@@ -70,8 +69,6 @@ export function ContextChipPopover(props: {
7069 copyMarkdown?: string;
7170 accessibleLabel: string;
7271 children: ReactNode;
73 popupClassName?: string;
74 viewportClassName?: string;
7572}) {
7673 return (
7774 <Popover>
......@@ -88,12 +85,7 @@ export function ContextChipPopover(props: {
8885 {props.icon}
8986 <ContextChipLabel>{props.label}</ContextChipLabel>
9087 </PopoverTrigger>
91 <PopoverPopup
92 side="top"
93 width="lg"
94 className={props.popupClassName}
95 viewportClassName={cn("overflow-x-auto p-2", props.viewportClassName)}
96 >
88 <PopoverPopup side="top" width="lg" padding="compact">
9789 <PopoverTitle className="sr-only">{props.accessibleLabel}</PopoverTitle>
9890 {props.children}
9991 </PopoverPopup>
apps/web/src/components/preview/previewMiniPlayerLayout.ts+1-1
......@@ -11,7 +11,7 @@ import type { DeviceScreenSize } from "@t3tools/client-runtime/device/stream";
1111
1212export const PREVIEW_MINI_PLAYER_EDGE_GAP = 12;
1313export const PREVIEW_MINI_PLAYER_CORNER_RADIUS = 12;
14// The mini-player shell straddles this webview at 47 and 49; dialogs begin at 50.
14// The mini-player shell straddles this webview at 47 and 49: above --z-sheet, under dialogs (50).
1515export const PREVIEW_MINI_PLAYER_WEBVIEW_Z_INDEX = 48;
1616// A fresh player is the largest box at the source aspect ratio that fits here.
1717const PREVIEW_MINI_PLAYER_DEFAULT_BOX = { width: 320, height: 320 } as const;
apps/web/src/components/pullRequest/PullRequestCandidatePicker.tsx-1
......@@ -147,7 +147,6 @@ export function PullRequestCandidatePicker<T>({
147147 index={index}
148148 value={keys[index]}
149149 disabled={disabled}
150 contentClassName="flex min-w-0 items-center gap-2"
151150 >
152151 {children(candidate)}
153152 </ComboboxItem>
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+1-6
......@@ -388,12 +388,7 @@ function PullRequestBaseFreshnessWarning({
388388 {children}
389389 <TriangleAlertIcon aria-hidden className={cn("size-3.5 shrink-0", iconClassName)} />
390390 </PopoverTrigger>
391 <PopoverPopup
392 align="start"
393 side="bottom"
394 className="max-w-80"
395 viewportClassName="py-2.5 [--viewport-inline-padding:--spacing(3)]"
396 >
391 <PopoverPopup align="start" side="bottom" className="max-w-80" padding="compact">
397392 <p className="text-xs text-foreground">{summary}</p>
398393 <p className="mt-0.5 text-xs text-muted-foreground">Changes can be cleanly merged.</p>
399394 {/* Each way the host offers and this reader may take, as its own button: a split button
apps/web/src/components/pullRequest/PullRequestReactions.tsx+1-1
......@@ -134,7 +134,7 @@ export function PullRequestReactionBar({
134134 >
135135 <SmilePlusIcon aria-hidden className="size-3.5" />
136136 </PopoverTrigger>
137 <PopoverPopup align="start" side="top" viewportClassName="py-2">
137 <PopoverPopup align="start" side="top" padding="compact">
138138 <div className="flex items-center gap-0.5">
139139 {PULL_REQUEST_REACTION_ORDER.map((content) => {
140140 const reacted =
apps/web/src/components/settings/SettingInheritance.tsx+1-5
......@@ -223,11 +223,7 @@ export function SettingInheritance({
223223 </TooltipTrigger>
224224 <TooltipPopup side="top">{overrideSummary}</TooltipPopup>
225225 </Tooltip>
226 <PopoverPopup
227 align="start"
228 width="md"
229 viewportClassName="p-0 [--viewport-inline-padding:0px]"
230 >
226 <PopoverPopup align="start" width="md" padding="none">
231227 <div className="divide-y divide-border/60">
232228 {chains.map(({ target, environment, machine, layers }) => (
233229 <section
apps/web/src/components/ui/autocomplete.tsx+1-1
......@@ -30,7 +30,7 @@ function AutocompleteInput({
3030 {startAddon && (
3131 <div
3232 aria-hidden="true"
33 className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
33 className="[&_svg]:-mx-0.5 pointer-events-none has-[button]:pointer-events-auto absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
3434 data-slot="autocomplete-start-addon"
3535 >
3636 {startAddon}
apps/web/src/components/ui/combobox.tsx+6-10
......@@ -30,7 +30,6 @@ function Combobox<Value, Multiple extends boolean | undefined = false>(
3030
3131function ComboboxInput({
3232 className,
33 inputClassName,
3433 showTrigger = true,
3534 showClear = false,
3635 startAddon,
......@@ -38,7 +37,6 @@ function ComboboxInput({
3837 unstyled = false,
3938 ...props
4039}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
41 inputClassName?: string;
4240 showTrigger?: boolean;
4341 showClear?: boolean;
4442 startAddon?: React.ReactNode;
......@@ -71,7 +69,10 @@ function ComboboxInput({
7169 data-slot="combobox-input"
7270 render={
7371 <Input
74 className={cn("has-disabled:opacity-100", inputClassName)}
72 className={cn(
73 "has-disabled:opacity-100",
74 unstyled && "rounded-none bg-transparent text-sm",
75 )}
7576 nativeInput
7677 size={sizeValue}
7778 unstyled={unstyled}
......@@ -116,7 +117,6 @@ function ComboboxSearchInput(props: React.ComponentProps<typeof ComboboxInput>)
116117 <ComboboxInput
117118 {...props}
118119 className="[&_input]:h-6.5 [&_input]:ps-5 [&_input]:font-sans [&_input]:leading-6.5"
119 inputClassName="rounded-none bg-transparent text-sm"
120120 showTrigger={false}
121121 size="sm"
122122 unstyled
......@@ -185,12 +185,10 @@ function ComboboxPopup({
185185
186186function ComboboxItem({
187187 className,
188 contentClassName,
189188 children,
190189 hideIndicator: _hideIndicator = false,
191190 ...props
192191}: ComboboxPrimitive.Item.Props & {
193 contentClassName?: string;
194192 hideIndicator?: boolean;
195193}) {
196194 return (
......@@ -202,11 +200,9 @@ function ComboboxItem({
202200 data-slot="combobox-item"
203201 {...props}
204202 >
203 {/* Children lay out as one row: a label that truncates, then any trailing meta. */}
205204 <div
206 className={cn(
207 "min-w-0 flex-1 [&_svg:not([class*='text-'])]:text-muted-foreground",
208 contentClassName,
209 )}
205 className="flex min-w-0 flex-1 items-center gap-2 [&_svg:not([class*='text-'])]:text-muted-foreground"
210206 data-slot="combobox-item-content"
211207 >
212208 {children}
apps/web/src/components/ui/command.tsx+2-10
......@@ -93,19 +93,11 @@ function Command({
9393
9494function CommandInput({
9595 className,
96 wrapperClassName,
9796 placeholder,
9897 ...props
99}: React.ComponentProps<typeof AutocompleteInput> & {
100 wrapperClassName?: string | undefined;
101}) {
98}: React.ComponentProps<typeof AutocompleteInput>) {
10299 return (
103 <div
104 className={cn(
105 "px-[var(--command-shell-inset)] py-1.5 [&_[data-slot=autocomplete-start-addon]]:ps-[calc(var(--command-shell-inset)+0.0625rem)]",
106 wrapperClassName,
107 )}
108 >
100 <div className="px-[var(--command-shell-inset)] py-1.5 [&_[data-slot=autocomplete-start-addon]]:ps-[calc(var(--command-shell-inset)+0.0625rem)]">
109101 <AutocompleteInput
110102 autoFocus
111103 className={cn(
apps/web/src/components/ui/dialog.tsx+4-6
......@@ -61,24 +61,22 @@ function DialogPopup({
6161 children,
6262 showCloseButton = true,
6363 bottomStickOnMobile = true,
64 backdropClassName,
65 viewportClassName,
6664 variant = "default",
6765 ...props
6866}: DialogPrimitive.Popup.Props & {
6967 showCloseButton?: boolean;
7068 bottomStickOnMobile?: boolean;
71 backdropClassName?: string;
72 viewportClassName?: string;
7369 variant?: "default" | "media";
7470}) {
7571 return (
7672 <DialogPortal>
77 <DialogBackdrop className={backdropClassName} variant={variant} />
73 {/* Media opens from inside other overlays (a composer chip, a popover), so it sits above them. */}
74 <DialogBackdrop className={variant === "media" ? "z-[60]" : undefined} variant={variant} />
7875 <DialogViewport
7976 className={cn(
8077 bottomStickOnMobile && "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12",
81 viewportClassName,
78 variant === "media" &&
79 "z-[60] grid-rows-1 place-items-center px-4 py-6 [-webkit-app-region:no-drag]",
8280 )}
8381 >
8482 <DialogPrimitive.Popup
apps/web/src/components/ui/popover.tsx+15-6
......@@ -25,10 +25,19 @@ const popoverPopupWidthClassName = {
2525 lg: "w-96",
2626} as const;
2727
28// The inset around the content. "compact" suits dense content (a list, a code excerpt, a
29// row of reactions); "none" is for content that draws its own frame edge to edge.
30const popoverViewportPaddingClassName = {
31 default: "py-4 [--viewport-inline-padding:--spacing(4)]",
32 compact: "py-2 [--viewport-inline-padding:--spacing(3)]",
33 // Rounded to the popup so edge-to-edge content clips to its corners.
34 none: "rounded-[calc(var(--radius-lg)-1px)] py-0 [--viewport-inline-padding:0px]",
35} as const;
36
2837function PopoverPopup({
2938 children,
3039 className,
31 viewportClassName,
40 padding = "default",
3241 width = "auto",
3342 side = "bottom",
3443 align = "center",
......@@ -39,7 +48,7 @@ function PopoverPopup({
3948 anchor,
4049 ...props
4150}: PopoverPrimitive.Popup.Props & {
42 viewportClassName?: string;
51 padding?: keyof typeof popoverViewportPaddingClassName;
4352 side?: PopoverPrimitive.Positioner.Props["side"];
4453 align?: PopoverPrimitive.Positioner.Props["align"];
4554 sideOffset?: PopoverPrimitive.Positioner.Props["sideOffset"];
......@@ -78,11 +87,11 @@ function PopoverPopup({
7887 >
7988 <Viewport
8089 className={cn(
81 "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) py-4 [--viewport-inline-padding:--spacing(4)] has-data-[slot=calendar]:p-2 data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity",
82 tooltipStyle
90 "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) has-data-[slot=calendar]:p-2 data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity",
91 tooltipStyle && padding === "default"
8392 ? "py-1 [--viewport-inline-padding:--spacing(2)]"
84 : "not-data-transitioning:overflow-y-auto",
85 viewportClassName,
93 : popoverViewportPaddingClassName[padding],
94 !tooltipStyle && "not-data-transitioning:overflow-y-auto",
8695 )}
8796 data-slot="popover-viewport"
8897 >
apps/web/src/components/ui/select.tsx-3
......@@ -102,7 +102,6 @@ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
102102
103103function SelectPopup({
104104 className,
105 popupClassName,
106105 children,
107106 side = "bottom",
108107 sideOffset = 4,
......@@ -113,7 +112,6 @@ function SelectPopup({
113112 anchor,
114113 ...props
115114}: SelectPrimitive.Popup.Props & {
116 popupClassName?: string;
117115 side?: SelectPrimitive.Positioner.Props["side"];
118116 sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
119117 align?: SelectPrimitive.Positioner.Props["align"];
......@@ -149,7 +147,6 @@ function SelectPopup({
149147 className={cn(
150148 "dropdown-glass relative h-full rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]",
151149 matchTriggerWidth && "min-w-(--anchor-width)",
152 popupClassName,
153150 )}
154151 >
155152 <SelectPrimitive.List
apps/web/src/components/ui/sheet.tsx+10-12
......@@ -10,11 +10,13 @@ const Sheet = SheetPrimitive.Root;
1010
1111const SheetPortal = SheetPrimitive.Portal;
1212
13// Sheets are docked panels, not dialogs: their layer (--z-sheet) sits under dialogs that open
14// from inside them and under anything the app floats above panels.
1315function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) {
1416 return (
1517 <SheetPrimitive.Backdrop
1618 className={cn(
17 "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
19 "fixed inset-0 z-(--z-sheet) bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
1820 className,
1921 )}
2022 data-slot="sheet-backdrop"
......@@ -35,7 +37,7 @@ function SheetViewport({
3537 return (
3638 <SheetPrimitive.Viewport
3739 className={cn(
38 "fixed inset-0 z-50 grid",
40 "fixed inset-0 z-(--z-sheet) grid",
3941 side === "bottom" && "grid grid-rows-[1fr_auto] pt-12",
4042 side === "top" && "grid grid-rows-[auto_1fr] pb-12",
4143 side === "left" && "flex justify-start",
......@@ -55,8 +57,6 @@ function SheetPopup({
5557 showCloseButton = true,
5658 keepMounted = false,
5759 transitionDurationMs,
58 backdropClassName,
59 viewportClassName,
6060 side = "right",
6161 variant = "default",
6262 style,
......@@ -65,8 +65,6 @@ function SheetPopup({
6565 showCloseButton?: boolean;
6666 keepMounted?: boolean;
6767 transitionDurationMs?: number;
68 backdropClassName?: string;
69 viewportClassName?: string;
7068 side?: "right" | "left" | "top" | "bottom";
7169 variant?: "default" | "inset";
7270}) {
......@@ -79,14 +77,14 @@ function SheetPopup({
7977 return (
8078 <SheetPortal keepMounted={keepMounted}>
8179 <SheetBackdrop
82 className={cn(
83 instant &&
84 "transition-none! data-ending-style:opacity-100! data-starting-style:opacity-100!",
85 backdropClassName,
86 )}
80 className={
81 instant
82 ? "transition-none! data-ending-style:opacity-100! data-starting-style:opacity-100!"
83 : undefined
84 }
8785 style={transitionStyle}
8886 />
89 <SheetViewport className={viewportClassName} side={side} variant={variant}>
87 <SheetViewport side={side} variant={variant}>
9088 <SheetPrimitive.Popup
9189 className={cn(
9290 "relative flex max-h-full min-h-0 w-full min-w-0 flex-col bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
apps/web/src/index.css+3
......@@ -101,6 +101,9 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
101101 --command-shell-inset: 0.5rem;
102102 --command-content-inset: 1rem;
103103 --floating-content-inset: 0.75rem;
104 /* App layer order: docked sheets, then the floating preview player (47–49, see
105 previewMiniPlayerLayout.ts), then dialogs and popovers (50 and up). */
106 --z-sheet: 46;
104107 --glass-blur: 12px;
105108 --glass-opacity: 80%;
106109 --glass-saturation: 1.14;
apps/web/src/rightPanelLayout.ts-2
......@@ -1,5 +1,3 @@
11export const RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 980px)";
2// Applied only while a floating preview overlaps the compact sheet.
3export const RIGHT_PANEL_SHEET_LAYER_CLASS_NAME = "z-[35]";
42export const RIGHT_PANEL_SHEET_CLASS_NAME =
53 "w-[min(42vw,28rem)] min-w-80 max-w-[28rem] p-0 max-[760px]:w-[min(88vw,24rem)] max-[760px]:min-w-0 wco:mt-[env(titlebar-area-height)] wco:h-[calc(100%-env(titlebar-area-height))] wco:max-h-[calc(100%-env(titlebar-area-height))]";
scripts/lint-restyle-ceiling.ts+1-1
......@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
88// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
99// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
1010// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 603;
11export const RESTYLE_CEILING = 582;
1212
1313const RULE = "shadcn(no-restyle)";
1414