fix goose2 small-window chat and settings layouts (#9019)
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,8 @@
|
|||||||
"core:window:allow-toggle-maximize",
|
"core:window:allow-toggle-maximize",
|
||||||
"core:window:allow-show",
|
"core:window:allow-show",
|
||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-set-min-size",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
{
|
{
|
||||||
"identifier": "opener:allow-open-path",
|
"identifier": "opener:allow-open-path",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-toggle-maximize","core:window:allow-show","core:window:allow-close","opener:default",{"identifier":"opener:allow-open-path","allow":[{"path":"$HOME/**"},{"path":"$HOME/.goose/**"},{"path":"$TEMP/**"},{"path":"/Volumes/**"},{"path":"/mnt/**"},{"path":"/workspace/**"},{"path":"/workspaces/**"},{"path":"/opt/**"},{"path":"/srv/**"},{"path":"*:/**"}]},"window-state:allow-restore-state","window-state:allow-save-window-state","dialog:allow-open","dialog:allow-save","app-test-driver:default","core:webview:allow-set-webview-zoom"]}}
|
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-toggle-maximize","core:window:allow-show","core:window:allow-close","core:window:allow-set-size","core:window:allow-set-min-size","opener:default",{"identifier":"opener:allow-open-path","allow":[{"path":"$HOME/**"},{"path":"$HOME/.goose/**"},{"path":"$TEMP/**"},{"path":"/Volumes/**"},{"path":"/mnt/**"},{"path":"/workspace/**"},{"path":"/workspaces/**"},{"path":"/opt/**"},{"path":"/srv/**"},{"path":"*:/**"}]},"window-state:allow-restore-state","window-state:allow-save-window-state","dialog:allow-open","dialog:allow-save","app-test-driver:default","core:webview:allow-set-webview-zoom"]}}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"title": "Goose",
|
"title": "Goose",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"minWidth": 800,
|
"minWidth": 608,
|
||||||
"minHeight": 600,
|
"minHeight": 600,
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"titleBarStyle": "Overlay",
|
"titleBarStyle": "Overlay",
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ const SIDEBAR_MIN_WIDTH = 180;
|
|||||||
const SIDEBAR_MAX_WIDTH = 380;
|
const SIDEBAR_MAX_WIDTH = 380;
|
||||||
const SIDEBAR_SNAP_COLLAPSE_THRESHOLD = 100;
|
const SIDEBAR_SNAP_COLLAPSE_THRESHOLD = 100;
|
||||||
const SIDEBAR_COLLAPSED_WIDTH = 48;
|
const SIDEBAR_COLLAPSED_WIDTH = 48;
|
||||||
|
const APP_SHELL_HORIZONTAL_CHROME_WIDTH = 28;
|
||||||
|
const MIN_MAIN_CONTENT_WIDTH = 532;
|
||||||
|
const MIN_WINDOW_HEIGHT = 600;
|
||||||
|
const COLLAPSED_WINDOW_MIN_WIDTH =
|
||||||
|
SIDEBAR_COLLAPSED_WIDTH +
|
||||||
|
APP_SHELL_HORIZONTAL_CHROME_WIDTH +
|
||||||
|
MIN_MAIN_CONTENT_WIDTH;
|
||||||
const SETTINGS_SECTIONS = new Set<SectionId>([
|
const SETTINGS_SECTIONS = new Set<SectionId>([
|
||||||
"appearance",
|
"appearance",
|
||||||
"providers",
|
"providers",
|
||||||
@@ -59,6 +66,39 @@ const SETTINGS_SECTIONS = new Set<SectionId>([
|
|||||||
"doctor",
|
"doctor",
|
||||||
"about",
|
"about",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function getExpandedSidebarFitWidth(sidebarWidth: number) {
|
||||||
|
return (
|
||||||
|
sidebarWidth + APP_SHELL_HORIZONTAL_CHROME_WIDTH + MIN_MAIN_CONTENT_WIDTH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureWindowWidth(minWidth: number) {
|
||||||
|
if (!window.__TAURI_INTERNALS__ || window.innerWidth >= minWidth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getCurrentWindow, LogicalSize } = await import(
|
||||||
|
"@tauri-apps/api/window"
|
||||||
|
);
|
||||||
|
await getCurrentWindow().setSize(
|
||||||
|
new LogicalSize(minWidth, window.innerHeight),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncWindowMinimumSize() {
|
||||||
|
if (!window.__TAURI_INTERNALS__) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getCurrentWindow, LogicalSize } = await import(
|
||||||
|
"@tauri-apps/api/window"
|
||||||
|
);
|
||||||
|
await getCurrentWindow().setMinSize(
|
||||||
|
new LogicalSize(COLLAPSED_WINDOW_MIN_WIDTH, MIN_WINDOW_HEIGHT),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AppShell({ children }: { children?: React.ReactNode }) {
|
export function AppShell({ children }: { children?: React.ReactNode }) {
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_DEFAULT_WIDTH);
|
const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_DEFAULT_WIDTH);
|
||||||
@@ -548,7 +588,30 @@ export function AppShell({ children }: { children?: React.ReactNode }) {
|
|||||||
handleNavigate("agents"),
|
handleNavigate("agents"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleSidebar = () => setSidebarCollapsed((prev) => !prev);
|
const collapseSidebar = useCallback(() => {
|
||||||
|
setSidebarCollapsed(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const expandSidebar = useCallback(async () => {
|
||||||
|
const expandedFitWidth = getExpandedSidebarFitWidth(sidebarWidth);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ensureWindowWidth(expandedFitWidth);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to resize window before expanding sidebar:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSidebarCollapsed(false);
|
||||||
|
}, [sidebarWidth]);
|
||||||
|
|
||||||
|
const toggleSidebar = useCallback(() => {
|
||||||
|
if (sidebarCollapsed) {
|
||||||
|
void expandSidebar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collapseSidebar();
|
||||||
|
}, [collapseSidebar, expandSidebar, sidebarCollapsed]);
|
||||||
|
|
||||||
const handleResizeStart = useCallback(
|
const handleResizeStart = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
@@ -595,10 +658,39 @@ export function AppShell({ children }: { children?: React.ReactNode }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleResizeDoubleClick = useCallback(() => {
|
const handleResizeDoubleClick = useCallback(() => {
|
||||||
setSidebarCollapsed(false);
|
|
||||||
setSidebarWidth(SIDEBAR_DEFAULT_WIDTH);
|
setSidebarWidth(SIDEBAR_DEFAULT_WIDTH);
|
||||||
|
void ensureWindowWidth(getExpandedSidebarFitWidth(SIDEBAR_DEFAULT_WIDTH))
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn(
|
||||||
|
"Failed to resize window before resetting sidebar:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => setSidebarCollapsed(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void syncWindowMinimumSize().catch((error) => {
|
||||||
|
console.warn("Failed to update window minimum size:", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sidebarCollapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWindowResize = () => {
|
||||||
|
if (window.innerWidth < getExpandedSidebarFitWidth(sidebarWidth)) {
|
||||||
|
setSidebarCollapsed(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleWindowResize();
|
||||||
|
window.addEventListener("resize", handleWindowResize);
|
||||||
|
return () => window.removeEventListener("resize", handleWindowResize);
|
||||||
|
}, [sidebarCollapsed, sidebarWidth]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: KeyboardEvent) => {
|
const handler = (e: KeyboardEvent) => {
|
||||||
// Cmd+, for settings
|
// Cmd+, for settings
|
||||||
@@ -609,7 +701,7 @@ export function AppShell({ children }: { children?: React.ReactNode }) {
|
|||||||
// Cmd+B for sidebar toggle
|
// Cmd+B for sidebar toggle
|
||||||
if (e.key === "b" && e.metaKey) {
|
if (e.key === "b" && e.metaKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSidebarCollapsed((prev) => !prev);
|
toggleSidebar();
|
||||||
}
|
}
|
||||||
// Cmd+W returns to home instead of closing the window
|
// Cmd+W returns to home instead of closing the window
|
||||||
if (e.key === "w" && e.metaKey) {
|
if (e.key === "w" && e.metaKey) {
|
||||||
@@ -628,7 +720,7 @@ export function AppShell({ children }: { children?: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
window.addEventListener("keydown", handler);
|
window.addEventListener("keydown", handler);
|
||||||
return () => window.removeEventListener("keydown", handler);
|
return () => window.removeEventListener("keydown", handler);
|
||||||
}, [clearActiveSession, sessionStore]);
|
}, [clearActiveSession, sessionStore, toggleSidebar]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen flex-col overflow-hidden bg-background text-foreground">
|
<div className="flex h-screen w-screen flex-col overflow-hidden bg-background text-foreground">
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function AgentModelPicker({
|
|||||||
disabled={loading && !selectedAgentLabel}
|
disabled={loading && !selectedAgentLabel}
|
||||||
leftIcon={getProviderIcon(selectedAgentId, "size-3.5")}
|
leftIcon={getProviderIcon(selectedAgentId, "size-3.5")}
|
||||||
rightIcon={<IconChevronDown className="opacity-50" />}
|
rightIcon={<IconChevronDown className="opacity-50" />}
|
||||||
className="min-w-0"
|
className="min-w-0 max-w-full"
|
||||||
>
|
>
|
||||||
<span className={cn("truncate", isCompact ? "max-w-32" : "max-w-56")}>
|
<span className={cn("truncate", isCompact ? "max-w-32" : "max-w-56")}>
|
||||||
{triggerModelLabel ??
|
{triggerModelLabel ??
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
IconLayoutSidebarRightFilled,
|
IconLayoutSidebarRightFilled,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
|
import { cn } from "@/shared/lib/cn";
|
||||||
import { ContextPanel } from "./ContextPanel";
|
import { ContextPanel } from "./ContextPanel";
|
||||||
|
|
||||||
const CP_PAD = 12;
|
const CP_PAD = 12;
|
||||||
@@ -12,6 +14,7 @@ const CP_TOGGLE_RIGHT = CP_PAD + 12;
|
|||||||
const CP_TOGGLE_TOP = CP_PAD + 10;
|
const CP_TOGGLE_TOP = CP_PAD + 10;
|
||||||
const CP_FADE_S = 0.15;
|
const CP_FADE_S = 0.15;
|
||||||
const CP_REFLOW_MS = 200;
|
const CP_REFLOW_MS = 200;
|
||||||
|
const CP_COMPACT_QUERY = "(max-width: 900px)";
|
||||||
|
|
||||||
interface ChatContextPanelProps {
|
interface ChatContextPanelProps {
|
||||||
activeSessionId: string;
|
activeSessionId: string;
|
||||||
@@ -33,15 +36,33 @@ export function ChatContextPanel({
|
|||||||
setOpen,
|
setOpen,
|
||||||
}: ChatContextPanelProps) {
|
}: ChatContextPanelProps) {
|
||||||
const shouldReduceMotion = useReducedMotion();
|
const shouldReduceMotion = useReducedMotion();
|
||||||
|
const [isCompactViewport, setIsCompactViewport] = useState(false);
|
||||||
const fadeTransition = { duration: shouldReduceMotion ? 0 : CP_FADE_S };
|
const fadeTransition = { duration: shouldReduceMotion ? 0 : CP_FADE_S };
|
||||||
const reflowDuration = shouldReduceMotion ? 0 : CP_REFLOW_MS;
|
const reflowDuration = shouldReduceMotion ? 0 : CP_REFLOW_MS;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!window.matchMedia) return;
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia(CP_COMPACT_QUERY);
|
||||||
|
setIsCompactViewport(mediaQuery.matches);
|
||||||
|
|
||||||
|
const handleChange = (event: MediaQueryListEvent) => {
|
||||||
|
setIsCompactViewport(event.matches);
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="shrink-0 overflow-hidden"
|
className={cn(
|
||||||
|
"shrink-0",
|
||||||
|
isCompactViewport ? "overflow-visible" : "overflow-hidden",
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
width: isOpen ? CP_TOTAL_W : 0,
|
width: isOpen && !isCompactViewport ? CP_TOTAL_W : 0,
|
||||||
transition: `width ${reflowDuration}ms ease`,
|
transition: `width ${reflowDuration}ms ease`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -49,17 +70,31 @@ export function ChatContextPanel({
|
|||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="context-panel"
|
key="context-panel"
|
||||||
className="flex h-full"
|
className={cn(
|
||||||
style={{
|
"flex",
|
||||||
width: CP_TOTAL_W,
|
isCompactViewport
|
||||||
padding: CP_PAD,
|
? "absolute bottom-3 right-3 top-12 z-10 w-[min(340px,calc(100%-1.5rem))]"
|
||||||
}}
|
: "h-full",
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
isCompactViewport
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
width: CP_TOTAL_W,
|
||||||
|
padding: CP_PAD,
|
||||||
|
}
|
||||||
|
}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={fadeTransition}
|
transition={fadeTransition}
|
||||||
>
|
>
|
||||||
<aside className="flex min-w-0 flex-1 overflow-hidden rounded-xl border border-border bg-background">
|
<aside
|
||||||
|
className={cn(
|
||||||
|
"flex min-w-0 flex-1 overflow-hidden rounded-xl border border-border bg-background",
|
||||||
|
isCompactViewport && "shadow-modal",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<ContextPanel
|
<ContextPanel
|
||||||
sessionId={activeSessionId}
|
sessionId={activeSessionId}
|
||||||
projectName={project?.name}
|
projectName={project?.name}
|
||||||
|
|||||||
@@ -370,14 +370,19 @@ export function ChatInput({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={300}>
|
<TooltipProvider delayDuration={300}>
|
||||||
<div className={cn("relative z-10 px-4 pb-6 pt-0", className)}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 px-2 pb-3 pt-0 sm:px-4 sm:pb-6",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="mx-auto max-w-3xl">
|
<div className="mx-auto max-w-3xl">
|
||||||
<Popover open={mentionOpen}>
|
<Popover open={mentionOpen}>
|
||||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: drop zone for file attachments */}
|
{/* biome-ignore lint/a11y/noStaticElementInteractions: drop zone for file attachments */}
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative rounded-2xl border border-border bg-background px-4 pb-3 pt-4 transition-colors",
|
"relative rounded-2xl border border-border bg-background px-3 pb-3 pt-4 transition-colors sm:px-4",
|
||||||
isAttachmentDragOver && "bg-muted/20",
|
isAttachmentDragOver && "bg-muted/20",
|
||||||
)}
|
)}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export function ChatInputSelector({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0",
|
"min-w-0",
|
||||||
triggerVariant === "default" && "justify-between",
|
triggerVariant === "default" && "justify-between",
|
||||||
|
triggerVariant === "toolbar" && "max-w-40",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="truncate">{triggerLabel}</span>
|
<span className="truncate">{triggerLabel}</span>
|
||||||
|
|||||||
@@ -209,9 +209,19 @@ export function ChatInputToolbar({
|
|||||||
}, [isContextPopoverOpen, showContextUsage]);
|
}, [isContextPopoverOpen, showContextUsage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-between gap-2",
|
||||||
|
isCompact && "flex-wrap gap-y-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Left side: pickers */}
|
{/* Left side: pickers */}
|
||||||
<div className="flex items-center gap-0.5">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex min-w-0 items-center gap-0.5",
|
||||||
|
isCompact && "flex-1",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{(agentProviders.length > 0 || providersLoading) && (
|
{(agentProviders.length > 0 || providersLoading) && (
|
||||||
<AgentModelPicker
|
<AgentModelPicker
|
||||||
agents={agentProviders}
|
agents={agentProviders}
|
||||||
@@ -281,7 +291,7 @@ export function ChatInputToolbar({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side: actions */}
|
{/* Right side: actions */}
|
||||||
<div className="flex items-center">
|
<div className={cn("flex shrink-0 items-center", isCompact && "ml-auto")}>
|
||||||
<div className="flex items-center gap-px">
|
<div className="flex items-center gap-px">
|
||||||
{showContextUsage && (
|
{showContextUsage && (
|
||||||
<Popover
|
<Popover
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function SettingsModal({
|
|||||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: click handler only prevents backdrop dismiss propagation */}
|
{/* biome-ignore lint/a11y/noStaticElementInteractions: click handler only prevents backdrop dismiss propagation */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-[min(600px,calc(100vh-4rem))] w-[calc(100vw-2rem)] max-w-3xl overflow-hidden rounded-xl border bg-background shadow-modal transition-opacity duration-300 ease-out",
|
"flex h-[min(600px,calc(100vh-4rem))] w-[calc(100vw-2rem)] max-w-3xl overflow-hidden rounded-xl border bg-background shadow-modal transition-opacity duration-300 ease-out max-[640px]:h-[calc(100vh-1rem)] max-[640px]:w-[calc(100vw-1rem)] max-[640px]:flex-col",
|
||||||
isLoaded ? "opacity-100" : "opacity-0",
|
isLoaded ? "opacity-100" : "opacity-0",
|
||||||
)}
|
)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
@@ -110,13 +110,13 @@ export function SettingsModal({
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-0 w-44 flex-shrink-0 flex-col border-r bg-muted/50 transition-all duration-700 ease-out",
|
"flex min-h-0 w-44 flex-shrink-0 flex-col border-r bg-muted/50 transition-all duration-700 ease-out max-[640px]:max-h-32 max-[640px]:w-full max-[640px]:border-b max-[640px]:border-r-0",
|
||||||
isLoaded ? "opacity-100 translate-x-0" : "opacity-0 -translate-x-2",
|
isLoaded ? "opacity-100 translate-x-0" : "opacity-0 -translate-x-2",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 py-4 transition-all duration-500 ease-out",
|
"px-4 py-4 transition-all duration-500 ease-out max-[640px]:px-3 max-[640px]:py-3",
|
||||||
isLoaded
|
isLoaded
|
||||||
? "opacity-100 translate-x-0"
|
? "opacity-100 translate-x-0"
|
||||||
: "opacity-0 -translate-x-2",
|
: "opacity-0 -translate-x-2",
|
||||||
@@ -124,8 +124,8 @@ export function SettingsModal({
|
|||||||
>
|
>
|
||||||
<h2 className="text-sm font-semibold">{t("title")}</h2>
|
<h2 className="text-sm font-semibold">{t("title")}</h2>
|
||||||
</div>
|
</div>
|
||||||
<nav className="min-h-0 flex-1 overflow-y-auto px-2 pb-3">
|
<nav className="min-h-0 flex-1 overflow-y-auto px-2 pb-3 max-[640px]:overflow-x-auto max-[640px]:overflow-y-hidden max-[640px]:pb-2">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1 max-[640px]:min-w-max max-[640px]:flex-row">
|
||||||
{navItems.map((item, index) => (
|
{navItems.map((item, index) => (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -134,7 +134,7 @@ export function SettingsModal({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => setActiveSection(item.id)}
|
onClick={() => setActiveSection(item.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-start rounded-lg px-3 py-2 transition-all duration-600 ease-out",
|
"w-full justify-start rounded-lg px-3 py-2 transition-all duration-600 ease-out max-[640px]:w-auto max-[640px]:shrink-0 max-[640px]:px-2.5",
|
||||||
activeSection === item.id
|
activeSection === item.id
|
||||||
? "bg-background text-foreground shadow-sm hover:bg-background"
|
? "bg-background text-foreground shadow-sm hover:bg-background"
|
||||||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground duration-300",
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground duration-300",
|
||||||
@@ -162,13 +162,13 @@ export function SettingsModal({
|
|||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label={t("common:actions.close")}
|
aria-label={t("common:actions.close")}
|
||||||
className="absolute right-4 top-4 z-30 rounded-md text-muted-foreground hover:text-foreground"
|
className="absolute right-4 top-4 z-30 rounded-md text-muted-foreground hover:text-foreground max-[640px]:right-3 max-[640px]:top-3"
|
||||||
>
|
>
|
||||||
<X className="size-4" />
|
<X className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
<div className="px-6 pb-4">
|
<div className="px-6 pb-4 max-[640px]:px-4 max-[640px]:pt-1">
|
||||||
{activeSection === "appearance" && <AppearanceSettings />}
|
{activeSection === "appearance" && <AppearanceSettings />}
|
||||||
{activeSection === "providers" && <ProvidersSettings />}
|
{activeSection === "providers" && <ProvidersSettings />}
|
||||||
{activeSection === "compaction" && <CompactionSettings />}
|
{activeSection === "compaction" && <CompactionSettings />}
|
||||||
|
|||||||
Reference in New Issue
Block a user