From 722c479e49beceff4047fa0412a6ef202c129e97 Mon Sep 17 00:00:00 2001 From: Michelle Date: Wed, 24 Dec 2025 00:27:52 +0400 Subject: [PATCH 1/6] refactor: flatten route groups into single root layout Layout Structure: - Remove (no-right-sidebar) and (with-right-sidebar) route groups - Move pages directly under app/(root)/ - Delete redundant nested layout files Root Layout: - Add main content padding directly to root layout Both nested layouts had identical styling, making the route group separation unnecessary. This simplifies the directory structure and consolidates layout logic in one place. --- app/(root)/(no-right-sidebar)/layout.tsx | 7 ------- app/(root)/(with-right-sidebar)/layout.tsx | 11 ----------- .../{(with-right-sidebar) => }/ask-question/page.tsx | 0 .../{(with-right-sidebar) => }/collections/page.tsx | 0 .../{(no-right-sidebar) => }/community/page.tsx | 0 app/(root)/{(no-right-sidebar) => }/jobs/page.tsx | 0 app/(root)/layout.tsx | 2 +- app/(root)/{(with-right-sidebar) => }/page.tsx | 0 app/(root)/{(no-right-sidebar) => }/profile/page.tsx | 0 app/(root)/{(no-right-sidebar) => }/tags/page.tsx | 0 10 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 app/(root)/(no-right-sidebar)/layout.tsx delete mode 100644 app/(root)/(with-right-sidebar)/layout.tsx rename app/(root)/{(with-right-sidebar) => }/ask-question/page.tsx (100%) rename app/(root)/{(with-right-sidebar) => }/collections/page.tsx (100%) rename app/(root)/{(no-right-sidebar) => }/community/page.tsx (100%) rename app/(root)/{(no-right-sidebar) => }/jobs/page.tsx (100%) rename app/(root)/{(with-right-sidebar) => }/page.tsx (100%) rename app/(root)/{(no-right-sidebar) => }/profile/page.tsx (100%) rename app/(root)/{(no-right-sidebar) => }/tags/page.tsx (100%) diff --git a/app/(root)/(no-right-sidebar)/layout.tsx b/app/(root)/(no-right-sidebar)/layout.tsx deleted file mode 100644 index 34cae74..0000000 --- a/app/(root)/(no-right-sidebar)/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -// Full-width layout without right sidebar -// Layout: [LeftSidebar (sticky)] [Main Content] -const NoRightSidebarLayout = ({ children }: { children: React.ReactNode }) => { - return
{children}
; -}; - -export default NoRightSidebarLayout; diff --git a/app/(root)/(with-right-sidebar)/layout.tsx b/app/(root)/(with-right-sidebar)/layout.tsx deleted file mode 100644 index ad0517f..0000000 --- a/app/(root)/(with-right-sidebar)/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// TODO: Add for tag widgets, hot questions, etc. -// Layout: [LeftSidebar (sticky)] [Main Content] [RightSidebar] -const WithRightSidebarLayout = ({ - children, -}: { - children: React.ReactNode; -}) => { - return
{children}
; -}; - -export default WithRightSidebarLayout; diff --git a/app/(root)/(with-right-sidebar)/ask-question/page.tsx b/app/(root)/ask-question/page.tsx similarity index 100% rename from app/(root)/(with-right-sidebar)/ask-question/page.tsx rename to app/(root)/ask-question/page.tsx diff --git a/app/(root)/(with-right-sidebar)/collections/page.tsx b/app/(root)/collections/page.tsx similarity index 100% rename from app/(root)/(with-right-sidebar)/collections/page.tsx rename to app/(root)/collections/page.tsx diff --git a/app/(root)/(no-right-sidebar)/community/page.tsx b/app/(root)/community/page.tsx similarity index 100% rename from app/(root)/(no-right-sidebar)/community/page.tsx rename to app/(root)/community/page.tsx diff --git a/app/(root)/(no-right-sidebar)/jobs/page.tsx b/app/(root)/jobs/page.tsx similarity index 100% rename from app/(root)/(no-right-sidebar)/jobs/page.tsx rename to app/(root)/jobs/page.tsx diff --git a/app/(root)/layout.tsx b/app/(root)/layout.tsx index b4916a3..89d818d 100644 --- a/app/(root)/layout.tsx +++ b/app/(root)/layout.tsx @@ -9,7 +9,7 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
-
{children}
+
{children}
diff --git a/app/(root)/(with-right-sidebar)/page.tsx b/app/(root)/page.tsx similarity index 100% rename from app/(root)/(with-right-sidebar)/page.tsx rename to app/(root)/page.tsx diff --git a/app/(root)/(no-right-sidebar)/profile/page.tsx b/app/(root)/profile/page.tsx similarity index 100% rename from app/(root)/(no-right-sidebar)/profile/page.tsx rename to app/(root)/profile/page.tsx diff --git a/app/(root)/(no-right-sidebar)/tags/page.tsx b/app/(root)/tags/page.tsx similarity index 100% rename from app/(root)/(no-right-sidebar)/tags/page.tsx rename to app/(root)/tags/page.tsx From 2982f5408e27549995417adf5930601d29c844ed Mon Sep 17 00:00:00 2001 From: Michelle Date: Thu, 25 Dec 2025 03:54:33 +0400 Subject: [PATCH 2/6] chore: add shadcn/ui sidebar component with dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components: - Add sidebar.tsx, input.tsx, separator.tsx, skeleton.tsx, tooltip.tsx - Add use-mobile.ts hook for responsive behaviour - Add biome-ignore comments for shadcn/ui lint compatibility Configuration: - Fix components.json CSS path from globals.css to app/globals.css - Install @radix-ui/react-separator and @radix-ui/react-tooltip The CSS path correction resolves Tailwind IntelliSense warnings in VS Code. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- components.json | 2 +- components/ui/input.tsx | 21 ++ components/ui/separator.tsx | 28 ++ components/ui/sidebar.tsx | 728 ++++++++++++++++++++++++++++++++++++ components/ui/skeleton.tsx | 13 + components/ui/tooltip.tsx | 61 +++ hooks/use-mobile.ts | 21 ++ package-lock.json | 258 +++++++++++++ package.json | 2 + 9 files changed, 1133 insertions(+), 1 deletion(-) create mode 100644 components/ui/input.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 hooks/use-mobile.ts diff --git a/components.json b/components.json index ce09c89..b27c50f 100644 --- a/components.json +++ b/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "globals.css", + "css": "app/globals.css", "baseColor": "slate", "cssVariables": true, "prefix": "" diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..73ea867 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,21 @@ +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ); +} + +export { Input }; diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx new file mode 100644 index 0000000..50733e0 --- /dev/null +++ b/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Separator }; diff --git a/components/ui/sidebar.tsx b/components/ui/sidebar.tsx new file mode 100644 index 0000000..3399bdc --- /dev/null +++ b/components/ui/sidebar.tsx @@ -0,0 +1,728 @@ +"use client"; + +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; +import * as React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; + +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; + +type SidebarContextProps = { + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; + +const SidebarContext = React.createContext(null); + +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } + + return context; +} + +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props +}: React.ComponentProps<"div"> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}) { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + // biome-ignore lint/suspicious/noDocumentCookie: shadcn/ui component uses document.cookie for sidebar state persistence + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + // biome-ignore lint/correctness/useExhaustiveDependencies: shadcn/ui includes setOpenMobile for clarity + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + // biome-ignore lint/correctness/useExhaustiveDependencies: shadcn/ui includes setOpenMobile for clarity + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], + ); + + return ( + + +
+ {children} +
+
+
+ ); +} + +function Sidebar({ + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props +}: React.ComponentProps<"div"> & { + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === "none") { + return ( +
+ {children} +
+ ); + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ); + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ); +} + +function SidebarTrigger({ + className, + onClick, + ...props +}: React.ComponentProps) { + const { toggleSidebar } = useSidebar(); + + return ( + + ); +} + +function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { + const { toggleSidebar } = useSidebar(); + + return ( + + + + + + +
+ + ); +} diff --git a/components/navigation/full-logo.tsx b/components/navigation/full-logo.tsx index 41c5735..0a91628 100644 --- a/components/navigation/full-logo.tsx +++ b/components/navigation/full-logo.tsx @@ -17,7 +17,7 @@ export function ThemeLogo({ className }: ThemeLogoProps) { + {/* Left: Logo icon */} + + {/* biome-ignore lint/a11y/useAltText: Decorative logo, aria-label on parent link */} + {/* biome-ignore lint/performance/noImgElement: SVG logo doesn't benefit from next/image optimisation */} + + + + {/* Right: Theme toggle + hamburger */} +
+ + +
+ + ); +} diff --git a/components/navigation/mobile-nav.tsx b/components/navigation/mobile-nav.tsx index 736608e..ebf6fd0 100644 --- a/components/navigation/mobile-nav.tsx +++ b/components/navigation/mobile-nav.tsx @@ -75,7 +75,7 @@ export function MobileNav() { {/* Navigation Links */} -