refactor layout

* ui polishing
* frontend and backend fixes
This commit is contained in:
Philipinho
2024-05-31 21:51:44 +01:00
parent 046dd6d150
commit 06d854a7d2
95 changed files with 1548 additions and 821 deletions

View File

@ -0,0 +1,23 @@
.header {
height: 100%;
margin-bottom: rem(120px);
background-color: var(--mantine-color-body);
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
padding-left: var(--mantine-spacing-md);
padding-right: var(--mantine-spacing-md);
}
.link {
display: block;
line-height: 1;
padding: rem(8px) rem(12px);
border-radius: var(--mantine-radius-sm);
text-decoration: none;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
font-size: var(--mantine-font-size-sm);
font-weight: 500;
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
}

View File

@ -0,0 +1,70 @@
import { Group } from "@mantine/core";
import { IconSquareLetterDFilled } from "@tabler/icons-react";
import classes from "./app-header.module.css";
import React from "react";
import TopMenu from "@/components/layouts/global/top-menu.tsx";
import { Link } from "react-router-dom";
import APP_ROUTE from "@/lib/app-route.ts";
import { useAtom } from "jotai/index";
import {
desktopSidebarAtom,
mobileSidebarAtom,
} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
import SidebarToggle from "@/components/ui/sidebar-toggle-button.tsx";
const links = [{ link: APP_ROUTE.HOME, label: "Home" }];
export function AppHeader() {
const [mobileOpened] = useAtom(mobileSidebarAtom);
const toggleMobile = useToggleSidebar(mobileSidebarAtom);
const [desktopOpened] = useAtom(desktopSidebarAtom);
const toggleDesktop = useToggleSidebar(desktopSidebarAtom);
const isHomeRoute = location.pathname.startsWith("/home");
const items = links.map((link) => (
<Link key={link.label} to={link.link} className={classes.link}>
{link.label}
</Link>
));
return (
<>
<Group h="100%" px="md" justify="space-between" wrap={"nowrap"}>
<Group>
<IconSquareLetterDFilled size={30} />
{!isHomeRoute && (
<>
<SidebarToggle
aria-label="sidebar toggle"
opened={mobileOpened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<SidebarToggle
aria-label="sidebar toggle"
opened={desktopOpened}
onClick={toggleDesktop}
visibleFrom="sm"
size="sm"
/>
</>
)}
<Group ml={50} gap={5} className={classes.links} visibleFrom="sm">
{items}
</Group>
</Group>
<Group px={"xl"}>
<TopMenu />
</Group>
</Group>
</>
);
}

View File

@ -0,0 +1,16 @@
.header, .navbar, .aside {
background-color: light-dark(#f6f7f9, var(--mantine-color-dark-8));
}
.navbar, .aside {
@media (max-width: $mantine-breakpoint-sm) {
width: 350px;
}
}
.aside {
@media (max-width: $mantine-breakpoint-sm) {
margin-top: 45px;
}
}

View File

@ -0,0 +1,42 @@
import { Box, ScrollArea, Text } from "@mantine/core";
import CommentList from "@/features/comment/components/comment-list.tsx";
import { useAtom } from "jotai";
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import React, { ReactNode } from "react";
export default function Aside() {
const [{ tab }] = useAtom(asideStateAtom);
let title: string;
let component: ReactNode;
switch (tab) {
case "comments":
component = <CommentList />;
title = "Comments";
break;
default:
component = null;
title = null;
}
return (
<Box p="md">
{component && (
<>
<Text mb="md" fw={500}>
{title}
</Text>
<ScrollArea
style={{ height: "85vh" }}
scrollbarSize={5}
type="scroll"
>
<div style={{ paddingBottom: "200px" }}>{component}</div>
</ScrollArea>
</>
)}
</Box>
);
}

View File

@ -0,0 +1,77 @@
import { AppShell, Container } from "@mantine/core";
import React from "react";
import { useLocation } from "react-router-dom";
import SettingsSidebar from "@/components/settings/settings-sidebar.tsx";
import { useAtom } from "jotai";
import {
asideStateAtom,
desktopSidebarAtom,
mobileSidebarAtom,
} from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import { SpaceSidebar } from "@/features/space/components/sidebar/space-sidebar.tsx";
import { AppHeader } from "@/components/layouts/global/app-header.tsx";
import Aside from "@/components/layouts/global/aside.tsx";
import classes from "./app-shell.module.css";
export default function GlobalAppShell({
children,
}: {
children: React.ReactNode;
}) {
const [mobileOpened] = useAtom(mobileSidebarAtom);
const [desktopOpened] = useAtom(desktopSidebarAtom);
const [{ isAsideOpen }] = useAtom(asideStateAtom);
const location = useLocation();
const isSettingsRoute = location.pathname.startsWith("/settings");
const isSpaceRoute = location.pathname.startsWith("/s/");
const isHomeRoute = location.pathname.startsWith("/home");
const isPageRoute = location.pathname.includes("/p/");
return (
<AppShell
header={{ height: 45 }}
navbar={
!isHomeRoute && {
width: 300,
breakpoint: "sm",
collapsed: {
mobile: !mobileOpened,
desktop: !desktopOpened,
},
}
}
aside={
isPageRoute && {
width: 350,
breakpoint: "sm",
collapsed: { mobile: !isAsideOpen, desktop: !isAsideOpen },
}
}
padding="md"
>
<AppShell.Header px="md" className={classes.header}>
<AppHeader />
</AppShell.Header>
{!isHomeRoute && (
<AppShell.Navbar className={classes.navbar} withBorder={false}>
{isSpaceRoute && <SpaceSidebar />}
{isSettingsRoute && <SettingsSidebar />}
</AppShell.Navbar>
)}
<AppShell.Main>
{isSettingsRoute ? (
<Container size={800}>{children}</Container>
) : (
children
)}
</AppShell.Main>
{isPageRoute && (
<AppShell.Aside className={classes.aside} p="md" withBorder={false}>
<Aside />
</AppShell.Aside>
)}
</AppShell>
);
}

View File

@ -0,0 +1,21 @@
import { atomWithWebStorage } from "@/lib/jotai-helper.ts";
import { atom } from "jotai";
export const mobileSidebarAtom = atom<boolean>(false);
export const desktopSidebarAtom = atomWithWebStorage<boolean>(
"showSidebar",
true,
);
export const desktopAsideAtom = atom<boolean>(false);
type AsideStateType = {
tab: string;
isAsideOpen: boolean;
};
export const asideStateAtom = atom<AsideStateType>({
tab: "",
isAsideOpen: false,
});

View File

@ -0,0 +1,8 @@
import { useAtom } from "jotai";
export function useToggleSidebar(sidebarAtom: any) {
const [sidebarState, setSidebarState] = useAtom(sidebarAtom);
return () => {
setSidebarState(!sidebarState);
}
}

View File

@ -0,0 +1,13 @@
import { UserProvider } from "@/features/user/user-provider.tsx";
import { Outlet } from "react-router-dom";
import GlobalAppShell from "@/components/layouts/global/global-app-shell.tsx";
export default function Layout() {
return (
<UserProvider>
<GlobalAppShell>
<Outlet />
</GlobalAppShell>
</UserProvider>
);
}

View File

@ -0,0 +1,124 @@
import { Avatar, Group, Menu, rem, UnstyledButton, Text } from "@mantine/core";
import {
IconChevronDown,
IconLogout,
IconSettings,
IconUserCircle,
IconUsers,
} from "@tabler/icons-react";
import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
import { Link } from "react-router-dom";
import APP_ROUTE from "@/lib/app-route.ts";
import useAuth from "@/features/auth/hooks/use-auth.ts";
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
export default function TopMenu() {
const [currentUser] = useAtom(currentUserAtom);
const { logout } = useAuth();
const user = currentUser?.user;
const workspace = currentUser?.workspace;
return (
<Menu width={250} position="bottom-end" withArrow shadow={"lg"}>
<Menu.Target>
<UnstyledButton>
<Group gap={7} wrap={"nowrap"}>
<Avatar
src={workspace.logo}
alt={workspace.name}
radius="xl"
size={20}
/>
<Text fw={500} size="sm" lh={1} mr={3}>
{workspace.name}
</Text>
<IconChevronDown
style={{ width: rem(12), height: rem(12) }}
stroke={1.5}
/>
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Workspace</Menu.Label>
<Menu.Item
component={Link}
to={APP_ROUTE.SETTINGS.WORKSPACE.GENERAL}
leftSection={
<IconSettings
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
>
Workspace settings
</Menu.Item>
<Menu.Item
component={Link}
to={APP_ROUTE.SETTINGS.WORKSPACE.MEMBERS}
leftSection={
<IconUsers
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
>
Manage members
</Menu.Item>
<Menu.Divider />
<Menu.Label>Account</Menu.Label>
<Menu.Item component={Link} to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE}>
<Group wrap={"nowrap"}>
<UserAvatar
radius="xl"
size={"sm"}
avatarUrl={user.avatarUrl}
name={user.name}
/>
<div>
<Text size="sm" fw={500} lineClamp={1}>
{user.name}
</Text>
<Text size="xs" c="dimmed">
{user.email}
</Text>
</div>
</Group>
</Menu.Item>
<Menu.Item
component={Link}
to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE}
leftSection={
<IconUserCircle
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
>
My profile
</Menu.Item>
<Menu.Divider />
<Menu.Item
onClick={logout}
leftSection={
<IconLogout
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
>
Logout
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}