Files
starter-e2e-mrf90kvm/apps/app/src/components/AppShell.tsx
2026-07-10 20:07:50 +02:00

91 lines
2.7 KiB
TypeScript

import { AppShell as MantineAppShell, Box, Button, NavLink, Stack } from '@pikku/mantine/core'
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
import { useState, type FC } from 'react'
import { m } from '@/i18n/messages'
import { useLocale } from '@/i18n/config'
import { signOut } from '@/lib/auth'
import { Wordmark } from './Wordmark'
import { LanguageSelector } from './LanguageSelector'
import { ThemeSelector } from './ThemeSelector'
const HomeGlyph = () => <NavGlyph d="M3 12 12 4l9 8M5 10v9h5v-6h4v6h5v-9" />
const SignOutGlyph = () => (
<NavGlyph d="M16 17l5-5-5-5M21 12H9M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
)
function NavGlyph({ d }: { d: string }) {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d={d} />
</svg>
)
}
export const AppShell: FC = () => {
useLocale()
const pathname = useRouterState({ select: (s) => s.location.pathname })
const [isSigningOut, setIsSigningOut] = useState(false)
return (
<MantineAppShell navbar={{ width: 236, breakpoint: 'sm' }} padding="xl">
<MantineAppShell.Navbar p="md">
<Stack h="100%" gap={4}>
<Box px="xs" py="sm">
<Wordmark name={m.app__name()} size={26} />
</Box>
<Stack gap={2} mt="xs">
<NavLink
component={Link}
to="/app"
label={m.nav__home()}
leftSection={<HomeGlyph />}
active={pathname === '/app'}
/>
</Stack>
<Stack gap="xs" mt="auto">
<ThemeSelector />
<LanguageSelector />
<Button
variant="subtle"
color="gray"
fullWidth
justify="flex-start"
leftSection={<SignOutGlyph />}
loading={isSigningOut}
onClick={async () => {
setIsSigningOut(true)
try {
await signOut()
window.location.href = '/login'
} finally {
setIsSigningOut(false)
}
}}
>
{m.app_shell__sign_out()}
</Button>
</Stack>
</Stack>
</MantineAppShell.Navbar>
{/* Flex column so a full-height page (e.g. a chat) can fill the remaining
viewport with just `flex: 1, minHeight: 0` — no viewport math needed.
Content-sized pages are unaffected. */}
<MantineAppShell.Main style={{ display: 'flex', flexDirection: 'column' }}>
<Outlet />
</MantineAppShell.Main>
</MantineAppShell>
)
}