Added base appshell

This commit is contained in:
Sianida26 2024-01-22 00:35:43 +07:00
parent 1cbb80ede9
commit 52b0f09440
16 changed files with 311 additions and 97 deletions

View File

@ -1,47 +1,49 @@
{
"name": "dashboard-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth/prisma-adapter": "^1.0.14",
"@mantine/core": "^7.4.0",
"@mantine/form": "^7.4.0",
"@mantine/hooks": "^7.4.0",
"@prisma/client": "5.7.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.36.1",
"@trpc/client": "^10.45.0",
"@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.45.0",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.5",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"next": "14.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.10.6",
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"autoprefixer": "^10.4.16",
"eslint": "^8.56.0",
"eslint-config-next": "14.0.4",
"postcss": "^8.4.33",
"postcss-preset-mantine": "^1.12.3",
"postcss-simple-vars": "^7.0.1",
"prisma": "^5.7.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
}
}
"name": "dashboard-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth/prisma-adapter": "^1.0.14",
"@mantine/core": "^7.4.0",
"@mantine/form": "^7.4.0",
"@mantine/hooks": "^7.4.0",
"@prisma/client": "5.7.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.36.1",
"@trpc/client": "^10.45.0",
"@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.45.0",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.5",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"next": "14.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"sass": "^1.70.0",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.10.6",
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"autoprefixer": "^10.4.16",
"eslint": "^8.56.0",
"eslint-config-next": "14.0.4",
"postcss": "^8.4.33",
"postcss-preset-mantine": "^1.12.3",
"postcss-simple-vars": "^7.0.1",
"prisma": "^5.7.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
}
}

View File

@ -52,13 +52,19 @@ dependencies:
version: 9.0.2
next:
specifier: 14.0.4
version: 14.0.4(react-dom@18.2.0)(react@18.2.0)
version: 14.0.4(react-dom@18.2.0)(react@18.2.0)(sass@1.70.0)
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-icons:
specifier: ^5.0.1
version: 5.0.1(react@18.2.0)
sass:
specifier: ^1.70.0
version: 1.70.0
superjson:
specifier: ^2.2.1
version: 2.2.1
@ -594,7 +600,7 @@ packages:
'@trpc/client': 10.45.0(@trpc/server@10.45.0)
'@trpc/react-query': 10.45.0(@tanstack/react-query@4.36.1)(@trpc/client@10.45.0)(@trpc/server@10.45.0)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server': 10.45.0
next: 14.0.4(react-dom@18.2.0)(react@18.2.0)
next: 14.0.4(react-dom@18.2.0)(react@18.2.0)(sass@1.70.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@ -798,7 +804,6 @@ packages:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
@ -970,7 +975,6 @@ packages:
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@ -989,7 +993,6 @@ packages:
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/browserslist@4.22.2:
resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
@ -1055,7 +1058,6 @@ packages:
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
dev: true
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
@ -1666,7 +1668,6 @@ packages:
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
@ -1722,7 +1723,6 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind@1.1.2:
@ -1791,7 +1791,6 @@ packages:
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
@ -1934,6 +1933,10 @@ packages:
engines: {node: '>= 4'}
dev: true
/immutable@4.3.4:
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
dev: false
/import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@ -1997,7 +2000,6 @@ packages:
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
@ -2028,7 +2030,6 @@ packages:
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-finalizationregistry@1.0.2:
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
@ -2052,7 +2053,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-map@2.0.2:
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
@ -2073,7 +2073,6 @@ packages:
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
@ -2437,7 +2436,7 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
/next@14.0.4(react-dom@18.2.0)(react@18.2.0):
/next@14.0.4(react-dom@18.2.0)(react@18.2.0)(sass@1.70.0):
resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==}
engines: {node: '>=18.17.0'}
hasBin: true
@ -2460,6 +2459,7 @@ packages:
postcss: 8.4.31
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
sass: 1.70.0
styled-jsx: 5.1.1(react@18.2.0)
watchpack: 2.4.0
optionalDependencies:
@ -2508,7 +2508,6 @@ packages:
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/normalize-range@0.1.2:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
@ -2674,7 +2673,6 @@ packages:
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
@ -2853,6 +2851,14 @@ packages:
scheduler: 0.23.0
dev: false
/react-icons@5.0.1(react@18.2.0):
resolution: {integrity: sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==}
peerDependencies:
react: '*'
dependencies:
react: 18.2.0
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@ -2960,7 +2966,6 @@ packages:
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/reflect.getprototypeof@1.0.4:
resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==}
@ -3056,6 +3061,16 @@ packages:
is-regex: 1.1.4
dev: true
/sass@1.70.0:
resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
chokidar: 3.5.3
immutable: 4.3.4
source-map-js: 1.0.2
dev: false
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@ -3364,7 +3379,6 @@ packages:
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}

View File

@ -1,8 +0,0 @@
import { AppShell } from '@mantine/core'
import React from 'react'
export default function AppNavbar() {
return (
<AppShell.Navbar p="md">a</AppShell.Navbar>
)
}

View File

@ -1,33 +1,21 @@
"use client"
import { AppShell, AppShellHeader, Burger } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import Image from 'next/image'
import React from 'react'
import logo from "@/assets/logos/logo.png"
import AppHeader from '../components/AppHeader'
import AppNavbar from '../components/AppNavbar'
import AppHeader from '../../components/AppHeader'
import AppNavbar from '../../components/AppNavbar'
import DashboardLayout from '@/components/DashboardLayout'
interface Props {
children: React.ReactNode
}
export default function layout(props: Props) {
const [openNavbar, { toggle }] = useDisclosure(false)
export default function Layout(props: Props) {
return (
<AppShell
padding="md"
header={{ height: 60 }}
navbar={{ width: 300, breakpoint: 'sm', collapsed: { mobile: !openNavbar } }}
>
{/* Header */}
<AppHeader />
{/* Navbar */}
<AppNavbar />
<AppShell.Main>{props.children}</AppShell.Main>
</AppShell>
<DashboardLayout>
{props.children}
</DashboardLayout>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -3,22 +3,25 @@ import React from 'react'
import { AppShell, Burger, Group } from "@mantine/core"
import { useDisclosure } from '@mantine/hooks'
import Image from 'next/image'
import logo from "@/assets/logos/logo.png"
import logo from "@/assets/logos/logo-dsg.png"
export default function AppHeader() {
interface Props {
openNavbar: boolean,
toggle: () => void
}
const [openNavbar, { toggle }] = useDisclosure()
export default function AppHeader(props: Props) {
return (
<AppShell.Header>
<Group h="100%" px="md">
<Burger
opened={openNavbar}
onClick={toggle}
opened={props.openNavbar}
onClick={props.toggle}
hiddenFrom="sm"
size="sm"
/>
<Image src={logo} alt='' height={30} />
<Image src={logo} alt='' height={57} />
</Group>
</AppShell.Header>
)

View File

@ -0,0 +1,19 @@
import { AppShell, ScrollArea } from '@mantine/core'
import React from 'react'
import allMenu from './_data/allMenu'
import MenuItem from './_components/MenuItem'
export default function AppNavbar() {
const menus = allMenu.map((menu, i) => <MenuItem menu={menu} key={i} />)
return (
<AppShell.Navbar p="md">
<ScrollArea style={{flex: "1"}}>
<div>
{menus}
</div>
</ScrollArea>
</AppShell.Navbar>
)
}

View File

@ -0,0 +1,23 @@
import { Text } from "@mantine/core";
import React from "react";
import classNames from "./childMenu.module.css";
import { MenuItem } from "../_data/allMenu";
import { isNotEmpty } from "@mantine/form";
interface Props {
item: NonNullable<MenuItem["children"]>[number];
}
export default function ChildMenu(props: Props) {
return (
<Text<"a">
component="a"
className={classNames.link}
href={props.item.link}
onClick={(e) => e.preventDefault()}
>
{props.item.label}
</Text>
);
}

View File

@ -0,0 +1,71 @@
import React, { useState } from "react";
import {
Box,
Collapse,
Group,
ThemeIcon,
UnstyledButton,
rem,
} from "@mantine/core";
import { MenuItem } from "../_data/allMenu";
import { TbChevronRight } from "react-icons/tb";
import classNames from "./menuItem.module.css";
import ChildMenu from "./ChildMenu";
interface Props {
menu: MenuItem;
}
export default function MenuItem({ menu }: Props) {
const hasChildren = Array.isArray(menu.children);
const [opened, setOpened] = useState(false);
const toggleOpenMenu = () => {
setOpened((prev) => !prev);
};
const subItems = (hasChildren ? menu.children! : []).map((child, i) => (
<ChildMenu key={i} item={child} />
));
return (
<>
{/* Main Section */}
<UnstyledButton
onClick={toggleOpenMenu}
className={classNames.control}
>
<Group justify="space-between" gap={0}>
{/* Left Section */}
<Box style={{ display: "flex", alignItems: "center" }}>
{/* Icon */}
<ThemeIcon variant="light" size={30} color={menu.color}>
<menu.icon
style={{ width: rem(18), height: rem(18) }}
/>
</ThemeIcon>
{/* Label */}
<Box ml="md">{menu.label}</Box>
</Box>
{/* Right Section (Chevron if available) */}
{hasChildren && (
<TbChevronRight
// stroke="1.5"
style={{
width: rem(16),
height: rem(16),
transform: opened ? "rotate(-90deg)" : "rotate(90deg)",
}}
className={classNames.chevron}
/>
)}
</Group>
</UnstyledButton>
{hasChildren ? <Collapse in={opened}>{subItems}</Collapse> : null}
</>
);
}

View File

@ -0,0 +1,16 @@
.link {
font-weight: 500;
display: block;
text-decoration: none;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
padding-left: var(--mantine-spacing-md);
margin-left: var(--mantine-spacing-xl);
font-size: var(--mantine-font-size-sm);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
border-left: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
}
}

View File

@ -0,0 +1,17 @@
.control {
font-weight: 500;
display: block;
width: 100%;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
color: var(--mantine-color-text);
font-size: var(--mantine-font-size-sm);
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
}
}
.chevron {
transition: transform 200ms ease;
}

View File

@ -0,0 +1,31 @@
import React from "react";
import { TbLayoutDashboard, TbUsers } from "react-icons/tb";
export interface MenuItem {
label: string,
icon: React.FC<any>,
children?: {
label: string,
link: string,
}[],
color?: string,
}
const allMenu: MenuItem[] = [
{
label: "Dashboard",
icon: TbLayoutDashboard,
},
{
label: "Users",
icon: TbUsers,
color: "green",
children: [
{ label: "Users", link: "#"},
{ label: "Roles", link: "#"},
{ label: "Permissions", link: "#"},
]
},
];
export default allMenu;

View File

@ -0,0 +1,35 @@
"use client";
import React from "react";
import { AppShell, AppShellHeader, Burger } from "@mantine/core";
import AppHeader from "../AppHeader";
import AppNavbar from "../AppNavbar";
import { useDisclosure } from "@mantine/hooks";
interface Props {
children: React.ReactNode;
}
export default function DashboardLayout(props: Props) {
const [openNavbar, { toggle }] = useDisclosure(false);
return (
<AppShell
padding="md"
header={{ height: 70 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !openNavbar },
}}
>
{/* Header */}
<AppHeader openNavbar={openNavbar} toggle={toggle} />
{/* Navbar */}
<AppNavbar />
<AppShell.Main>{props.children}</AppShell.Main>
</AppShell>
);
}

View File

@ -0,0 +1,3 @@
import DashboardLayout from "./DashboardLayout";
export default DashboardLayout;