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

@ -1,6 +0,0 @@
.breadcrumb {
a {
color: var(--mantine-color-default-color);
text-overflow: ellipsis;
}
}

View File

@ -1,133 +0,0 @@
import { useAtomValue } from "jotai";
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
import React, { useEffect, useState } from "react";
import { findBreadcrumbPath } from "@/features/page/tree/utils";
import {
Button,
Anchor,
Popover,
Breadcrumbs,
ActionIcon,
Text,
} from "@mantine/core";
import { IconDots } from "@tabler/icons-react";
import { Link, useParams } from "react-router-dom";
import classes from "./breadcrumb.module.css";
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
import { buildPageSlug } from "@/features/page/page.utils.ts";
import { usePageQuery } from "@/features/page/queries/page-query.ts";
function getTitle(name: string, icon: string) {
if (icon) {
return `${icon} ${name}`;
}
return name;
}
export default function Breadcrumb() {
const treeData = useAtomValue(treeDataAtom);
const [breadcrumbNodes, setBreadcrumbNodes] = useState<
SpaceTreeNode[] | null
>(null);
const { slugId } = useParams();
const { data: currentPage } = usePageQuery(slugId);
useEffect(() => {
if (treeData?.length > 0 && currentPage) {
const breadcrumb = findBreadcrumbPath(treeData, currentPage.id);
if (breadcrumb) {
setBreadcrumbNodes(breadcrumb);
}
}
}, [currentPage?.id, treeData]);
const HiddenNodesTooltipContent = () =>
breadcrumbNodes?.slice(1, -2).map((node) => (
<Button.Group orientation="vertical" key={node.id}>
<Button
justify="start"
component={Link}
to={buildPageSlug(node.slugId, node.name)}
variant="default"
style={{ border: "none" }}
>
<Text truncate="end">{getTitle(node.name, node.icon)}</Text>
</Button>
</Button.Group>
));
const getLastNthNode = (n: number) =>
breadcrumbNodes && breadcrumbNodes[breadcrumbNodes.length - n];
const getBreadcrumbItems = () => {
if (breadcrumbNodes?.length > 3) {
return [
<Anchor
component={Link}
to={buildPageSlug(breadcrumbNodes[0].slugId, breadcrumbNodes[0].name)}
underline="never"
key={breadcrumbNodes[0].slugId}
>
{getTitle(breadcrumbNodes[0].name, breadcrumbNodes[0].icon)}
</Anchor>,
<Popover
width={250}
position="bottom"
withArrow
shadow="xl"
key="hidden-nodes"
>
<Popover.Target>
<ActionIcon c="gray" variant="transparent">
<IconDots size={20} stroke={2} />
</ActionIcon>
</Popover.Target>
<Popover.Dropdown>
<HiddenNodesTooltipContent />
</Popover.Dropdown>
</Popover>,
<Anchor
component={Link}
to={buildPageSlug(getLastNthNode(2)?.slugId, getLastNthNode(2)?.name)}
underline="never"
key={getLastNthNode(2)?.slugId}
>
{getTitle(getLastNthNode(2)?.name, getLastNthNode(2)?.icon)}
</Anchor>,
<Anchor
component={Link}
to={buildPageSlug(getLastNthNode(1)?.slugId, getLastNthNode(1)?.name)}
underline="never"
key={getLastNthNode(1)?.slugId}
>
{getTitle(getLastNthNode(1)?.name, getLastNthNode(1)?.icon)}
</Anchor>,
];
}
if (breadcrumbNodes) {
return breadcrumbNodes.map((node) => (
<Anchor
component={Link}
to={buildPageSlug(node.slugId, node.name)}
underline="never"
key={node.id}
>
{getTitle(node.name, node.icon)}
</Anchor>
));
}
return [];
};
return (
<div className={classes.breadcrumb}>
{breadcrumbNodes ? (
<Breadcrumbs>{getBreadcrumbItems()}</Breadcrumbs>
) : (
<></>
)}
</div>
);
}

View File

@ -1,17 +0,0 @@
import { UserProvider } from "@/features/user/user-provider.tsx";
import Shell from "./shell.tsx";
import { Outlet } from "react-router-dom";
import { Helmet } from "react-helmet-async";
export default function DashboardLayout() {
return (
<UserProvider>
<Shell>
<Helmet>
<title>Home</title>
</Helmet>
<Outlet />
</Shell>
</UserProvider>
);
}

View File

@ -1,100 +0,0 @@
import { ActionIcon, Menu, Tooltip } from "@mantine/core";
import {
IconDots,
IconHistory,
IconLink,
IconMessage,
} from "@tabler/icons-react";
import React from "react";
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
import { useAtom } from "jotai";
import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts";
import { useClipboard } from "@mantine/hooks";
import { useParams } from "react-router-dom";
import { usePageQuery } from "@/features/page/queries/page-query.ts";
import { buildPageSlug } from "@/features/page/page.utils.ts";
import { notifications } from "@mantine/notifications";
export default function Header() {
const toggleAside = useToggleAside();
return (
<>
{/*
<Button variant="default" style={{ border: "none" }} size="compact-sm">
Share
</Button>
*/}
<Tooltip label="Comments" openDelay={250} withArrow>
<ActionIcon
variant="default"
style={{ border: "none" }}
onClick={() => toggleAside("comments")}
>
<IconMessage size={20} stroke={2} />
</ActionIcon>
</Tooltip>
<PageActionMenu />
</>
);
}
function PageActionMenu() {
const [, setHistoryModalOpen] = useAtom(historyAtoms);
const clipboard = useClipboard({ timeout: 500 });
const { slugId } = useParams();
const { data: page, isLoading, isError } = usePageQuery(slugId);
const handleCopyLink = () => {
const pageLink =
window.location.host + buildPageSlug(page.slugId, page.title);
clipboard.copy(pageLink);
notifications.show({ message: "Link copied" });
};
const openHistoryModal = () => {
setHistoryModalOpen(true);
};
return (
<Menu
shadow="xl"
position="bottom-end"
offset={20}
width={200}
withArrow
arrowPosition="center"
>
<Menu.Target>
<ActionIcon variant="default" style={{ border: "none" }}>
<IconDots size={20} stroke={2} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
leftSection={<IconLink size={16} stroke={2} />}
onClick={handleCopyLink}
>
Copy link
</Menu.Item>
<Menu.Divider />
<Menu.Item
leftSection={<IconHistory size={16} stroke={2} />}
onClick={openHistoryModal}
>
Page history
</Menu.Item>
{/*
<Menu.Divider />
<Menu.Item leftSection={<IconTrash size={16} stroke={2} />}>
Delete
</Menu.Item>
*/}
</Menu.Dropdown>
</Menu>
);
}

View File

@ -1,38 +0,0 @@
.header,
.footer {
@media (max-width: 992px) {
[data-layout="alt"] & {
--_section-right: var(--app-shell-aside-offset, 0px);
}
}
}
.aside {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8));
@media (min-width: 993px) {
[data-layout="alt"] & {
--_section-top: var(--_section-top, var(--app-shell-header-offset, 0px));
--_section-height: var(
--_section-height,
calc(
100dvh - var(--app-shell-header-offset, 0px) -
var(--app-shell-footer-offset, 0px)
)
);
}
}
}
@media (max-width: 48em) {
.aside {
width: 350px;
}
}
@media (max-width: 48em) {
.navbar {
width: 300px;
}
}

View File

@ -1,88 +0,0 @@
import {
asideStateAtom,
desktopSidebarAtom,
} from "@/components/navbar/atoms/sidebar-atom.ts";
import { useToggleSidebar } from "@/components/navbar/hooks/use-toggle-sidebar.ts";
import { Navbar } from "@/components/navbar/navbar.tsx";
import { AppShell, Burger, Group } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { useAtom } from "jotai";
import classes from "./shell.module.css";
import Header from "@/components/layouts/dashboard/header.tsx";
import Breadcrumb from "@/components/layouts/components/breadcrumb.tsx";
import Aside from "@/components/layouts/dashboard/aside.tsx";
import { useMatchPath } from "@/hooks/use-match-path.tsx";
import React from "react";
export default function Shell({ children }: { children: React.ReactNode }) {
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] =
useDisclosure();
const [desktopOpened] = useAtom(desktopSidebarAtom);
const toggleDesktop = useToggleSidebar(desktopSidebarAtom);
const matchPath = useMatchPath();
const isPageRoute = matchPath("/p/:pageId");
const [{ isAsideOpen }] = useAtom(asideStateAtom);
return (
<AppShell
layout="alt"
header={{ height: 45 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
}}
aside={{
width: 350,
breakpoint: "sm",
collapsed: { mobile: !isAsideOpen, desktop: !isAsideOpen },
}}
padding="md"
>
<AppShell.Header className={classes.header} withBorder={false}>
<Group justify="space-between" h="100%" px="md" wrap="nowrap">
<Group
h="100%"
maw="60%"
px="md"
wrap="nowrap"
style={{ overflow: "hidden" }}
>
<Burger
opened={mobileOpened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Burger
opened={desktopOpened}
onClick={toggleDesktop}
visibleFrom="sm"
size="sm"
/>
{isPageRoute && <Breadcrumb />}
</Group>
{isPageRoute && (
<Group justify="flex-end" h="100%" px="md" wrap="nowrap">
<Header />
</Group>
)}
</Group>
</AppShell.Header>
<AppShell.Navbar className={classes.navbar} withBorder={false}>
<Navbar />
</AppShell.Navbar>
<AppShell.Main>{children}</AppShell.Main>
{isPageRoute && (
<AppShell.Aside className={classes.aside} withBorder={false}>
<Aside />
</AppShell.Aside>
)}
</AppShell>
);
}

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

@ -1,7 +1,7 @@
import { Box, ScrollArea, Text } from "@mantine/core";
import CommentList from "@/features/comment/components/comment-list.tsx";
import { useAtom } from "jotai";
import { asideStateAtom } from "@/components/navbar/atoms/sidebar-atom.ts";
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import React, { ReactNode } from "react";
export default function Aside() {

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>
);
}

View File

@ -1,17 +0,0 @@
import { UserProvider } from "@/features/user/user-provider.tsx";
import { Outlet } from "react-router-dom";
import SettingsShell from "@/components/layouts/settings/settings-shell.tsx";
import { Helmet } from "react-helmet-async";
export default function SettingsLayout() {
return (
<UserProvider>
<SettingsShell>
<Helmet>
<title>Settings</title>
</Helmet>
<Outlet />
</SettingsShell>
</UserProvider>
);
}

View File

@ -1,36 +0,0 @@
import { desktopSidebarAtom } from "@/components/navbar/atoms/sidebar-atom.ts";
import { AppShell, Container } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { useAtom } from "jotai";
import React from "react";
import SettingsSidebar from "@/components/layouts/settings/settings-sidebar.tsx";
export default function SettingsShell({
children,
}: {
children: React.ReactNode;
}) {
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure();
const [desktopOpened] = useAtom(desktopSidebarAtom);
return (
<AppShell
layout="alt"
header={{ height: 45 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
}}
padding="md"
>
<AppShell.Navbar>
<SettingsSidebar />
</AppShell.Navbar>
<AppShell.Main>
<Container size={800}>{children}</Container>
</AppShell.Main>
</AppShell>
);
}

View File

@ -1,108 +0,0 @@
import React, { useState } from "react";
import { Group, Text, ScrollArea, ActionIcon, rem } from "@mantine/core";
import {
IconUser,
IconSettings,
IconUsers,
IconArrowLeft,
IconUsersGroup,
IconSpaces,
IconBrush,
} from "@tabler/icons-react";
import { Link, useLocation } from "react-router-dom";
import classes from "./settings.module.css";
interface DataItem {
label: string;
icon: React.ElementType;
path: string;
}
interface DataGroup {
heading: string;
items: DataItem[];
}
const groupedData: DataGroup[] = [
{
heading: "Account",
items: [
{ label: "Profile", icon: IconUser, path: "/settings/account/profile" },
{
label: "Preferences",
icon: IconBrush,
path: "/settings/account/preferences",
},
],
},
{
heading: "Workspace",
items: [
{ label: "General", icon: IconSettings, path: "/settings/workspace" },
{
label: "Members",
icon: IconUsers,
path: "/settings/members",
},
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
],
},
];
export default function SettingsSidebar() {
const pathname = useLocation().pathname;
const [active, setActive] = useState(pathname);
const menuItems = groupedData.map((group) => (
<div key={group.heading}>
<Text c="dimmed" className={classes.linkHeader}>
{group.heading}
</Text>
{group.items.map((item) => (
<Link
className={classes.link}
data-active={active.startsWith(item.path) || undefined}
key={item.label}
to={item.path}
onClick={() => {
setActive(item.path);
}}
>
<item.icon className={classes.linkIcon} stroke={2} />
<span>{item.label}</span>
</Link>
))}
</div>
));
return (
<nav className={classes.navbar}>
<div>
<Group className={classes.header} justify="flex-start">
<ActionIcon
component={Link}
to="/home"
variant="transparent"
c="gray"
aria-label="Home"
>
<IconArrowLeft stroke={2} />
</ActionIcon>
<Text fw={500}>Settings</Text>
</Group>
<ScrollArea h="80vh" w="100%">
{menuItems}
</ScrollArea>
</div>
<div className={classes.footer}>
<Link to="/home" className={classes.link}>
<IconArrowLeft className={classes.linkIcon} stroke={1.5} />
<span>Return to the app</span>
</Link>
</div>
</nav>
);
}

View File

@ -1,13 +0,0 @@
import React from 'react';
import { Divider, Title } from '@mantine/core';
export default function SettingsTitle({ title }: { title: string }) {
return (
<>
<Title order={3}>
{title}
</Title>
<Divider my="md" />
</>
);
}

View File

@ -1,72 +0,0 @@
.navbar {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
height: 100%;
width: 100%;
padding: var(--mantine-spacing-md);
display: flex;
flex-direction: column;
/*border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));*/
}
.navbarMain {
flex: 1;
}
.header {
padding-bottom: var(--mantine-spacing-md);
margin-bottom: calc(var(--mantine-spacing-md) * 1.5);
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
.footer {
padding-top: var(--mantine-spacing-md);
margin-top: var(--mantine-spacing-md);
border-top: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
.link {
cursor: pointer;
display: flex;
align-items: center;
text-decoration: none;
font-size: var(--mantine-font-size-sm);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
padding-left: var(--mantine-spacing-xs) ;
min-height: 30px;
border-radius: var(--mantine-radius-sm);
font-weight: 500;
user-select: none;
@mixin hover {
background-color: light-dark(
var(--mantine-color-gray-1),
var(--mantine-color-dark-6)
);
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
}
&[data-active] {
&,
& :hover {
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
}
}
}
.linkIcon {
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
margin-right: var(--mantine-spacing-sm);
width: rem(16px);
height: rem(16px);
}
.linkHeader {
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
font-size: var(--mantine-font-size-sm);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
}