From ff89bcba4b04bed5365e28db17d0b4f2f920daaa Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 12 Aug 2023 12:25:44 +0200 Subject: [PATCH] add: responsive layout for sidebar and dashboard --- pkgs/ui/README.md | 8 + pkgs/ui/pconf.cjs | 4 + pkgs/ui/src/app/layout.tsx | 57 ++++- pkgs/ui/src/app/nodes/NodeList.tsx | 295 ++++++++++++++--------- pkgs/ui/src/app/nodes/NodePieChart.tsx | 83 ++++--- pkgs/ui/src/app/nodes/page.tsx | 122 +++++++--- pkgs/ui/src/app/page.tsx | 61 ++++- pkgs/ui/src/app/theme/themes.ts | 3 - pkgs/ui/src/components/sidebar/index.tsx | 61 +++-- 9 files changed, 472 insertions(+), 222 deletions(-) create mode 100644 pkgs/ui/pconf.cjs diff --git a/pkgs/ui/README.md b/pkgs/ui/README.md index 9d600bd0..93a122f0 100644 --- a/pkgs/ui/README.md +++ b/pkgs/ui/README.md @@ -10,3 +10,11 @@ Update floco dependencies: `nix run github:aakropotkin/floco -- translate -pt -o ./nix/pdefs.nix` + +The prettier tailwind class sorting is not yet working properly with our devShell integration. + +To sort classnames manually: + +`cd /clan-core/pkgs/ui/` + +`prettier -w ./src/ --config pconf.cjs` diff --git a/pkgs/ui/pconf.cjs b/pkgs/ui/pconf.cjs new file mode 100644 index 00000000..fa7765f5 --- /dev/null +++ b/pkgs/ui/pconf.cjs @@ -0,0 +1,4 @@ +// prettier.config.js +module.exports = { + plugins: ["prettier-plugin-tailwindcss"], +}; diff --git a/pkgs/ui/src/app/layout.tsx b/pkgs/ui/src/app/layout.tsx index 49106047..9cc1ef4a 100644 --- a/pkgs/ui/src/app/layout.tsx +++ b/pkgs/ui/src/app/layout.tsx @@ -3,13 +3,21 @@ import "./globals.css"; import localFont from "next/font/local"; import * as React from "react"; -import { CssBaseline, ThemeProvider } from "@mui/material"; +import { + CssBaseline, + IconButton, + ThemeProvider, + useMediaQuery, +} from "@mui/material"; import { ChangeEvent, useState } from "react"; import { StyledEngineProvider } from "@mui/material/styles"; import { darkTheme, lightTheme } from "./theme/themes"; import { Sidebar } from "@/components/sidebar"; +import MenuIcon from "@mui/icons-material/Menu"; +import { ChevronLeft } from "@mui/icons-material"; +import Image from "next/image"; const roboto = localFont({ src: [ @@ -26,12 +34,18 @@ export default function RootLayout({ }: { children: React.ReactNode; }) { + const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)"); let [useDarkTheme, setUseDarkTheme] = useState(false); - let [theme, setTheme] = useState(useDarkTheme ? darkTheme : lightTheme); + let [showSidebar, setShowSidebar] = useState(true); + React.useEffect(() => { + if (useDarkTheme !== userPrefersDarkmode) { + // Enable dark theme if the user prefers dark mode + setUseDarkTheme(userPrefersDarkmode); + } + }, [userPrefersDarkmode, useDarkTheme, setUseDarkTheme]); const changeThemeHandler = (target: ChangeEvent, currentValue: boolean) => { setUseDarkTheme(currentValue); - setTheme(currentValue ? darkTheme : lightTheme); }; return ( @@ -43,13 +57,42 @@ export default function RootLayout({ - +
- -
-
{children}
+ setShowSidebar(false)} + /> +
+
+
+
+ +
+
+ Clan Logo +
+
+
+ +
+
+
{children}
+
+
diff --git a/pkgs/ui/src/app/nodes/NodeList.tsx b/pkgs/ui/src/app/nodes/NodeList.tsx index cd82dced..e52a4cc7 100644 --- a/pkgs/ui/src/app/nodes/NodeList.tsx +++ b/pkgs/ui/src/app/nodes/NodeList.tsx @@ -1,38 +1,43 @@ -"use client" - -import * as React from 'react'; -import { alpha } from '@mui/material/styles'; -import Box from '@mui/material/Box'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TablePagination from '@mui/material/TablePagination'; -import TableRow from '@mui/material/TableRow'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; -import Paper from '@mui/material/Paper'; -import Checkbox from '@mui/material/Checkbox'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Switch from '@mui/material/Switch'; -import DeleteIcon from '@mui/icons-material/Delete'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import { visuallyHidden } from '@mui/utils'; -import CircleIcon from '@mui/icons-material/Circle'; -import Stack from '@mui/material/Stack/Stack'; -import ModeIcon from '@mui/icons-material/Mode'; -import ClearIcon from '@mui/icons-material/Clear'; -import Fade from '@mui/material/Fade/Fade'; -import NodePieChart, { PieData } from './NodePieChart'; -import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2 -import { Card, CardContent, Container, FormGroup, useTheme } from '@mui/material'; -import hexRgb from 'hex-rgb'; -import useMediaQuery from '@mui/material/useMediaQuery'; +"use client"; +import * as React from "react"; +import { alpha } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import TableSortLabel from "@mui/material/TableSortLabel"; +import Toolbar from "@mui/material/Toolbar"; +import Typography from "@mui/material/Typography"; +import Paper from "@mui/material/Paper"; +import Checkbox from "@mui/material/Checkbox"; +import IconButton from "@mui/material/IconButton"; +import Tooltip from "@mui/material/Tooltip"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import DeleteIcon from "@mui/icons-material/Delete"; +import FilterListIcon from "@mui/icons-material/FilterList"; +import { visuallyHidden } from "@mui/utils"; +import CircleIcon from "@mui/icons-material/Circle"; +import Stack from "@mui/material/Stack/Stack"; +import ModeIcon from "@mui/icons-material/Mode"; +import ClearIcon from "@mui/icons-material/Clear"; +import Fade from "@mui/material/Fade/Fade"; +import NodePieChart, { PieData } from "./NodePieChart"; +import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2 +import { + Card, + CardContent, + Container, + FormGroup, + useTheme, +} from "@mui/material"; +import hexRgb from "hex-rgb"; +import useMediaQuery from "@mui/material/useMediaQuery"; export interface TableData { name: string; @@ -47,7 +52,6 @@ export enum NodeStatus { Pending, } - interface HeadCell { disablePadding: boolean; id: keyof TableData; @@ -57,22 +61,22 @@ interface HeadCell { const headCells: readonly HeadCell[] = [ { - id: 'name', + id: "name", alignRight: false, disablePadding: false, - label: 'DISPLAY NAME & ID', + label: "DISPLAY NAME & ID", }, { - id: 'status', + id: "status", alignRight: false, disablePadding: false, - label: 'STATUS', + label: "STATUS", }, { - id: 'last_seen', + id: "last_seen", alignRight: false, disablePadding: false, - label: 'LAST SEEN', + label: "LAST SEEN", }, ]; @@ -86,7 +90,7 @@ function descendingComparator(a: T, b: T, orderBy: keyof T) { return 0; } -type Order = 'asc' | 'desc'; +type Order = "asc" | "desc"; function getComparator( order: Order, @@ -95,7 +99,7 @@ function getComparator( a: { [key in Key]: number | string | boolean }, b: { [key in Key]: number | string | boolean }, ) => number { - return order === 'desc' + return order === "desc" ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); } @@ -104,7 +108,10 @@ function getComparator( // stableSort() brings sort stability to non-modern browsers (notably IE11). If you // only support modern browsers you can replace stableSort(exampleArray, exampleComparator) // with exampleArray.slice().sort(exampleComparator) -function stableSort(array: readonly T[], comparator: (a: T, b: T) => number) { +function stableSort( + array: readonly T[], + comparator: (a: T, b: T) => number, +) { const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); stabilizedThis.sort((a, b) => { const order = comparator(a[0], b[0]); @@ -116,18 +123,18 @@ function stableSort(array: readonly T[], comparator: (a: T, b: T) => number) return stabilizedThis.map((el) => el[0]); } - - interface EnhancedTableProps { - onRequestSort: (event: React.MouseEvent, property: keyof TableData) => void; + onRequestSort: ( + event: React.MouseEvent, + property: keyof TableData, + ) => void; order: Order; orderBy: string; rowCount: number; } function EnhancedTableHead(props: EnhancedTableProps) { - const { order, orderBy, onRequestSort } = - props; + const { order, orderBy, onRequestSort } = props; const createSortHandler = (property: keyof TableData) => (event: React.MouseEvent) => { onRequestSort(event, property); @@ -139,19 +146,19 @@ function EnhancedTableHead(props: EnhancedTableProps) { {headCells.map((headCell) => ( {headCell.label} {orderBy === headCell.id ? ( - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} + {order === "desc" ? "sorted descending" : "sorted ascending"} ) : null} @@ -162,8 +169,6 @@ function EnhancedTableHead(props: EnhancedTableProps) { ); } - - interface EnhancedTableToolbarProps { selected: string | undefined; tableData: TableData[]; @@ -172,41 +177,49 @@ interface EnhancedTableToolbarProps { function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { const { selected, onClear, tableData } = props; const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.down('lg')); + const matches = useMediaQuery(theme.breakpoints.down("lg")); const isSelected = selected != undefined; const [debug, setDebug] = React.useState(false); - const debugSx = debug ? { - '--Grid-borderWidth': '1px', - borderTop: 'var(--Grid-borderWidth) solid', - borderLeft: 'var(--Grid-borderWidth) solid', - borderColor: 'divider', - '& > div': { - borderRight: 'var(--Grid-borderWidth) solid', - borderBottom: 'var(--Grid-borderWidth) solid', - borderColor: 'divider', - } - } : {}; + const debugSx = debug + ? { + "--Grid-borderWidth": "1px", + borderTop: "var(--Grid-borderWidth) solid", + borderLeft: "var(--Grid-borderWidth) solid", + borderColor: "divider", + "& > div": { + borderRight: "var(--Grid-borderWidth) solid", + borderBottom: "var(--Grid-borderWidth) solid", + borderColor: "divider", + }, + } + : {}; const pieData = React.useMemo(() => { - const online = tableData.filter((row) => row.status === NodeStatus.Online).length; - const offline = tableData.filter((row) => row.status === NodeStatus.Offline).length; - const pending = tableData.filter((row) => row.status === NodeStatus.Pending).length; + const online = tableData.filter( + (row) => row.status === NodeStatus.Online, + ).length; + const offline = tableData.filter( + (row) => row.status === NodeStatus.Offline, + ).length; + const pending = tableData.filter( + (row) => row.status === NodeStatus.Pending, + ).length; return [ - { name: 'Online', value: online, color: '#2E7D32' }, - { name: 'Offline', value: offline, color: '#db3927' }, - { name: 'Pending', value: pending, color: '#FFBB28' }, + { name: "Online", value: online, color: "#2E7D32" }, + { name: "Offline", value: offline, color: "#db3927" }, + { name: "Pending", value: pending, color: "#FFBB28" }, ]; }, [tableData]); const cardData = React.useMemo(() => { - return pieData.filter((pieItem) => pieItem.value > 0).concat( - { - name: 'Total', + return pieData + .filter((pieItem) => pieItem.value > 0) + .concat({ + name: "Total", value: pieData.reduce((a, b) => a + b.value, 0), - color: '#000000' - } - ); + color: "#000000", + }); }, [pieData]); const cardStack = ( @@ -217,17 +230,38 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { display="flex" flexDirection="column" justifyContent="flex-start" - flexWrap="wrap"> + flexWrap="wrap" + > {cardData.map((pieItem) => ( - - - + + + {pieItem.value} - + {pieItem.name} - ))} @@ -240,15 +274,19 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { pl: { sm: 2 }, pr: { xs: 1, sm: 1 }, bgcolor: (theme) => - alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity), - }}> + alpha( + theme.palette.primary.main, + theme.palette.action.activatedOpacity, + ), + }} + > - + ); const unselectedToolbar = ( @@ -270,16 +308,15 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { pr: { xs: 1, sm: 1 }, }} > - + - + ); - return ( @@ -295,19 +332,41 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { {/* Debug Controls */} - { setDebug(!debug) }} checked={debug} />} label="Debug" /> + { + setDebug(!debug); + }} + checked={debug} + /> + } + label="Debug" + /> {/* Pie Chart Grid */} - + {/* Card Stack Grid */} - + {cardStack} @@ -315,12 +374,10 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { {isSelected ? selectedToolbar : unselectedToolbar} - ); } - function renderLastSeen(last_seen: number) { return ( @@ -376,13 +433,13 @@ function renderStatus(status: NodeStatus) { } export interface NodeTableProps { - tableData: TableData[] + tableData: TableData[]; } export default function NodeTable(props: NodeTableProps) { let { tableData } = props; - const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('status'); + const [order, setOrder] = React.useState("asc"); + const [orderBy, setOrderBy] = React.useState("status"); const [selected, setSelected] = React.useState(undefined); const [page, setPage] = React.useState(0); const [dense, setDense] = React.useState(false); @@ -392,8 +449,8 @@ export default function NodeTable(props: NodeTableProps) { event: React.MouseEvent, property: keyof TableData, ) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); setOrderBy(property); }; @@ -410,7 +467,9 @@ export default function NodeTable(props: NodeTableProps) { setPage(newPage); }; - const handleChangeRowsPerPage = (event: React.ChangeEvent) => { + const handleChangeRowsPerPage = ( + event: React.ChangeEvent, + ) => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; @@ -431,18 +490,20 @@ export default function NodeTable(props: NodeTableProps) { [order, orderBy, page, rowsPerPage, tableData], ); - - return ( - - - setSelected(undefined)} /> + + + setSelected(undefined)} + /> - + {renderName(row.name, row.id)} - {renderStatus(row.status)} - {renderLastSeen(row.last_seen)} + + {renderStatus(row.status)} + + + {renderLastSeen(row.last_seen)} + ); })} diff --git a/pkgs/ui/src/app/nodes/NodePieChart.tsx b/pkgs/ui/src/app/nodes/NodePieChart.tsx index f85a513a..6e715826 100644 --- a/pkgs/ui/src/app/nodes/NodePieChart.tsx +++ b/pkgs/ui/src/app/nodes/NodePieChart.tsx @@ -1,45 +1,50 @@ -import React, { PureComponent } from 'react'; -import { PieChart, Pie, Sector, Cell, ResponsiveContainer, Legend } from 'recharts'; -import { useTheme } from '@mui/material/styles'; -import { Box, Color } from '@mui/material'; - +import React, { PureComponent } from "react"; +import { + PieChart, + Pie, + Sector, + Cell, + ResponsiveContainer, + Legend, +} from "recharts"; +import { useTheme } from "@mui/material/styles"; +import { Box, Color } from "@mui/material"; export interface PieData { - name: string; - value: number; - color: string; -}; + name: string; + value: number; + color: string; +} interface Props { - data: PieData[]; - showLabels?: boolean; -}; + data: PieData[]; + showLabels?: boolean; +} -export default function NodePieChart(props: Props ) { - const theme = useTheme(); - const {data, showLabels} = props; +export default function NodePieChart(props: Props) { + const theme = useTheme(); + const { data, showLabels } = props; - - return ( - - - - - {data.map((entry, index) => ( - - ))} - - - - - - ); -}; + return ( + + + + + {data.map((entry, index) => ( + + ))} + + + + + + ); +} diff --git a/pkgs/ui/src/app/nodes/page.tsx b/pkgs/ui/src/app/nodes/page.tsx index 3ed9eda1..03993bb5 100644 --- a/pkgs/ui/src/app/nodes/page.tsx +++ b/pkgs/ui/src/app/nodes/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { StrictMode } from "react"; import NodeList, { NodeStatus, TableData } from "./NodeList"; @@ -6,42 +6,98 @@ import NodeList, { NodeStatus, TableData } from "./NodeList"; import Box from "@mui/material/Box"; function createData( - name: string, - id: string, - status: NodeStatus, - last_seen: number, - + name: string, + id: string, + status: NodeStatus, + last_seen: number, ): TableData { - - - return { - name, - id, - status, - last_seen: last_seen, - }; + return { + name, + id, + status, + last_seen: last_seen, + }; } const tableData = [ - createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", NodeStatus.Pending, 0), - createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", NodeStatus.Online, 0), - createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", NodeStatus.Offline, 16.0), - createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", NodeStatus.Offline, 6.0), - createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", NodeStatus.Offline, 13), - createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", NodeStatus.Online, 0), - createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", NodeStatus.Online, 0), - createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", NodeStatus.Online, 0.0), - createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", NodeStatus.Offline, 7.0), - createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", NodeStatus.Offline, 2), - createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0), - createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0), - createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", NodeStatus.Online, 0), + createData( + "Matchbox", + "42:0:f21:6916:e333:c47e:4b5c:e74c", + NodeStatus.Pending, + 0, + ), + createData( + "Ahorn", + "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", + NodeStatus.Online, + 0, + ), + createData( + "Yellow", + "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", + NodeStatus.Offline, + 16.0, + ), + createData( + "Rauter", + "42:0:61ea:b777:61ea:803:f885:3523", + NodeStatus.Offline, + 6.0, + ), + createData( + "Porree", + "42:0:e644:4499:d034:895e:34c8:6f9a", + NodeStatus.Offline, + 13, + ), + createData( + "Helsinki", + "42:0:3c46:fd4a:acf9:e971:6036:8047", + NodeStatus.Online, + 0, + ), + createData( + "Kelle", + "42:0:3c46:362d:a9aa:4996:c78e:839a", + NodeStatus.Online, + 0, + ), + createData( + "Shodan", + "42:0:3c46:6745:adf4:a844:26c4:bf91", + NodeStatus.Online, + 0.0, + ), + createData( + "Qubasa", + "42:0:3c46:123e:bbea:3529:db39:6764", + NodeStatus.Offline, + 7.0, + ), + createData( + "Green", + "42:0:a46e:5af:632c:d2fe:a71d:cde0", + NodeStatus.Offline, + 2, + ), + createData("Gum", "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0), + createData("Xu", "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0), + createData( + "Zaatar", + "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", + NodeStatus.Online, + 0, + ), ]; export default function Page() { - return ( - - - - ); -} \ No newline at end of file + return ( + + + + ); +} diff --git a/pkgs/ui/src/app/page.tsx b/pkgs/ui/src/app/page.tsx index 4918af51..093b3e98 100644 --- a/pkgs/ui/src/app/page.tsx +++ b/pkgs/ui/src/app/page.tsx @@ -1,13 +1,60 @@ -import { Button } from "@mui/material"; +interface DashboardCardProps { + children?: React.ReactNode; +} +const DashboardCard = (props: DashboardCardProps) => { + const { children } = props; + return ( +
+ {children} +
+ ); +}; + +interface DashboardPanelProps { + children?: React.ReactNode; +} +const DashboardPanel = (props: DashboardPanelProps) => { + const { children } = props; + return ( +
+ {children} +
+ ); +}; + +interface SplitDashboardCardProps { + children?: React.ReactNode[]; +} +const SplitDashboardCard = (props: SplitDashboardCardProps) => { + const { children } = props; + return ( +
+
+ {children?.map((row, idx) => ( +
+ {row} +
+ ))} +
+
+ ); +}; export default function Dashboard() { return ( -
-
- Welcome to the Dashboard - +
+
+ Current CLAN Overview + Recent Activity Log + +
Notifications
+
Quick Action
+
+ Panel + Side Bar (misc)
); diff --git a/pkgs/ui/src/app/theme/themes.ts b/pkgs/ui/src/app/theme/themes.ts index 06538189..fd845dec 100644 --- a/pkgs/ui/src/app/theme/themes.ts +++ b/pkgs/ui/src/app/theme/themes.ts @@ -1,16 +1,13 @@ import { createTheme } from "@mui/material/styles"; - export const darkTheme = createTheme({ palette: { mode: "dark", - }, }); export const lightTheme = createTheme({ palette: { mode: "light", - }, }); diff --git a/pkgs/ui/src/components/sidebar/index.tsx b/pkgs/ui/src/components/sidebar/index.tsx index 8418af74..8c8a32d8 100644 --- a/pkgs/ui/src/components/sidebar/index.tsx +++ b/pkgs/ui/src/components/sidebar/index.tsx @@ -1,5 +1,7 @@ import { Divider, + Icon, + IconButton, List, ListItem, ListItemButton, @@ -7,7 +9,7 @@ import { ListItemText, } from "@mui/material"; import Image from "next/image"; -import { ReactNode } from "react"; +import { ReactNode, useState } from "react"; import DashboardIcon from "@mui/icons-material/Dashboard"; import DevicesIcon from "@mui/icons-material/Devices"; @@ -16,6 +18,9 @@ import AppsIcon from "@mui/icons-material/Apps"; import DesignServicesIcon from "@mui/icons-material/DesignServices"; import BackupIcon from "@mui/icons-material/Backup"; import Link from "next/link"; +import { tw } from "@/utils/tailwind"; + +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; type MenuEntry = { icon: ReactNode; @@ -58,11 +63,23 @@ const menuEntries: MenuEntry[] = [ }, ]; -export function Sidebar() { +const hideSidebar = tw`-translate-x-12 absolute lg:-translate-x-64`; +const showSidebar = tw`lg:translate-x-0 static`; + +interface SidebarProps { + show: boolean; + onClose: () => void; +} +export function Sidebar(props: SidebarProps) { + const { show, onClose } = props; return ( -