Make space for windows controls (#635)

This commit is contained in:
Will Chen
2025-07-11 16:28:13 -07:00
committed by GitHub
parent e539cbefdd
commit be1321152e
2 changed files with 149 additions and 97 deletions

View File

@@ -78,13 +78,13 @@ export const TitleBar = () => {
return ( return (
<> <>
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center"> <div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center">
<div className="pl-20"></div> <div className="pl-18"></div>
<img src={logo} alt="Dyad Logo" className="w-6 h-6 mr-2" /> <img src={logo} alt="Dyad Logo" className="w-6 h-6 mr-0.5" />
<Button <Button
data-testid="title-bar-app-name-button" data-testid="title-bar-app-name-button"
variant="outline" variant="outline"
size="sm" size="sm"
className={`hidden @md:block no-app-region-drag text-sm font-medium ${ className={`hidden @2xl:block no-app-region-drag text-xs max-w-38 truncate font-medium ${
selectedApp ? "cursor-pointer" : "" selectedApp ? "cursor-pointer" : ""
}`} }`}
onClick={handleAppClick} onClick={handleAppClick}
@@ -210,13 +210,15 @@ export function DyadProButton({
}} }}
variant="outline" variant="outline"
className={cn( className={cn(
"ml-4 no-app-region-drag h-7 bg-indigo-600 text-white dark:bg-indigo-600 dark:text-white", "hidden @2xl:block ml-1 no-app-region-drag h-7 bg-indigo-600 text-white dark:bg-indigo-600 dark:text-white text-xs px-2 pt-1 pb-1",
!isDyadProEnabled && "bg-zinc-600 dark:bg-zinc-600", !isDyadProEnabled && "bg-zinc-600 dark:bg-zinc-600",
)} )}
size="sm" size="sm"
> >
{isDyadProEnabled ? "Dyad Pro" : "Dyad Pro (disabled)"} {isDyadProEnabled ? "Pro" : "Pro (off)"}
{userBudget && <AICreditStatus userBudget={userBudget} />} {userBudget && isDyadProEnabled && (
<AICreditStatus userBudget={userBudget} />
)}
</Button> </Button>
); );
} }

View File

@@ -21,6 +21,12 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { showError, showSuccess } from "@/lib/toast"; import { showError, showSuccess } from "@/lib/toast";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { useCheckProblems } from "@/hooks/useCheckProblems"; import { useCheckProblems } from "@/hooks/useCheckProblems";
@@ -28,6 +34,9 @@ import { isPreviewOpenAtom } from "@/atoms/viewAtoms";
export type PreviewMode = "preview" | "code" | "problems" | "configure"; export type PreviewMode = "preview" | "code" | "problems" | "configure";
const BUTTON_CLASS_NAME =
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-[13px] font-medium z-10 hover:bg-[var(--background)]";
// Preview Header component with preview mode toggle // Preview Header component with preview mode toggle
export const PreviewHeader = () => { export const PreviewHeader = () => {
const [previewMode, setPreviewMode] = useAtom(previewModeAtom); const [previewMode, setPreviewMode] = useAtom(previewModeAtom);
@@ -38,9 +47,22 @@ export const PreviewHeader = () => {
const problemsRef = useRef<HTMLButtonElement>(null); const problemsRef = useRef<HTMLButtonElement>(null);
const configureRef = useRef<HTMLButtonElement>(null); const configureRef = useRef<HTMLButtonElement>(null);
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 }); const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const { problemReport } = useCheckProblems(selectedAppId); const { problemReport } = useCheckProblems(selectedAppId);
const { restartApp, refreshAppIframe } = useRunApp(); const { restartApp, refreshAppIframe } = useRunApp();
const isCompact = windowWidth < 840;
// Track window width
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
const selectPanel = (panel: PreviewMode) => { const selectPanel = (panel: PreviewMode) => {
if (previewMode === panel) { if (previewMode === panel) {
setIsPreviewOpen(!isPreviewOpen); setIsPreviewOpen(!isPreviewOpen);
@@ -130,100 +152,128 @@ export const PreviewHeader = () => {
// Small delay to ensure DOM is updated // Small delay to ensure DOM is updated
const timeoutId = setTimeout(updateIndicator, 10); const timeoutId = setTimeout(updateIndicator, 10);
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
}, [previewMode, displayCount, isPreviewOpen]); }, [previewMode, displayCount, isPreviewOpen, isCompact]);
const renderButton = (
mode: PreviewMode,
ref: React.RefObject<HTMLButtonElement | null>,
icon: React.ReactNode,
text: string,
testId: string,
badge?: React.ReactNode,
) => {
const buttonContent = (
<button
data-testid={testId}
ref={ref}
className={BUTTON_CLASS_NAME}
onClick={() => selectPanel(mode)}
>
{icon}
{!isCompact && <span>{text}</span>}
{badge}
</button>
);
if (isCompact) {
return (
<Tooltip>
<TooltipTrigger asChild>{buttonContent}</TooltipTrigger>
<TooltipContent>
<p>{text}</p>
</TooltipContent>
</Tooltip>
);
}
return buttonContent;
};
return ( return (
<div className="flex items-center justify-between px-4 py-2 mt-1 border-b border-border"> <TooltipProvider>
<div className="relative flex rounded-md p-0.5 gap-2"> <div className="flex items-center justify-between px-1 py-2 mt-1 border-b border-border">
<motion.div <div className="relative flex rounded-md p-0.5 gap-0.5">
className="absolute top-0.5 bottom-0.5 bg-[var(--background-lightest)] shadow rounded-md" <motion.div
animate={{ className="absolute top-0.5 bottom-0.5 bg-[var(--background-lightest)] shadow rounded-md"
left: indicatorStyle.left, animate={{
width: indicatorStyle.width, left: indicatorStyle.left,
}} width: indicatorStyle.width,
transition={{ }}
type: "spring", transition={{
stiffness: 600, type: "spring",
damping: 35, stiffness: 600,
mass: 0.6, damping: 35,
}} mass: 0.6,
/> }}
<button />
data-testid="preview-mode-button" {renderButton(
ref={previewRef} "preview",
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10 hover:bg-[var(--background)]" previewRef,
onClick={() => selectPanel("preview")} <Eye size={14} />,
> "Preview",
<Eye size={14} /> "preview-mode-button",
<span>Preview</span>
</button>
<button
data-testid="problems-mode-button"
ref={problemsRef}
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10 hover:bg-[var(--background)]"
onClick={() => selectPanel("problems")}
>
<AlertTriangle size={14} />
<span>Problems</span>
{displayCount && (
<span className="ml-0.5 px-1 py-0.5 text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-full min-w-[16px] text-center">
{displayCount}
</span>
)} )}
</button> {renderButton(
"problems",
<button problemsRef,
data-testid="code-mode-button" <AlertTriangle size={14} />,
ref={codeRef} "Problems",
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10 hover:bg-[var(--background)]" "problems-mode-button",
onClick={() => selectPanel("code")} displayCount && (
> <span className="ml-0.5 px-1 py-0.5 text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-full min-w-[16px] text-center">
<Code size={14} /> {displayCount}
<span>Code</span> </span>
</button> ),
<button )}
data-testid="configure-mode-button" {renderButton(
ref={configureRef} "code",
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10 hover:bg-[var(--background)]" codeRef,
onClick={() => selectPanel("configure")} <Code size={14} />,
> "Code",
<Wrench size={14} /> "code-mode-button",
<span>Configure</span> )}
</button> {renderButton(
"configure",
configureRef,
<Wrench size={14} />,
"Configure",
"configure-mode-button",
)}
</div>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
data-testid="preview-more-options-button"
className="flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
title="More options"
>
<MoreVertical size={16} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-60">
<DropdownMenuItem onClick={onCleanRestart}>
<Cog size={16} />
<div className="flex flex-col">
<span>Rebuild</span>
<span className="text-xs text-muted-foreground">
Re-installs node_modules and restarts
</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem onClick={onClearSessionData}>
<Trash2 size={16} />
<div className="flex flex-col">
<span>Clear Cache</span>
<span className="text-xs text-muted-foreground">
Clears cookies and local storage and other app cache
</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
<div className="flex items-center"> </TooltipProvider>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
data-testid="preview-more-options-button"
className="flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
title="More options"
>
<MoreVertical size={16} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-60">
<DropdownMenuItem onClick={onCleanRestart}>
<Cog size={16} />
<div className="flex flex-col">
<span>Rebuild</span>
<span className="text-xs text-muted-foreground">
Re-installs node_modules and restarts
</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem onClick={onClearSessionData}>
<Trash2 size={16} />
<div className="flex flex-col">
<span>Clear Cache</span>
<span className="text-xs text-muted-foreground">
Clears cookies and local storage and other app cache
</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
); );
}; };