Make space for windows controls (#635)
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user