mirror of
https://github.com/docmost/docmost.git
synced 2025-11-22 16:01:08 +10:00
client: updates
* work on groups ui * move settings to its own page * other fixes and refactoring
This commit is contained in:
@ -0,0 +1,13 @@
|
||||
import { UserProvider } from "@/features/user/user-provider.tsx";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import SettingsShell from "@/components/layouts/settings/settings-shell.tsx";
|
||||
|
||||
export default function SettingsLayout() {
|
||||
return (
|
||||
<UserProvider>
|
||||
<SettingsShell>
|
||||
<Outlet />
|
||||
</SettingsShell>
|
||||
</UserProvider>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
108
apps/client/src/components/layouts/settings/settings-sidebar.tsx
Normal file
108
apps/client/src/components/layouts/settings/settings-sidebar.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useState } from "react";
|
||||
import { Group, Text, ScrollArea, ActionIcon, rem } from "@mantine/core";
|
||||
import {
|
||||
IconFingerprint,
|
||||
IconUser,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
IconArrowLeft,
|
||||
IconUsersGroup,
|
||||
IconSpaces,
|
||||
} from "@tabler/icons-react";
|
||||
import { Link } 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/profile" },
|
||||
{ label: "Preferences", icon: IconUser, path: "/settings/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" },
|
||||
{
|
||||
label: "Security",
|
||||
icon: IconFingerprint,
|
||||
path: "/settings/security",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function SettingsSidebar() {
|
||||
const [active, setActive] = useState("Profile");
|
||||
|
||||
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={item.label === active || undefined}
|
||||
key={item.label}
|
||||
to={item.path}
|
||||
onClick={() => {
|
||||
setActive(item.label);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
.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: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user