[#21] feat(ui): enhance sidebar collapse functionality (#27)

* [#21] feat(ui): enhance sidebar collapse functionality

* refactor: shadcn compose button and types

* fix(lint): resolve linter errors

* refactor: use index for types
This commit is contained in:
James Haworth
2025-02-07 01:42:35 +00:00
committed by GitHub
parent e8b9458572
commit 9b985cfd3c
3 changed files with 77 additions and 19 deletions

View File

@@ -1,7 +1,8 @@
"use client";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
import { Account } from "@/types";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
@@ -24,19 +25,17 @@ import {
SidebarMenuButton,
useSidebar,
} from "@/components/ui/sidebar";
import React from "react";
interface AccountSwitcherProps {
accounts: {
name: string;
logo: React.ComponentType<{ className?: string }>;
email: string;
}[];
accounts: Account[];
}
export function AccountSwitcher({ accounts }: AccountSwitcherProps) {
const [selectedAccount, setSelectedAccount] = React.useState(accounts[0]);
const { isMobile } = useSidebar();
const { isMobile, state } = useSidebar();
const collapsed = state === "collapsed";
return (
<DropdownMenu>
@@ -47,7 +46,12 @@ export function AccountSwitcher({ accounts }: AccountSwitcherProps) {
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="flex aspect-square size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-sidebar-primary-foreground">
<div
className={cn(
"flex aspect-square size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-sidebar-primary-foreground",
collapsed && "w-full",
)}
>
<selectedAccount.logo className="size-5" />
</div>
<div className="flex min-w-0 flex-col gap-0.5 leading-none">

View File

@@ -16,8 +16,8 @@ import {
Pencil,
} from "lucide-react";
import { Gmail, Outlook, Vercel } from "@/components/icons/icons";
import { Button } from "@/components/ui/button";
import * as React from "react";
import { SidebarData } from "@/types";
import React from "react";
import {
Sidebar,
@@ -25,6 +25,9 @@ import {
SidebarFooter,
SidebarHeader,
SidebarRail,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
} from "@/components/ui/sidebar";
import { useSidebar } from "@/components/ui/sidebar";
import { AccountSwitcher } from "./account-switcher";
@@ -33,9 +36,7 @@ import { SidebarToggle } from "./sidebar-toggle";
import { NavMain } from "./nav-main";
import { NavUser } from "./nav-user";
// This is sample data that matches the screenshot
const data = {
const data: SidebarData = {
user: {
name: "nizzy",
email: "nizabizaher@gmail.com",
@@ -153,7 +154,29 @@ const data = {
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const [composeOpen, setComposeOpen] = React.useState(false);
const { isMobile } = useSidebar();
const sidebarContext = useSidebar();
const { isMobile } = sidebarContext;
const handleComposeClick = React.useCallback(() => {
setComposeOpen(true);
}, []);
// Memoized compose button component
const ComposeButton = React.memo(function ComposeButton() {
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
className="m-2 w-fit bg-primary px-3 text-primary-foreground transition-[margin] hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground group-data-[collapsible=icon]:mx-0"
onClick={handleComposeClick}
>
<Pencil className="size-4" />
<span>Compose</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
);
});
return (
<>
@@ -161,19 +184,20 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<AccountSwitcher accounts={data.accounts} />
<ComposeButton />
</SidebarHeader>
<SidebarContent>
<Button className="mx-3.5 mt-2 w-fit" onClick={() => setComposeOpen(true)}>
<Pencil className="size-4" />
Compose
</Button>
<NavMain items={data.navMain} />
</SidebarContent>
<SidebarFooter>
<NavUser />
</SidebarFooter>
<SidebarRail />
<MailCompose open={composeOpen} onClose={() => setComposeOpen(false)} />
<MailCompose
open={composeOpen}
onClose={() => setComposeOpen(false)}
aria-label="Compose email dialog"
/>
</Sidebar>
</>
);

30
types/index.ts Normal file
View File

@@ -0,0 +1,30 @@
export interface User {
name: string;
email: string;
avatar: string;
}
export interface Account {
name: string;
logo: React.ComponentType<{ className?: string }>;
email: string;
}
export interface NavItem {
title: string;
url: string;
icon?: React.ComponentType<{ className?: string }>;
isActive?: boolean;
badge?: number;
}
export interface NavSection {
title: string;
items: NavItem[];
}
export interface SidebarData {
user: User;
accounts: Account[];
navMain: NavSection[];
}