Default Sidebar

A simple, modular sidebar for easy customization. Build your own sidebar by defining styles and routes.

Implementation

How to use this component in Astro.

custom-sidebar.tsx - typescript
import { ContentRoutes } from '@core/lib/routes'; // adjust import
import { Sidebar } from '@sidebar/default'; // adjust import

export const CleanSidebar = ({
    currentPath
}: {
    currentPath: string
}) => {
    return (
        <Sidebar currentPath={currentPath} >
            <Sidebar.Items className='gap-6'>
                {ContentRoutes.routeGroups.map((routeGroup) => (
                    <Sidebar.Items key={routeGroup.group}>
                        <Sidebar.Item group className='uppercase font-normal mb-2'>
                            <h3>{routeGroup.group}</h3>
                        </Sidebar.Item>

                        <Sidebar.Items className='gap-1'>
                            {routeGroup.routes.map((route) => (
                                <Sidebar.Item 
                                    key={route.route} 
                                    route={`${ContentRoutes.baseUrl}${routeGroup.group}/${route.route}/`} 
                                    className="capitalize"
                                >
                                    {route.name}
                                </Sidebar.Item>
                            ))}
                        </Sidebar.Items>
                    </Sidebar.Items>
                ))}
            </Sidebar.Items> 
        </Sidebar>
    );
};

Copy and paste the following code into your project.

Component for the sidebar.

sidebar.tsx - typescript
import { cn } from '@core/lib/utils'; // adjust import
import React from 'react';
import { SidebarContextProvider, useSidebarContext } from '@sidebar/default'; // adjust import

function SidebarContent({
    children,
    className
}: {
    children: React.ReactNode;
    className?: string;
}) {
    return (
        <div className={cn('min-w-fit min-h-fit h-full w-52 xl:w-80 flex flex-col justify-between select-none', className)}>
            {children}
        </div>
    );
}

function SidebarItems({
    children,
    className
}: {
    children?: React.ReactNode;
    className?: string;
}) {
    return (
        <div className={cn("flex flex-col cursor-pointer", className)}>
            {children}
        </div>
    );
}

function SidebarItem({
    children,
    className,
    route,
    group = false,
    ...other
}: {
    children?: React.ReactNode;
    className?: string;
    route?: string;
    group?: boolean;
    [x: string]: any;
}) {
    const { active } = useSidebarContext();
    const isActive = route ? active == route : group ? true : false
    return (
        <a {...other} href={route} className={cn(isActive ? "font-normal text-black" : "font-thin text-gray-600", "flex cursor-pointer" ,className)}>
            {children}
        </a>
    );
}

const SidebarComponent = ({
    children,
    className,
    currentPath,
}: {
    children: React.ReactNode;
    className?: string;
    currentPath: string;
}) => {
    return (
        <SidebarContextProvider currentPath={currentPath} >
            <SidebarContent className={className}>
                {children}
            </SidebarContent>
        </SidebarContextProvider>
    );
};

export const Sidebar = Object.assign(SidebarComponent, {
    Items: SidebarItems,
    Item: SidebarItem
});

export type RouteName = string
export interface Route {
    name: string;
    route: RouteName;
}
export interface RouteGroup{
    group:string,
    routes: Route[]
}
export type RouteMap = {
    baseUrl: string;
    routeGroup: RouteGroup[];
}

Copy and paste the following code into your project.

Use this sidebar context to share states or customize a render function to dynamically replace content within the content div for a single-page experience.

context/sidebar-context.tsx - typescript
import React, { createContext, useContext, useState } from 'react';
import type { RouteName } from '@sidebar/default'; // adjust import

interface SidebarContextProps {
    active: RouteName | undefined;
    handleChange: (routeName: RouteName) => void;
}

const SidebarContext = createContext<SidebarContextProps | null>(null);

export function SidebarContextProvider({
    children,
    currentPath,
}: {
    children: React.ReactNode;
    currentPath: string;
}) {

    const [active, setActive] = useState<RouteName | undefined>(currentPath);

    const handleChange = (routeName: RouteName) => {
        setActive(routeName);
    };

    return (
        <SidebarContext.Provider value={{ active, handleChange }}>
            {children}
        </SidebarContext.Provider>
    );
}

export const useSidebarContext = () => {
    const context = useContext(SidebarContext);
    if (!context) {
        throw new Error("useSidebarContext must be used within a SidebarContextProvider");
    }
    return context;
};

Define and map routes for your sidebar.

You can route with a slug or without, this just helps you map your existing route to the sidebar. Customize your own structure by changing the way you map your sidebar. But heres a general layout you can use.

routes/sidebar-routes.ts - typescript
export const ContentRoutes = {
    baseUrl:"/components/", // this is the base url for your routes, feel free to disregard
    routeGroups:[ // here you can define groups
        {
            group: "group1", // name of the group
            routes:[
                {
                    name: "route1", // here is the text you want to display to users
                    route: "route-1" // here is the route
                },
                {
                    name: "very long route",
                    route: "very-long-route" // you can change naming conventions for spaces
                },
            ]
        },
        {
            group:"group2",
            routes:[
                {
                    name: "route3",
                    route: "route-3"
                }
            ]
        }
    ]
}

Update imports.

Change the import paths to match your project.