Blocks

Application Shell 11

App Shells
2 ColumnsTopbarSidebarButtonsSearch BarAvatar Image
Application Shell 11

Code

relume-application-shell11.tsx
"use client";

import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarInput,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar";
import {
  RelumeIcon,
  Archive,
  BarChart,
  Settings,
  Description,
  Help,
  Home,
  Layers,
  PieChart,
  Search,
  Star,
  TrendingUp,
  ChevronRight,
  Close,
  Notifications,
} from "relume-icons";

const menuItems = [
  { title: "Home", url: "#", icon: Home },
  { title: "Saved", url: "#", icon: Star, badge: "24" },
  {
    title: "Dashboard",
    url: "#",
    icon: PieChart,
    subItems: [
      { title: "Trends", icon: TrendingUp, url: "#" },
      { title: "Analytics", icon: BarChart, url: "#" },
      { title: "Historical", icon: Archive, url: "#" },
    ],
  },
  { title: "Projects", url: "#", icon: Layers },
  { title: "Documents", url: "#", icon: Description },
];

const footerItems = [
  { title: "Support", url: "#", icon: Help },
  { title: "Settings", url: "#", icon: Settings },
];

export const ApplicationShell11 = () => (
  <AppSidebar>
    <main className="bg-scheme-foreground lg:pt-18 flex-1 pt-16">
      <Topbar />
      <div className="text-neutral-darkest/50 border-b-2 border-dashed border-[#d3d3d3] py-6 text-center">
        <span>Click and paste Page Header</span>
      </div>
      <div className="container grid grid-cols-1 gap-12 px-6 py-8 md:grid-cols-[0.5fr_1fr] md:py-10 lg:px-8 lg:py-12">
        <span className="text-neutral-darkest/50 flex h-screen items-center justify-center border-2 border-dashed border-[#d3d3d3] text-center">
          Click and paste Secondary Content
        </span>
        <span className="text-neutral-darkest/50 flex h-screen items-center justify-center border-2 border-dashed border-[#d3d3d3] text-center">
          Click and paste Main Content
        </span>
      </div>
    </main>
  </AppSidebar>
);

const Topbar = () => {
  const [isSearchIconClicked, setIsSearchIconClicked] = useState(false);

  return (
    <header className="border-scheme-border md:min-h-18 fixed left-0 right-0 top-0 z-30 flex min-h-16 w-full items-center border-b bg-white px-4 md:px-8">
      <div className="mx-auto grid size-full grid-cols-2 items-center justify-between gap-4 lg:grid-cols-[1fr_1.5fr_1fr]">
        <div className="flex items-center gap-4">
          <SidebarTrigger className="lg:hidden" />
          <a href="#" className="justify-self-start">
            <img
              src="https://d22po4pjz3o32e.cloudfront.net/logo-image.svg"
              alt="Relume logo"
              className="shrink-0"
            />
          </a>
        </div>
        <div className="hidden lg:block lg:w-full lg:max-w-md lg:justify-self-center">
          <SidebarInput
            className="w-full"
            placeholder="Search"
            icon={<Search className="text-scheme-text size-6" />}
          />
        </div>
        <TopbarActions
          isSearchIconClicked={isSearchIconClicked}
          setIsSearchIconClicked={setIsSearchIconClicked}
        />
      </div>
      <AnimatePresence>
        {isSearchIconClicked && (
          <motion.div
            variants={{
              visible: { opacity: 1 },
              hidden: { opacity: 0 },
            }}
            initial="hidden"
            exit="hidden"
            animate={isSearchIconClicked ? "visible" : "hidden"}
            className="border-scheme-border absolute bottom-0 left-0 right-0 top-16 flex min-h-16 max-w-md items-center justify-center border-b bg-white px-6 lg:hidden"
          >
            <Input
              className="h-fit w-full"
              placeholder="Search"
              icon={<Search className="text-scheme-text size-6" />}
            />
            <button onClick={() => setIsSearchIconClicked(!isSearchIconClicked)}>
              <Close className="ml-4 size-6" />
            </button>
          </motion.div>
        )}
      </AnimatePresence>
    </header>
  );
};

const TopbarActions = ({
  isSearchIconClicked,
  setIsSearchIconClicked,
}: {
  isSearchIconClicked: boolean;
  setIsSearchIconClicked: (value: boolean) => void;
}) => {
  return (
    <div className="flex items-center gap-2 justify-self-end md:gap-4">
      <button
        onClick={() => setIsSearchIconClicked(!isSearchIconClicked)}
        className="p-2 lg:hidden"
      >
        <Search className="text-scheme-text size-6" />
      </button>
      <DropdownMenu>
        <DropdownMenuTrigger className="relative">
          <div className="bg-neutral-darkest absolute bottom-auto left-auto right-2 top-2 size-2 rounded-full outline-[3px] outline-offset-0 outline-white" />
          <Notifications className="text-scheme-text size-6" />
        </DropdownMenuTrigger>
        <DropdownMenuContent className="max-w-[19rem] px-0" align="end" sideOffset={0}>
          <div className="flex flex-col">
            <div className="flex items-center justify-between px-4 py-2">
              <DropdownMenuLabel className="p-0">Notifications</DropdownMenuLabel>
              <a href="#">Mark as read</a>
            </div>
            <DropdownMenuSeparator />
            <div className="h-full max-h-[14rem] overflow-auto px-2 py-1">
              <DropdownMenuItem className="mt-2 grid grid-cols-[max-content_1fr] gap-2 px-2 py-1">
                <div className="flex size-full flex-col items-start justify-start">
                  <RelumeIcon className="text-scheme-text size-6" />
                </div>
                <div>
                  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                  <p className="text-small mt-2">11 Jan 2022</p>
                </div>
              </DropdownMenuItem>
              <DropdownMenuItem className="mt-2 grid grid-cols-[max-content_1fr] gap-2 px-2 py-1">
                <div className="flex size-full flex-col items-start justify-start">
                  <RelumeIcon className="text-scheme-text size-6" />
                </div>
                <div>
                  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                  <p className="text-small mt-2">11 Jan 2022</p>
                </div>
              </DropdownMenuItem>
            </div>
          </div>
          <DropdownMenuSeparator />
          <div className="flex w-full items-end justify-end px-4 py-2">
            <Button
              variant="link"
              size="link"
              iconRight={<ChevronRight className="text-scheme-text" />}
              asChild
            >
              <a href="#">View All</a>
            </Button>
          </div>
        </DropdownMenuContent>
      </DropdownMenu>
      <DropdownMenu>
        <DropdownMenuTrigger className="flex items-center p-0">
          <img
            src="https://d22po4pjz3o32e.cloudfront.net/avatar-image.svg"
            alt="Avatar"
            className="size-10 rounded-full object-cover"
          />
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end" sideOffset={0} className="mt-1.5 px-0 py-2">
          <DropdownMenuGroup>
            <DropdownMenuItem>
              <a href="#">My Profile</a>
            </DropdownMenuItem>
            <DropdownMenuItem>
              <a href="#">Profile Settings</a>
            </DropdownMenuItem>
            <DropdownMenuSeparator className="mx-4" />
            <DropdownMenuItem>
              <a href="#">Log Out</a>
            </DropdownMenuItem>
          </DropdownMenuGroup>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  );
};

const AppSidebar = ({ children }: { children: React.ReactNode }) => (
  <SidebarProvider>
    <Sidebar className="py-6" closeButtonClassName="fixed top-4 right-4 text-white">
      <SidebarContent className="lg:pt-18 pt-6">
        <SidebarMenu>
          {menuItems.map((item, index) => (
            <SidebarMenuItem key={index}>
              {item.title === "Dashboard" ? (
                <Accordion type="single" collapsible className="w-full">
                  <AccordionItem value="dashboard" className="border-none">
                    <AccordionTrigger className="text-regular p-2 font-normal [&>svg]:size-4">
                      <span className="flex items-center gap-3">
                        <item.icon className="text-scheme-text size-6 shrink-0" />
                        <p>{item.title}</p>
                      </span>
                    </AccordionTrigger>
                    {item.subItems?.map((subItem, subIndex) => (
                      <AccordionContent
                        key={subIndex}
                        className="text-regular flex items-center p-2 pl-[2.75rem] text-center"
                      >
                        <a href={subItem.url} className="flex w-full items-center gap-3">
                          <subItem.icon className="size-6 shrink-0" />
                          <span>{subItem.title}</span>
                        </a>
                      </AccordionContent>
                    ))}
                  </AccordionItem>
                </Accordion>
              ) : (
                <SidebarMenuButton asChild>
                  <a href={item.url} className="flex w-full items-center gap-3 p-2">
                    <item.icon className="text-scheme-text size-6 shrink-0" />
                    <span>{item.title}</span>
                    {item.badge && (
                      <Badge variant="outline" className="ml-auto !rounded-[5rem] py-0">
                        {" "}
                        {item.badge}
                      </Badge>
                    )}
                  </a>
                </SidebarMenuButton>
              )}
            </SidebarMenuItem>
          ))}
        </SidebarMenu>
      </SidebarContent>
      <SidebarFooter className="mt-auto">
        <div>
          {footerItems.map((item, index) => (
            <SidebarMenuItem key={index}>
              <SidebarMenuButton asChild>
                <a href={item.url} className="flex w-full items-center gap-3 p-2">
                  <item.icon className="text-scheme-text size-6 shrink-0" />
                  <span>{item.title}</span>
                </a>
              </SidebarMenuButton>
            </SidebarMenuItem>
          ))}
        </div>
      </SidebarFooter>
    </Sidebar>
    {children}
  </SidebarProvider>
);

npm i @radix-ui/react-accordion @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tooltip class-variance-authority@0.7.1 clsx@^2.1.1 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2