From ec1b5d5acb58764d0438b81e9c5ee7fd21f20e98 Mon Sep 17 00:00:00 2001 From: LovelessCodes Date: Wed, 2 Jul 2025 12:58:10 +0200 Subject: [PATCH] feat: basic mobile support (#64) * feat: add mobile navigation with responsive layout and theme controls * feat: prevent workspace owners from removing themselves and update workspace dropdown styling * feat: add useClickOutside hook and event listener utilities for improved UI interactions * chore: improve mobile UI styling * refactor: improve UI layout and buttons in boards/members views feat: adjust /members layout * fix: standardize layout containers and improve mobile navigation styling * feat: update card view layout * feat: add responsive layout to board view * feat: readjust boards page layout * feat: enhance table view on mobile for members page * feat: extend mobile support with sliding side panels * fix: prevent two panels from being open at the same time * feat: close panels when clicking outside * fix: adjust font-sizing for import source --------- Co-authored-by: Henry --- apps/web/src/components/Dashboard.tsx | 149 ++++++++++++++++-- apps/web/src/components/Input.tsx | 2 +- apps/web/src/components/NewWorkspaceForm.tsx | 2 + apps/web/src/components/SideNavigation.tsx | 2 +- apps/web/src/hooks/useClickOutside.ts | 59 +++++++ apps/web/src/hooks/useEventListener.ts | 120 ++++++++++++++ .../src/hooks/useIsomorphicLayoutEffect.ts | 15 ++ apps/web/src/pages/_app.tsx | 8 + apps/web/src/pages/cards/[cardId]/index.tsx | 6 +- apps/web/src/styles/globals.css | 5 +- apps/web/src/views/board/index.tsx | 8 +- .../boards/components/ImportBoardsForm.tsx | 10 +- apps/web/src/views/boards/index.tsx | 37 ++--- .../web/src/views/card/components/Comment.tsx | 2 +- apps/web/src/views/card/index.tsx | 100 +++++++----- apps/web/src/views/members/index.tsx | 79 ++++++---- apps/web/src/views/settings/index.tsx | 2 +- 17 files changed, 479 insertions(+), 127 deletions(-) create mode 100644 apps/web/src/hooks/useClickOutside.ts create mode 100644 apps/web/src/hooks/useEventListener.ts create mode 100644 apps/web/src/hooks/useIsomorphicLayoutEffect.ts diff --git a/apps/web/src/components/Dashboard.tsx b/apps/web/src/components/Dashboard.tsx index 959a75b6..d7e7e7de 100644 --- a/apps/web/src/components/Dashboard.tsx +++ b/apps/web/src/components/Dashboard.tsx @@ -1,11 +1,69 @@ import Link from "next/link"; +import { useRef, useState } from "react"; +import { + TbLayoutSidebarLeftCollapse, + TbLayoutSidebarLeftExpand, + TbLayoutSidebarRightCollapse, + TbLayoutSidebarRightExpand, +} from "react-icons/tb"; -import { api } from "~/utils/api"; +import { authClient } from "@kan/auth/client"; + +import { useClickOutside } from "~/hooks/useClickOutside"; import FeedbackButton from "./FeedbackButton"; import SideNavigation from "./SideNavigation"; -export default function Dashboard(props: { children: React.ReactNode }) { - const { data, isLoading } = api.user.getUser.useQuery(); +interface DashboardProps { + children: React.ReactNode; + rightPanel?: React.ReactNode; + hasRightPanel?: boolean; +} + +export default function Dashboard({ + children, + rightPanel, + hasRightPanel = false, +}: DashboardProps) { + const { data: session, isPending: sessionLoading } = authClient.useSession(); + const [isSideNavOpen, setIsSideNavOpen] = useState(false); + const [isRightPanelOpen, setIsRightPanelOpen] = useState(false); + + const sideNavRef = useRef(null); + const rightPanelRef = useRef(null); + const sideNavButtonRef = useRef(null); + const rightPanelButtonRef = useRef(null); + + const toggleSideNav = () => { + setIsSideNavOpen(!isSideNavOpen); + if (!isSideNavOpen) { + setIsRightPanelOpen(false); + } + }; + + const toggleRightPanel = () => { + setIsRightPanelOpen(!isRightPanelOpen); + if (!isRightPanelOpen) { + setIsSideNavOpen(false); + } + }; + + useClickOutside(sideNavRef, (event) => { + if (sideNavButtonRef.current?.contains(event.target as Node)) { + return; + } + if (isSideNavOpen) { + setIsSideNavOpen(false); + } + }); + + useClickOutside(rightPanelRef, (event) => { + if (rightPanelButtonRef.current?.contains(event.target as Node)) { + return; + } + if (isRightPanelOpen) { + setIsRightPanelOpen(false); + } + }); return ( <> @@ -15,24 +73,91 @@ export default function Dashboard(props: { children: React.ReactNode }) { overflow: hidden; } `} -
-
+
+
- + + +

kan.bn

- + +
+ {hasRightPanel && ( + + )} +
+ +
+
- -
{props.children}
+
+ +
+ +
+
{children}
+ + {/* Mobile Right Panel */} + {hasRightPanel && rightPanel && ( +
+
{rightPanel}
+
+ )} + + {/* Desktop Right Panel */} + {hasRightPanel && rightPanel && ( +
{rightPanel}
+ )} +
diff --git a/apps/web/src/components/Input.tsx b/apps/web/src/components/Input.tsx index 1770a07d..abf1b24f 100644 --- a/apps/web/src/components/Input.tsx +++ b/apps/web/src/components/Input.tsx @@ -55,7 +55,7 @@ const Input = forwardRef(
{prefix && ( -
+
{prefix}
)} diff --git a/apps/web/src/components/NewWorkspaceForm.tsx b/apps/web/src/components/NewWorkspaceForm.tsx index cbee098a..720bd66d 100644 --- a/apps/web/src/components/NewWorkspaceForm.tsx +++ b/apps/web/src/components/NewWorkspaceForm.tsx @@ -19,10 +19,12 @@ export function NewWorkspaceForm() { const { showPopup } = usePopup(); const { switchWorkspace } = useWorkspace(); const { register, handleSubmit } = useForm(); + const utils = api.useUtils(); const createWorkspace = api.workspace.create.useMutation({ onSuccess: (values) => { if (values.publicId && values.name) { + utils.workspace.all.invalidate(); switchWorkspace({ publicId: values.publicId, name: values.name, diff --git a/apps/web/src/components/SideNavigation.tsx b/apps/web/src/components/SideNavigation.tsx index 76bf191e..46e4598a 100644 --- a/apps/web/src/components/SideNavigation.tsx +++ b/apps/web/src/components/SideNavigation.tsx @@ -54,7 +54,7 @@ export default function SideNavigation({ return ( <> -