feat: update default layout

This commit is contained in:
Sonny
2025-08-06 19:50:53 +02:00
parent d56bd1ef80
commit 97ba56b1e7
36 changed files with 627 additions and 119 deletions

View File

@@ -0,0 +1,36 @@
.navbarWrapper {
z-index: 9;
}
.navbar {
height: rem(60);
background-color: color-mix(
in srgb,
var(--mantine-color-body) 50%,
transparent
);
padding-inline: var(--mantine-spacing-lg);
transition: transform 400ms ease;
backdrop-filter: blur(16px);
overflow: hidden;
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 9;
}
.navbar__content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 100%;
max-width: 100%;
margin-inline: auto;
}
.navbar__content > div:last-child {
flex: 1;
justify-content: flex-end;
}

View File

@@ -0,0 +1,108 @@
import {
PROJECT_EXTENSION_URL,
PROJECT_NAME,
PROJECT_REPO_GITHUB_URL,
} from '#config/project';
import {
Box,
Burger,
Button,
Drawer,
Flex,
Group,
Image,
rem,
useMantineTheme,
} from '@mantine/core';
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { useEffect } from 'react';
import { UserDropdown } from '~/components/common/floating_navbar/user_dropdown';
import { ExternalLinkUnstyled } from '~/components/common/links/external_link_unstyled';
import { InternalLink } from '~/components/common/links/internal_link';
import { useAuth } from '~/hooks/use_auth';
import classes from './floating_navbar.module.css';
interface FloatingNavbarProps {
width: string;
}
export function FloatingNavbar({ width }: FloatingNavbarProps) {
const auth = useAuth();
const theme = useMantineTheme();
const [opened, handler] = useDisclosure(false);
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`, false);
useEffect(() => {
if (opened && !isMobile) {
handler.close();
}
}, [isMobile]);
const links = (
<>
<InternalLink href="/dashboard" style={{ fontSize: rem(16) }}>
Dashboard
</InternalLink>
<ExternalLinkUnstyled
href={PROJECT_REPO_GITHUB_URL}
style={{ fontSize: rem(16) }}
>
Github
</ExternalLinkUnstyled>
<ExternalLinkUnstyled
href={PROJECT_EXTENSION_URL}
style={{ fontSize: rem(16) }}
>
Extension
</ExternalLinkUnstyled>
</>
);
return (
<>
<Box className={classes.navbar}>
<Group className={classes.navbar__content} style={{ width }}>
<Group>
<InternalLink style={{ fontSize: rem(24) }} route="home">
<Image
src="/logo.png"
h={35}
alt="MyLinks's logo"
referrerPolicy="no-referrer"
/>
</InternalLink>
</Group>
<Group>
{!isMobile && <Group>{links}</Group>}
{isMobile && <Burger opened={opened} onClick={handler.toggle} />}
{auth.isAuthenticated && <UserDropdown />}
{!auth.isAuthenticated && (
<Button
variant="default"
component={ExternalLinkUnstyled}
newTab={false}
href="/auth/google"
>
Log in
</Button>
)}
</Group>
</Group>
{/* Mobile drawer */}
<Drawer
opened={opened}
onClose={handler.close}
padding="md"
title={PROJECT_NAME}
zIndex={999999}
onClick={handler.close}
>
<Flex direction="column" gap="md">
{links}
</Flex>
</Drawer>
</Box>
</>
);
}

View File

@@ -0,0 +1,24 @@
.user {
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
border-radius: var(--mantine-radius-sm);
transition: background-color 100ms ease;
&:hover {
background-color: light-dark(
var(--mantine-color-white),
var(--mantine-color-dark-8)
);
}
@media (max-width: 768px) {
display: none;
}
}
.userActive {
background-color: light-dark(
var(--mantine-color-white),
var(--mantine-color-dark-8)
);
}

View File

@@ -0,0 +1,73 @@
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import cx from 'clsx';
import { useTranslation } from 'react-i18next';
import { TbChevronDown, TbLogout, TbUser } from 'react-icons/tb';
import { InternalLink } from '~/components/common/links/internal_link';
import { InternalLinkUnstyled } from '~/components/common/links/internal_link_unstyled';
import { useAuth } from '~/hooks/use_auth';
import classes from './user_dropdown.module.css';
export function UserDropdown() {
const auth = useAuth();
const [userMenuOpened, { open: openUserMenu, close: closeUserMenu }] =
useDisclosure(false);
const { t } = useTranslation();
return (
<Menu
width={260}
position="bottom-end"
transitionProps={{ transition: 'pop-top-right' }}
onClose={closeUserMenu}
onOpen={openUserMenu}
withinPortal
>
<Menu.Target>
<UnstyledButton
className={cx(classes.user, { [classes.userActive]: userMenuOpened })}
>
<Group gap={7}>
<Avatar
src={auth.user?.avatarUrl}
alt={auth.user?.fullname}
radius="xl"
size={20}
/>
<Text fw={500} size="sm" lh={1} mr={3}>
{auth.user?.fullname}
</Text>
<TbChevronDown size={12} />
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>{t('common:user')}</Menu.Label>
<Menu.Item
leftSection={<TbUser size={16} />}
component={InternalLinkUnstyled}
href={`/user/${auth.user?.fullname}`}
color="inherit"
>
{t('common:profile')}
</Menu.Item>
{auth.user?.isAdmin && (
<>
<Menu.Label>{t('common:admin')}</Menu.Label>
<InternalLink href="/admin">{t('common:admin')}</InternalLink>
</>
)}
<Menu.Label>{t('common:settings')}</Menu.Label>
<Menu.Item
leftSection={<TbLogout size={16} />}
component={InternalLinkUnstyled}
href="/auth/logout"
color="inherit"
>
{t('common:logout')}
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}