feat: fix responsive layout, add theme switcher

This commit is contained in:
2024-08-17 00:21:23 +07:00
parent 43af9c8658
commit f503b261f5
22 changed files with 349 additions and 100 deletions
+25 -4
View File
@@ -1,12 +1,16 @@
import { cn } from "@/lib/utils";
import { cn, ucfirst } from "@/lib/utils";
import {
ArchiveIcon,
HardDrive,
KeySquare,
LayoutDashboard,
Palette,
} from "lucide-react";
import { Menu } from "react-daisyui";
import { Dropdown, Menu } from "react-daisyui";
import { Link, useLocation } from "react-router-dom";
import Button from "../ui/button";
import { themes } from "@/app/themes";
import appStore from "@/stores/app-store";
const pages = [
{ icon: LayoutDashboard, title: "Dashboard", path: "/", exact: true },
@@ -19,7 +23,7 @@ const Sidebar = () => {
const { pathname } = useLocation();
return (
<aside className="bg-base-100 border-r border-base-300/30 w-[220px] overflow-y-auto">
<aside className="bg-base-100 border-r border-base-300/30 w-[80%] md:w-[250px] flex flex-col items-stretch overflow-hidden h-full">
<div className="p-4">
<img
src="https://garagehq.deuxfleurs.fr/images/garage-logo.svg"
@@ -28,7 +32,8 @@ const Sidebar = () => {
/>
<p className="text-sm font-medium text-center">WebUI</p>
</div>
<Menu className="gap-y-1">
<Menu className="gap-y-1 flex-1 overflow-y-auto">
{pages.map((page) => {
const isActive = page.exact
? pathname === page.path
@@ -50,6 +55,22 @@ const Sidebar = () => {
);
})}
</Menu>
<Dropdown className="my-2 mx-4" vertical="top">
<Dropdown.Toggle button={false}>
<Button icon={Palette} color="ghost">
Theme
</Button>
</Dropdown.Toggle>
<Dropdown.Menu className="max-h-[200px] overflow-y-auto">
{themes.map((theme) => (
<Dropdown.Item key={theme} onClick={() => appStore.setTheme(theme)}>
{ucfirst(theme)}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
</aside>
);
};
@@ -0,0 +1,15 @@
import appStore from "@/stores/app-store";
import { useEffect } from "react";
import { useStore } from "zustand";
const ThemeProvider = () => {
const theme = useStore(appStore, (i) => i.theme);
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return null;
};
export default ThemeProvider;
+42 -18
View File
@@ -1,29 +1,46 @@
import { PageContext } from "@/context/page-context";
import { Suspense, useContext } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import { Suspense, useContext, useEffect } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import Sidebar from "../containers/sidebar";
import { ArrowLeft } from "lucide-react";
import { ArrowLeft, MenuIcon } from "lucide-react";
import Button from "../ui/button";
import { useDisclosure } from "@/hooks/useDisclosure";
import { Drawer } from "react-daisyui";
const MainLayout = () => {
const sidebar = useDisclosure();
const { pathname } = useLocation();
useEffect(() => {
if (sidebar.isOpen) {
sidebar.onClose();
}
}, [pathname]);
return (
<div className="flex flex-row items-stretch h-screen overflow-hidden">
<Sidebar />
<Drawer
open={sidebar.isOpen}
onClickOverlay={sidebar.onClose}
className="md:drawer-open h-screen"
side={<Sidebar />}
contentClassName="flex flex-col overflow-hidden"
>
<Header onSidebarOpen={sidebar.onOpen} />
<div className="flex-1 flex flex-col overflow-hidden">
<Header />
<main className="flex-1 overflow-y-auto p-4 md:p-8">
<Suspense>
<Outlet />
</Suspense>
</main>
</div>
</div>
<main className="flex-1 overflow-y-auto p-4 md:p-8">
<Suspense>
<Outlet />
</Suspense>
</main>
</Drawer>
);
};
const Header = () => {
type HeaderProps = {
onSidebarOpen: () => void;
};
const Header = ({ onSidebarOpen }: HeaderProps) => {
const page = useContext(PageContext);
const navigate = useNavigate();
@@ -35,11 +52,18 @@ const Header = () => {
onClick={() => navigate(page.prev!, { replace: true })}
color="ghost"
shape="circle"
className="-ml-4"
className="-mx-2"
>
<ArrowLeft />
</Button>
) : null}
) : (
<Button
icon={MenuIcon}
color="ghost"
className="md:hidden -mx-2"
onClick={onSidebarOpen}
/>
)}
<h1 className="text-xl flex-1 truncate">{page?.title || "Dashboard"}</h1>
+4 -3
View File
@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils";
import { ComponentPropsWithoutRef } from "react";
import { ComponentPropsWithoutRef, forwardRef } from "react";
import BaseSelect from "react-select";
import Creatable from "react-select/creatable";
@@ -8,11 +8,12 @@ type Props = ComponentPropsWithoutRef<typeof BaseSelect> & {
onCreateOption?: (inputValue: string) => void;
};
const Select = ({ creatable, ...props }: Props) => {
const Select = forwardRef<any, Props>(({ creatable, ...props }, ref) => {
const Comp = creatable ? Creatable : BaseSelect;
return (
<Comp
ref={ref}
unstyled
classNames={{
control: (p) =>
@@ -42,6 +43,6 @@ const Select = ({ creatable, ...props }: Props) => {
{...props}
/>
);
};
});
export default Select;
+51
View File
@@ -0,0 +1,51 @@
import React, { forwardRef } from "react";
import { Toggle as BaseToggle } from "react-daisyui";
import FormControl from "./form-control";
import { FieldValues } from "react-hook-form";
type ToggleProps = Omit<
React.ComponentPropsWithoutRef<typeof BaseToggle>,
"form"
> & { label?: string };
const Toggle = forwardRef<HTMLInputElement, ToggleProps>(
({ label, ...props }, ref) => {
return (
<label className="inline-flex justify-start label label-text gap-2 cursor-pointer">
<BaseToggle ref={ref} {...props} />
{label}
</label>
);
}
);
type ToggleFieldProps<T extends FieldValues> = Omit<
React.ComponentPropsWithoutRef<typeof FormControl<T>>,
"render"
> &
ToggleProps & {
inputClassName?: string;
};
export const ToggleField = <T extends FieldValues>({
form,
name,
title,
className,
inputClassName,
...props
}: ToggleFieldProps<T>) => {
return (
<FormControl
form={form}
name={name}
title={title}
className={className}
render={(field) => (
<Toggle {...props} {...field} className={inputClassName} />
)}
/>
);
};
export default Toggle;