feat: invert Settings and GlobalWindSettingsModal layout positions, add header theme switcher, and remove version from logo
This commit is contained in:
+100
-49
@@ -13,11 +13,12 @@ import TowerModule from './pages/TowerModule';
|
||||
import {
|
||||
Home, Settings, Menu, Cylinder, Church, CircleDot,
|
||||
Square, Layers, BarChart3, Activity, Warehouse,
|
||||
Settings2, Sun, Moon,
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ThemeProvider } from '@/lib/theme';
|
||||
import { ThemeProvider, useTheme } from '@/lib/theme';
|
||||
import { useI18n } from './store/i18nStore';
|
||||
import { GlobalWindSettingsModal } from './components/GlobalWindSettingsModal';
|
||||
|
||||
@@ -30,14 +31,12 @@ const BridgeIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={props.className}
|
||||
{...props}
|
||||
>
|
||||
<path d="M3 11h18" />
|
||||
<path d="M3 16h18" />
|
||||
<path d="M6 11v5" />
|
||||
<path d="M12 11v5" />
|
||||
<path d="M18 11v5" />
|
||||
<path d="M3 11c3-3 5-3 9-3s6 0 9 3" />
|
||||
<path d="M3 18V6l3-1 6 3 6-3 3 1v12" />
|
||||
<path d="M3 10h18" />
|
||||
<path d="M8 10v8M16 10v8" />
|
||||
<path d="M3 14c4-2 6-2 10 0s6 2 8 0" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -49,15 +48,15 @@ const TowerIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={props.className}
|
||||
{...props}
|
||||
>
|
||||
<path d="M9 22L11 2h2l2 20" />
|
||||
<path d="M10 7h4" />
|
||||
<path d="M9.5 12h5" />
|
||||
<path d="M9 17h6" />
|
||||
<path d="M9 22h6" />
|
||||
<path d="M11 2l4 5M13 2l-4 5" />
|
||||
<path d="M11 7l4 5M13 7l-4 5" />
|
||||
<path d="M6 22L10 2M18 22L14 2" />
|
||||
<path d="M10 2h4" />
|
||||
<path d="M8.5 7h7" />
|
||||
<path d="M7.5 12h9" />
|
||||
<path d="M6.5 17h11" />
|
||||
<path d="M6 22h12" />
|
||||
<path d="M10 2l4 5M12 7l-4 5" />
|
||||
<path d="M10 12l5 5M14 12l-5 5" />
|
||||
<path d="M9.5 17l5.5 5M14.5 17l-5.5 5" />
|
||||
</svg>
|
||||
@@ -67,6 +66,11 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
const location = useLocation();
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const { t } = useI18n();
|
||||
const { setTheme, effectiveTheme } = useTheme();
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(effectiveTheme === 'dark' ? 'light' : 'dark');
|
||||
};
|
||||
|
||||
const navItems = [
|
||||
{ path: '/', icon: <Home className="w-5 h-5" />, labelKey: 'nav_home' as const },
|
||||
@@ -80,7 +84,6 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
{ path: '/pontes', icon: <BridgeIcon className="w-5 h-5" />, labelKey: 'nav_bridge' as const },
|
||||
{ path: '/torre', icon: <TowerIcon className="w-5 h-5" />, labelKey: 'nav_tower' as const },
|
||||
{ path: '/dinamica', icon: <Activity className="w-5 h-5" />, labelKey: 'nav_dynamics' as const },
|
||||
{ path: '/settings', icon: <Settings className="w-5 h-5" />, labelKey: 'nav_settings' as const },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -92,16 +95,27 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
)}
|
||||
>
|
||||
<div className="h-14 flex items-center justify-between px-4 border-b">
|
||||
{!isCollapsed && <span className="font-bold text-primary truncate tracking-tight">{t('app_title')}</span>}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="shrink-0 ml-auto text-muted-foreground hover:text-foreground"
|
||||
title={isCollapsed ? t('nav_expand') : t('nav_collapse')}
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</Button>
|
||||
{!isCollapsed && <span className="font-bold text-primary truncate tracking-tight">BrainWind</span>}
|
||||
<div className="flex items-center gap-1 shrink-0 ml-auto">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
className="h-8 w-8 text-muted-foreground hover:text-foreground shrink-0 rounded-full"
|
||||
title={effectiveTheme === 'dark' ? 'Mudar para tema claro' : 'Mudar para tema escuro'}
|
||||
>
|
||||
{effectiveTheme === 'dark' ? <Sun className="h-4.5 w-4.5" /> : <Moon className="h-4.5 w-4.5" />}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
title={isCollapsed ? t('nav_expand') : t('nav_collapse')}
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto p-3 space-y-1">
|
||||
{navItems.map((item) => {
|
||||
@@ -125,17 +139,60 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
<GlobalWindSettingsModal
|
||||
trigger={
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2 rounded-md transition-colors text-sm w-full text-left cursor-pointer text-muted-foreground hover:bg-secondary/50 hover:text-secondary-foreground',
|
||||
isCollapsed && 'justify-center px-0',
|
||||
)}
|
||||
title={isCollapsed ? 'Parâmetros do Vento' : undefined}
|
||||
>
|
||||
<Settings2 className="w-5 h-5 shrink-0" />
|
||||
{!isCollapsed && <span>Parâmetros do Vento</span>}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</nav>
|
||||
<div className="border-t p-2 flex flex-col gap-2 items-center justify-center">
|
||||
<GlobalWindSettingsModal />
|
||||
<Link
|
||||
to="/settings"
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors text-sm w-full cursor-pointer text-muted-foreground hover:bg-secondary/50 hover:text-secondary-foreground',
|
||||
isCollapsed && 'justify-center px-0',
|
||||
)}
|
||||
title={isCollapsed ? t('nav_settings') : undefined}
|
||||
>
|
||||
<Settings className="w-5 h-5 shrink-0" />
|
||||
{!isCollapsed && <span>{t('nav_settings')}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 flex flex-col h-full overflow-hidden pb-16 md:pb-0">
|
||||
<header className="h-14 border-b bg-card flex items-center justify-between px-4 md:hidden">
|
||||
<span className="font-bold text-primary tracking-tight">{t('app_title')}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<GlobalWindSettingsModal />
|
||||
<span className="font-bold text-primary tracking-tight">BrainWind</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
className="h-8 w-8 text-muted-foreground hover:text-foreground shrink-0 rounded-full"
|
||||
title={effectiveTheme === 'dark' ? 'Tema Claro' : 'Tema Escuro'}
|
||||
>
|
||||
{effectiveTheme === 'dark' ? <Sun className="h-4.5 w-4.5" /> : <Moon className="h-4.5 w-4.5" />}
|
||||
</Button>
|
||||
<Link
|
||||
to="/settings"
|
||||
className={cn(
|
||||
'h-8 w-8 flex items-center justify-center text-muted-foreground hover:text-foreground shrink-0 rounded-full border border-border/40 bg-background/50',
|
||||
location.pathname === '/settings' && 'text-primary border-primary/20 bg-primary/5'
|
||||
)}
|
||||
title={t('nav_settings')}
|
||||
>
|
||||
<Settings className="h-4.5 w-4.5" />
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex-1 overflow-auto">{children}</div>
|
||||
@@ -166,7 +223,7 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
{/* Demais itens (Rolagem horizontal) */}
|
||||
<div className="flex-1 flex items-center h-full overflow-x-auto no-scrollbar py-1 gap-1">
|
||||
{navItems.slice(1, -1).map((item) => {
|
||||
{navItems.slice(1).map((item) => {
|
||||
const isActive = location.pathname === item.path;
|
||||
const label = t(item.labelKey);
|
||||
return (
|
||||
@@ -187,27 +244,21 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Preferências (Fixo na direita) */}
|
||||
{(() => {
|
||||
const settingsItem = navItems[navItems.length - 1];
|
||||
const isActive = location.pathname === settingsItem.path;
|
||||
const label = t(settingsItem.labelKey);
|
||||
return (
|
||||
<Link
|
||||
key={settingsItem.path}
|
||||
to={settingsItem.path}
|
||||
{/* Parâmetros do Vento (Fixo na direita) */}
|
||||
<GlobalWindSettingsModal
|
||||
trigger={
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center h-full px-3 text-xs transition-colors shrink-0 border-l bg-card z-20 shadow-[-4px_0_8px_-4px_rgba(0,0,0,0.15)]',
|
||||
isActive ? 'text-primary font-medium' : 'text-muted-foreground',
|
||||
'flex flex-col items-center justify-center h-full px-3 text-xs transition-colors shrink-0 border-l bg-card z-20 shadow-[-4px_0_8px_-4px_rgba(0,0,0,0.15)] cursor-pointer text-muted-foreground hover:text-primary',
|
||||
)}
|
||||
>
|
||||
<div className={cn('p-1 rounded-full transition-colors', isActive && 'bg-primary/10')}>
|
||||
{settingsItem.icon}
|
||||
<div className="p-1 rounded-full">
|
||||
<Settings2 className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<span className="scale-90 text-[10px]">{label}</span>
|
||||
</Link>
|
||||
);
|
||||
})()}
|
||||
<span className="scale-90 text-[10px] text-primary font-medium">Vento</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,11 @@ import { IsopletasModal } from '@/components/IsopletasModal';
|
||||
import { InfoV0Modal } from '@/components/InfoV0Modal';
|
||||
import { InfoParamModal } from '@/components/InfoParamModal';
|
||||
|
||||
export function GlobalWindSettingsModal() {
|
||||
interface GlobalWindSettingsModalProps {
|
||||
trigger?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProps) {
|
||||
const {
|
||||
v0,
|
||||
s1,
|
||||
@@ -52,10 +56,12 @@ export function GlobalWindSettingsModal() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="gap-2 bg-background shadow-sm hover:bg-muted/50 border-primary/20 text-primary">
|
||||
<Settings2 className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Parâmetros do Vento</span>
|
||||
</Button>
|
||||
{trigger || (
|
||||
<Button variant="outline" size="sm" className="gap-2 bg-background shadow-sm hover:bg-muted/50 border-primary/20 text-primary">
|
||||
<Settings2 className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Parâmetros do Vento</span>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[500px] p-4 sm:p-6">
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user