Shadcn UI - Sidebar Navigation

darkterminal avatar
GitHub Account@darkterminal
LanguageJAVASCRIPT
Published At2023-05-23 21:02:23

The Back Story about your Javascript Metaphor

I was super thrilled when I found out about Shadcn-UI, and guess what? It's NOT just another component library. It's a collection of reusable components that you can simply copy and paste into your app.

OhMyPunk! It's going to be so much fun creating a user interface with tools made by fellow Punks.

Punk is all about capitalism and freedom for those who believe in big things in the future for themselves.

Peek 2023-05-24 03-07

The javascript Story!

I don't want to beat around the bush too much this time because this is an extraordinary story for me.

To create a sidebar and collapsible menu using Shadcn UI, we need to define a type (yeah, let's use TypeScript, whatever you say!)

The Sidebar Definition

1// Filename: types/sidebar.ts
2export interface SubmenuItems {
3    label: string;
4    link: string;
5}
6
7export interface NavItems {
8    label: string;
9    icon: React.ReactElement;
10    link: string;
11    isParent: boolean;
12    subMenu?: SubmenuItems[];
13}
14
15export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
16    menus: NavItems[];
17}

With the above definition, we will achieve something unforgettable about the experience of creating sidebar navigation along with a collapsible menu using just an Array of Object.

Get The Accordion

After writing the sidebar definition, we need to copy & paste the accordion component from Shadcn UI using the following command:

1npx shadcn-ui add accordion

The Sidebar Component

OhMyPunk! Let me tell you a little story about this magical thing called the Sidebar! So, imagine this: I stumbled upon a whimsical place called Shadcn UI - Examples - Music, and it was like stepping into a sweet bakery full of delicious treats. Now, with a sprinkle of imagination and a dash of creativity, I took that Sidebar component and transformed it into something truly scrumptious, just like kneading dough for a delightful cake. It was like giving it a life of its own, ready to add a pinch of charm to any project. Oh, the wonders we can create with a little bit of inspiration and a touch of magic!

1// Filename: components/sidebar.tsx
2import { cn } from "@/lib/utils";
3import { Button, buttonVariants } from "@/components/ui/button";
4import { ScrollArea } from "@/components/ui/scroll-area";
5import { SidebarProps } from "@/types/sidebar";
6import {
7  Accordion,
8  AccordionContent,
9  AccordionItem,
10  AccordionTrigger,
11} from "@/components/ui/accordion"
12import Link from "next/link";
13
14export function Sidebar({ className, menus }: SidebarProps) {
15  return (
16    <div className={cn("pb-12", className)}>
17      <div className="space-y-4 py-4">
18        <div className="py-2">
19          <ScrollArea className="h-[300px] px-2">
20            <div className="space-y-1 p-2">
21              {menus?.map((menu, i) => {
22                if (menu.isParent) {
23                  return (
24                    <Accordion key={`${menu}-${i}`} type="single" collapsible className="w-full">
25                      <AccordionItem value="item-1" className="border-b-0">
26                        <AccordionTrigger className={buttonVariants({ size: 'sm', variant: 'ghost', align: 'flexBetween', className: 'hover:no-underline' })}>
27                          <span className="inline-flex items-center justify-center gap-1">{menu.icon} {menu.label}</span>
28                        </AccordionTrigger>
29                        <AccordionContent>
30                          {menu.subMenu?.map((subItem, subIndex) => (
31                            <Button
32                              key={`${subIndex}-${i}`}
33                              variant="ghost"
34                              size="sm"
35                              className="w-full justify-start font-normal"
36                            >
37                              &mdash; {subItem.label}
38                            </Button>
39                          ))}
40                        </AccordionContent>
41                      </AccordionItem>
42                    </Accordion>
43                  );
44                } else {
45                  return (
46                    <Link
47                      key={`${menu}-${i}`}
48                      className={buttonVariants({ size: 'sm', variant: 'ghost', align: 'flexLeft' })}
49                      href={menu.link}
50                    >
51                      <span className="inline-flex items-center justify-center gap-1">{menu.icon} {menu.label}</span>
52                    </Link>
53                  );
54                }
55              })}
56            </div>
57          </ScrollArea>
58        </div>
59      </div>
60    </div>
61  );
62}

The Little Secret Sauce

But hey, here's a little secret sauce! While we're on the topic, let me spill the beans about a little tweak I made. I couldn't resist giving some love to the button.tsx file nestled snugly in the cozy folder of components/ui. It was like giving it a fashionable makeover, adding a sprinkle of pizzazz and a dash of flair. Trust me, it's the kind of modification that makes you want to do a little happy dance. So, keep your eyes peeled for that delightful surprise when you delve into the magical world of code!

1// Filename: components/ui/button.tsx
2import * as React from "react"
3import { VariantProps, cva } from "class-variance-authority"
4
5import { cn } from "@/lib/utils"
6
7const buttonVariants = cva(
8  "rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
9  {
10    variants: {
11      variant: {
12        default: "bg-primary text-primary-foreground hover:bg-primary/90",
13        destructive:
14          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15        outline:
16          "border border-input hover:bg-accent hover:text-accent-foreground",
17        secondary:
18          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19        ghost: "hover:bg-accent hover:text-accent-foreground",
20        link: "underline-offset-4 hover:underline text-primary",
21      },
22      size: {
23        default: "h-10 py-2 px-4",
24        sm: "h-9 py-1.5 px-3 rounded-md",
25        lg: "h-11 px-8 rounded-md",
26      },
27      align: {
28        flexLeft: 'flex items-start justify-start',
29        flexCenter: 'flex items-center justify-center',
30        flexRight: 'flex items-end justify-end',
31        flexBetween: 'flex items-center justify-between',
32        inlineLeft: 'inline-flex items-start justify-start',
33        inlineCenter: 'inline-flex items-center justify-center',
34        inlineRight: 'inline-flex items-end justify-end',
35        inlineBetween: 'inline-flex items-center justify-between'
36      },
37      
38    },
39    defaultVariants: {
40      variant: "default",
41      size: "default",
42      align: 'flexCenter'
43    },
44  }
45)
46
47export interface ButtonProps
48  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
49    VariantProps<typeof buttonVariants> {}
50
51const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
52  ({ className, variant, size, ...props }, ref) => {
53    return (
54      <button
55        className={cn(buttonVariants({ variant, size, className }))}
56        ref={ref}
57        {...props}
58      />
59    )
60  }
61)
62Button.displayName = "Button"
63
64export { Button, buttonVariants }

Usage

Time to put our creation to the test and bring it to life! After following those steps above, it's now time to unleash our masterpiece into the wild. Get ready to work your magic story and implement what we've cooked up so far, just like following a scrumptious recipe. Remember, you're the master chef here, so trust your instincts and let your creativity shine. Brace yourself for the excitement as you witness your creation come alive in all its glory.

1// Filename: app/page.tsx
2import { Icons } from "@/components/icons"
3import { Sidebar } from "@/components/sidebar"
4import { NavItems } from "@/types/sidebar"
5
6const menus: NavItems[] = [
7  {
8    label: "Home",
9    icon: <Icons.home className="w-4 h-4" />,
10    link: "/home",
11    isParent: false,
12  },
13  {
14    label: "About",
15    icon: <Icons.info className="w-4 h-4" />,
16    link: "/about",
17    isParent: false,
18  },
19  {
20    label: "Settings",
21    icon: <Icons.settings className="w-4 h-4" />,
22    link: "/settings",
23    isParent: true,
24    subMenu: [
25      {
26        label: "Profile",
27        link: "/settings/profile",
28      },
29      {
30        label: "Preferences",
31        link: "/settings/preferences",
32      },
33    ],
34  },
35]
36
37export default function IndexPage() {
38  return (
39    <div className="grid lg:grid-cols-5">
40      <Sidebar menus={menus} className="hidden lg:block" />
41      <div className="col-span-3 lg:col-span-4 lg:border-l">
42        <h1>Hello</h1>
43      </div>
44    </div>
45  )
46}

A Javascript demo/repos link

No response

PayPal Link for Donation (Javascript Storyteller)

No response

Share This Story