From b9c3ffe7ab7c6036fd64b5a6e15c0240dfc01589 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 16 Jul 2025 22:55:04 +0100 Subject: [PATCH] feat: setup dashboard 2.0 --- apps/web/src/components/Dashboard.tsx | 118 ++++++++---------- apps/web/src/components/LottieIcon.tsx | 6 +- .../src/components/PatternedBackground.tsx | 4 +- apps/web/src/components/ReactiveButton.tsx | 13 +- apps/web/src/components/SideNavigation.tsx | 91 ++++++++++++-- apps/web/src/components/UserMenu.tsx | 24 +++- apps/web/src/components/WorkspaceMenu.tsx | 50 +++++--- 7 files changed, 208 insertions(+), 98 deletions(-) diff --git a/apps/web/src/components/Dashboard.tsx b/apps/web/src/components/Dashboard.tsx index a2e15a5a..f75dbb92 100644 --- a/apps/web/src/components/Dashboard.tsx +++ b/apps/web/src/components/Dashboard.tsx @@ -1,4 +1,3 @@ -import Link from "next/link"; import { useRef, useState } from "react"; import { TbLayoutSidebarLeftCollapse, @@ -11,7 +10,6 @@ import { authClient } from "@kan/auth/client"; import { useClickOutside } from "~/hooks/useClickOutside"; import { useTheme } from "~/providers/theme"; -import FeedbackButton from "./FeedbackButton"; import SideNavigation from "./SideNavigation"; interface DashboardProps { @@ -77,67 +75,57 @@ export default function Dashboard({ height: 100vh; overflow: hidden; min-width: 320px; - background-color: ${!isDarkMode ? "hsl(0deg 0% 97.3%)" : "#161616"}; + background-color: ${!isDarkMode ? "hsl(0deg 0% 97.3%)" : "#1c1c1c"}; } `} -
-
-
+
+ {/* Mobile Navigation Controls - positioned absolutely over content */} +
+ +
+ + {/* {hasRightPanel && ( +
- - -

- kan.bn -

- - -
- {hasRightPanel && ( - - )} -
- -
-
-
+ )} */} -
+
-
-
{children}
+
+
+
{children}
- {/* Mobile Right Panel */} - {hasRightPanel && rightPanel && ( -
-
{rightPanel}
-
- )} + {/* Mobile Right Panel */} + {hasRightPanel && rightPanel && ( +
+
+ {rightPanel} +
+
+ )} - {/* Desktop Right Panel */} - {hasRightPanel && rightPanel && ( -
{rightPanel}
- )} + {/* Desktop Right Panel */} + {hasRightPanel && rightPanel && ( +
{rightPanel}
+ )} +
diff --git a/apps/web/src/components/LottieIcon.tsx b/apps/web/src/components/LottieIcon.tsx index a9802b2d..bc48b15c 100644 --- a/apps/web/src/components/LottieIcon.tsx +++ b/apps/web/src/components/LottieIcon.tsx @@ -2,11 +2,11 @@ import dynamic from "next/dynamic"; const Lottie = dynamic(() => import("react-lottie-player"), { ssr: false }); -type IconProps = { +interface IconProps { isPlaying: boolean; index: number; json: object; -}; +} const Icon: React.FC = ({ isPlaying, index, json }) => { return ( @@ -15,7 +15,7 @@ const Icon: React.FC = ({ isPlaying, index, json }) => { animationData={json} play={isPlaying} loop={false} - style={{ width: 20, height: 20, fill: "white" }} + style={{ width: 18, height: 18, fill: "white" }} rendererSettings={{ preserveAspectRatio: "xMidYMid slice" }} /> ); diff --git a/apps/web/src/components/PatternedBackground.tsx b/apps/web/src/components/PatternedBackground.tsx index 6304f95e..4c2e143b 100644 --- a/apps/web/src/components/PatternedBackground.tsx +++ b/apps/web/src/components/PatternedBackground.tsx @@ -3,8 +3,8 @@ const PatternedBackground = () => ( = ({ href, current, name, json }) => { + isCollapsed?: boolean; +}> = ({ href, current, name, json, isCollapsed = false }) => { const [isHovered, setIsHovered] = useState(false); const [index, setIndex] = useState(0); @@ -26,12 +27,16 @@ const Button: React.FC<{ href={href} onMouseEnter={handleMouseEnter} className={classNames( - current ? "bg-light-200 dark:bg-dark-200" : "dark:bg-dark-50", - "group flex items-center gap-x-3 rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-200", + "group flex h-[34px] items-center rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 hover:bg-light-200 dark:hover:bg-dark-200 dark:hover:text-dark-1000", + current + ? "bg-light-200 dark:bg-dark-200 dark:text-dark-1000" + : "dark:bg-dark-100 dark:text-dark-900", + isCollapsed ? "justify-center" : "gap-x-3", )} + title={isCollapsed ? name : undefined} > - {name} + {!isCollapsed && name} ); }; diff --git a/apps/web/src/components/SideNavigation.tsx b/apps/web/src/components/SideNavigation.tsx index 46e4598a..ba184221 100644 --- a/apps/web/src/components/SideNavigation.tsx +++ b/apps/web/src/components/SideNavigation.tsx @@ -1,5 +1,13 @@ +import Link from "next/link"; import { useRouter } from "next/router"; +import { Button } from "@headlessui/react"; import { t } from "@lingui/core/macro"; +import { useEffect, useState } from "react"; +import { + TbLayoutSidebarLeftCollapse, + TbLayoutSidebarLeftExpand, +} from "react-icons/tb"; +import { twMerge } from "tailwind-merge"; import boardsIconDark from "~/assets/boards-dark.json"; import boardsIconLight from "~/assets/boards-light.json"; @@ -11,6 +19,7 @@ import ReactiveButton from "~/components/ReactiveButton"; import UserMenu from "~/components/UserMenu"; import WorkspaceMenu from "~/components/WorkspaceMenu"; import { useTheme } from "~/providers/theme"; +import FeedbackButton from "./FeedbackButton"; interface SideNavigationProps { user: UserType; @@ -27,6 +36,25 @@ export default function SideNavigation({ isLoading, }: SideNavigationProps) { const router = useRouter(); + const [isCollapsed, setIsCollapsed] = useState(false); + const [isInitialised, setIsInitialised] = useState(false); + + useEffect(() => { + const savedState = localStorage.getItem("kan_sidebar-collapsed"); + if (savedState !== null) { + setIsCollapsed(JSON.parse(savedState)); + } + setIsInitialised(true); + }, []); + + useEffect(() => { + if (isInitialised) { + localStorage.setItem( + "kan_sidebar-collapsed", + JSON.stringify(isCollapsed), + ); + } + }, [isCollapsed, isInitialised]); const { pathname } = router; @@ -52,11 +80,50 @@ export default function SideNavigation({ }, ]; + const toggleCollapse = () => { + setIsCollapsed(!isCollapsed); + }; + return ( <> -
- +
+ {!isCollapsed && ( + +

+ kan.bn +

+ + )} + +
+
+ +
    {navigation.map((item) => (
  • @@ -65,16 +132,26 @@ export default function SideNavigation({ current={pathname.includes(item.href)} name={item.name} json={item.icon} + isCollapsed={isCollapsed} />
  • ))}
- + +
+ {/* Feedback button above user menu */} + {/*
+ +
*/} + + +
); diff --git a/apps/web/src/components/UserMenu.tsx b/apps/web/src/components/UserMenu.tsx index 4c0a5a06..301a339f 100644 --- a/apps/web/src/components/UserMenu.tsx +++ b/apps/web/src/components/UserMenu.tsx @@ -13,6 +13,7 @@ interface UserMenuProps { imageUrl: string | undefined; email: string; isLoading: boolean; + isCollapsed?: boolean; } function classNames(...classes: string[]): string { @@ -23,6 +24,7 @@ export default function UserMenu({ imageUrl, email, isLoading, + isCollapsed = false, }: UserMenuProps) { const router = useRouter(); const { themePreference, switchTheme } = useTheme(); @@ -39,12 +41,17 @@ export default function UserMenu({
{isLoading ? ( -
+
-
+ {!isCollapsed && ( +
+ )}
) : ( - + {avatarUrl ? ( )} - {email} + {!isCollapsed && ( + {email} + )} )}
@@ -78,7 +87,12 @@ export default function UserMenu({ leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - +
diff --git a/apps/web/src/components/WorkspaceMenu.tsx b/apps/web/src/components/WorkspaceMenu.tsx index cfbf3a95..554d73c6 100644 --- a/apps/web/src/components/WorkspaceMenu.tsx +++ b/apps/web/src/components/WorkspaceMenu.tsx @@ -2,11 +2,16 @@ import { Menu, Transition } from "@headlessui/react"; import { t } from "@lingui/core/macro"; import { Fragment } from "react"; import { HiCheck } from "react-icons/hi2"; +import { twMerge } from "tailwind-merge"; import { useModal } from "~/providers/modal"; import { useWorkspace } from "~/providers/workspace"; -export default function WorkspaceMenu() { +export default function WorkspaceMenu({ + isCollapsed = false, +}: { + isCollapsed?: boolean; +}) { const { workspace, isLoading, availableWorkspaces, switchWorkspace } = useWorkspace(); const { openModal } = useModal(); @@ -15,24 +20,36 @@ export default function WorkspaceMenu() {
{isLoading ? ( -
-
-
+
+
+ {!isCollapsed && ( +
+ )}
) : ( - - + + {workspace.name.charAt(0).toUpperCase()} - - {workspace.name} - - {workspace.plan === "pro" && ( - - Pro - + {!isCollapsed && ( + <> + + {workspace.name} + + {workspace.plan === "pro" && ( + + Pro + + )} + )} )} @@ -47,7 +64,12 @@ export default function WorkspaceMenu() { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - +
{availableWorkspaces.map((availableWorkspace) => (