今天要來探索 Animate UI 的 Sidebar 元件,你可能會在 Shadcn UI 上看到超級像(甚至說一模一樣)的側邊欄,但配上 Animate UI 一些些小動畫點綴,讓它不僅能優雅地收放自如,還能讓整個介面瞬間有條理又有高級感!想要了解更多,請繼續看下去~


▲ 每個按鈕對應的過場動畫

▲ 在手機上也已經完成響應式設計
打開的方向似乎反了...? 一個月前的版本式正常的啊 🤯
使用 Shadcn CLI 加入 Sidebar
npx shadcn@latest add @animate-ui/components-radix-sidebar
npx shadcn@latest add @animate-ui/primitives-radix-collapsible
npx shadcn@latest add @animate-ui/components-radix-dropdown-menu
npx shadcn@latest add @animate-ui/components-radix-sheet
npx shadcn@latest add @animate-ui/components-animate-tooltip
npx shadcn@latest add avatar
npx shadcn@latest add breadcrumb

Import 元件並將元件放在想要的位置上
"use client"
import * as React from 'react';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Separator } from '@/components/ui/separator';
import {
SidebarProvider,
SidebarInset,
SidebarTrigger,
Sidebar,
SidebarHeader,
SidebarContent,
SidebarFooter,
SidebarRail,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarMenuSubButton,
SidebarMenuAction,
} from '@/components/animate-ui/components/radix/sidebar';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/animate-ui/primitives/radix/collapsible';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/components/animate-ui/components/radix/dropdown-menu';
import {
AudioWaveform,
BadgeCheck,
Bell,
BookOpen,
Bot,
ChevronRight,
ChevronsUpDown,
Command,
CreditCard,
Folder,
Forward,
Frame,
GalleryVerticalEnd,
LogOut,
Map,
MoreHorizontal,
PieChart,
Plus,
Settings2,
Sparkles,
SquareTerminal,
Trash2,
} from 'lucide-react';
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/components/ui/avatar';
import { useIsMobile } from '@/hooks/use-mobile';
側邊欄要顯示的資料
const DATA = {
user: {
    name: 'Skyleen',
    email: 'skyleen@example.com',
    avatar:
    'https://pbs.twimg.com/profile_images/1909615404789506048/MTqvRsjo_400x400.jpg',
},
teams: [
    {
    name: 'Acme Inc',
    logo: GalleryVerticalEnd,
    plan: 'Enterprise',
    },
    {
    name: 'Acme Corp.',
    logo: AudioWaveform,
    plan: 'Startup',
    },
    {
    name: 'Evil Corp.',
    logo: Command,
    plan: 'Free',
    },
],
navMain: [
    {
    title: 'Playground',
    url: '#',
    icon: SquareTerminal,
    isActive: true,
    items: [
        {
        title: 'History',
        url: '#',
        },
        {
        title: 'Starred',
        url: '#',
        },
        {
        title: 'Settings',
        url: '#',
        },
    ],
    },
    {
    title: 'Models',
    url: '#',
    icon: Bot,
    items: [
        {
        title: 'Genesis',
        url: '#',
        },
        {
        title: 'Explorer',
        url: '#',
        },
        {
        title: 'Quantum',
        url: '#',
        },
    ],
    },
    {
    title: 'Documentation',
    url: '#',
    icon: BookOpen,
    items: [
        {
        title: 'Introduction',
        url: '#',
        },
        {
        title: 'Get Started',
        url: '#',
        },
        {
        title: 'Tutorials',
        url: '#',
        },
        {
        title: 'Changelog',
        url: '#',
        },
    ],
    },
    {
    title: 'Settings',
    url: '#',
    icon: Settings2,
    items: [
        {
        title: 'General',
        url: '#',
        },
        {
        title: 'Team',
        url: '#',
        },
        {
        title: 'Billing',
        url: '#',
        },
        {
        title: 'Limits',
        url: '#',
        },
    ],
    },
],
projects: [
    {
    name: 'Design Engineering',
    url: '#',
    icon: Frame,
    },
    {
    name: 'Sales & Marketing',
    url: '#',
    icon: PieChart,
    },
    {
    name: 'Travel',
    url: '#',
    icon: Map,
    },
],
};
export default function Home() {
const isMobile = useIsMobile();
const [activeTeam, setActiveTeam] = React.useState(DATA.teams[0]);
if (!activeTeam) return null;
return (
    <SidebarProvider>
    <Sidebar collapsible="icon">
        <SidebarHeader>
        {/* Team Switcher */}
        <SidebarMenu>
            <SidebarMenuItem>
            <DropdownMenu>
                <DropdownMenuTrigger asChild>
                <SidebarMenuButton
                    size="lg"
                    className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
                >
                    <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
                    <activeTeam.logo className="size-4" />
                    </div>
                    <div className="grid flex-1 text-left text-sm leading-tight">
                    <span className="truncate font-semibold">
                        {activeTeam.name}
                    </span>
                    <span className="truncate text-xs">
                        {activeTeam.plan}
                    </span>
                    </div>
                    <ChevronsUpDown className="ml-auto" />
                </SidebarMenuButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
                align="start"
                side={isMobile ? 'bottom' : 'right'}
                sideOffset={4}
                >
                <DropdownMenuLabel className="text-xs text-muted-foreground">
                    Teams
                </DropdownMenuLabel>
                {DATA.teams.map((team, index) => (
                    <DropdownMenuItem
                    key={team.name}
                    onClick={() => setActiveTeam(team)}
                    className="gap-2 p-2"
                    >
                    <div className="flex size-6 items-center justify-center rounded-sm border">
                        <team.logo className="size-4 shrink-0" />
                    </div>
                    {team.name}
                    <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
                    </DropdownMenuItem>
                ))}
                <DropdownMenuSeparator />
                <DropdownMenuItem className="gap-2 p-2">
                    <div className="flex size-6 items-center justify-center rounded-md border bg-background">
                    <Plus className="size-4" />
                    </div>
                    <div className="font-medium text-muted-foreground">
                    Add team
                    </div>
                </DropdownMenuItem>
                </DropdownMenuContent>
            </DropdownMenu>
            </SidebarMenuItem>
        </SidebarMenu>
        {/* Team Switcher */}
        </SidebarHeader>
        <SidebarContent>
        {/* Nav Main */}
        <SidebarGroup>
            <SidebarGroupLabel>Platform</SidebarGroupLabel>
            <SidebarMenu>
            {DATA.navMain.map((item) => (
                <Collapsible
                key={item.title}
                asChild
                defaultOpen={item.isActive}
                className="group/collapsible"
                >
                <SidebarMenuItem>
                    <CollapsibleTrigger asChild>
                    <SidebarMenuButton tooltip={item.title}>
                        {item.icon && <item.icon />}
                        <span>{item.title}</span>
                        <ChevronRight className="ml-auto transition-transform duration-300 group-data-[state=open]/collapsible:rotate-90" />
                    </SidebarMenuButton>
                    </CollapsibleTrigger>
                    <CollapsibleContent>
                    <SidebarMenuSub>
                        {item.items?.map((subItem) => (
                        <SidebarMenuSubItem key={subItem.title}>
                            <SidebarMenuSubButton asChild>
                            <a href={subItem.url}>
                                <span>{subItem.title}</span>
                            </a>
                            </SidebarMenuSubButton>
                        </SidebarMenuSubItem>
                        ))}
                    </SidebarMenuSub>
                    </CollapsibleContent>
                </SidebarMenuItem>
                </Collapsible>
            ))}
            </SidebarMenu>
        </SidebarGroup>
        {/* Nav Main */}
        {/* Nav Project */}
        <SidebarGroup className="group-data-[collapsible=icon]:hidden">
            <SidebarGroupLabel>Projects</SidebarGroupLabel>
            <SidebarMenu>
            {DATA.projects.map((item) => (
                <SidebarMenuItem key={item.name}>
                <SidebarMenuButton asChild>
                    <a href={item.url}>
                    <item.icon />
                    <span>{item.name}</span>
                    </a>
                </SidebarMenuButton>
                <DropdownMenu>
                    <DropdownMenuTrigger asChild>
                    <SidebarMenuAction showOnHover>
                        <MoreHorizontal />
                        <span className="sr-only">More</span>
                    </SidebarMenuAction>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent
                    className="w-48 rounded-lg"
                    side={isMobile ? 'bottom' : 'right'}
                    align={isMobile ? 'end' : 'start'}
                    >
                    <DropdownMenuItem>
                        <Folder className="text-muted-foreground" />
                        <span>View Project</span>
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                        <Forward className="text-muted-foreground" />
                        <span>Share Project</span>
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem>
                        <Trash2 className="text-muted-foreground" />
                        <span>Delete Project</span>
                    </DropdownMenuItem>
                    </DropdownMenuContent>
                </DropdownMenu>
                </SidebarMenuItem>
            ))}
            <SidebarMenuItem>
                <SidebarMenuButton className="text-sidebar-foreground/70">
                <MoreHorizontal className="text-sidebar-foreground/70" />
                <span>More</span>
                </SidebarMenuButton>
            </SidebarMenuItem>
            </SidebarMenu>
        </SidebarGroup>
        {/* Nav Project */}
        </SidebarContent>
        <SidebarFooter>
        {/* Nav User */}
        <SidebarMenu>
            <SidebarMenuItem>
            <DropdownMenu>
                <DropdownMenuTrigger asChild>
                <SidebarMenuButton
                    size="lg"
                    className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
                >
                    <Avatar className="h-8 w-8 rounded-lg">
                    <AvatarImage
                        src={DATA.user.avatar}
                        alt={DATA.user.name}
                    />
                    <AvatarFallback className="rounded-lg">CN</AvatarFallback>
                    </Avatar>
                    <div className="grid flex-1 text-left text-sm leading-tight">
                    <span className="truncate font-semibold">
                        {DATA.user.name}
                    </span>
                    <span className="truncate text-xs">
                        {DATA.user.email}
                    </span>
                    </div>
                    <ChevronsUpDown className="ml-auto size-4" />
                </SidebarMenuButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
                side={isMobile ? 'bottom' : 'right'}
                align="end"
                sideOffset={4}
                >
                <DropdownMenuLabel className="p-0 font-normal">
                    <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
                    <Avatar className="h-8 w-8 rounded-lg">
                        <AvatarImage
                        src={DATA.user.avatar}
                        alt={DATA.user.name}
                        />
                        <AvatarFallback className="rounded-lg">
                        CN
                        </AvatarFallback>
                    </Avatar>
                    <div className="grid flex-1 text-left text-sm leading-tight">
                        <span className="truncate font-semibold">
                        {DATA.user.name}
                        </span>
                        <span className="truncate text-xs">
                        {DATA.user.email}
                        </span>
                    </div>
                    </div>
                </DropdownMenuLabel>
                <DropdownMenuSeparator />
                <DropdownMenuGroup>
                    <DropdownMenuItem>
                    <Sparkles />
                    Upgrade to Pro
                    </DropdownMenuItem>
                </DropdownMenuGroup>
                <DropdownMenuSeparator />
                <DropdownMenuGroup>
                    <DropdownMenuItem>
                    <BadgeCheck />
                    Account
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                    <CreditCard />
                    Billing
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                    <Bell />
                    Notifications
                    </DropdownMenuItem>
                </DropdownMenuGroup>
                <DropdownMenuSeparator />
                <DropdownMenuItem>
                    <LogOut />
                    Log out
                </DropdownMenuItem>
                </DropdownMenuContent>
            </DropdownMenu>
            </SidebarMenuItem>
        </SidebarMenu>
        {/* Nav User */}
        </SidebarFooter>
        <SidebarRail />
    </Sidebar>
    <SidebarInset>
        <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
        <div className="flex items-center gap-2 px-4">
            <SidebarTrigger className="-ml-1" />
            <Separator orientation="vertical" className="mr-2 h-4" />
            <Breadcrumb>
            <BreadcrumbList>
                <BreadcrumbItem className="hidden md:block">
                <BreadcrumbLink href="#">
                    Building Your Application
                </BreadcrumbLink>
                </BreadcrumbItem>
                <BreadcrumbSeparator className="hidden md:block" />
                <BreadcrumbItem>
                <BreadcrumbPage>Data Fetching</BreadcrumbPage>
                </BreadcrumbItem>
            </BreadcrumbList>
            </Breadcrumb>
        </div>
        </header>
        <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
        <div className="grid auto-rows-min gap-4 md:grid-cols-3">
            <div className="aspect-video rounded-xl bg-muted/50" />
            <div className="aspect-video rounded-xl bg-muted/50" />
            <div className="aspect-video rounded-xl bg-muted/50" />
        </div>
        <div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
        </div>
    </SidebarInset>
    </SidebarProvider>
);
}
改成你要的功能 😂,這個可以變化的地方太多了
也可以至 Shadcn UI 中的 Blocks 來看看不同種類的 Sidebar,搞不好看完後會讓你更有想法~

這次介紹了 Animate UI 的 Sidebar 元件,讓你體驗到「側邊欄」其實也能很有戲。
不論是固定、可收合,還是動態出場,都能透過簡單的設定實現!
透過 shadcn CLI 的安裝與調整,你也可以輕鬆打造出既俐落又有互動感的 Sidebar,讓你的介面不只是好看,還 hen~ 好用 💪
喜歡gpt的簡潔(畢竟也看了一年多(?

為何流汗 但昨天朋友說g有送2T是否要轉台了呢