npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ippis/app-layout-theme

v1.0.2

Published

A modern React component library with AppShell, built with TypeScript, Tailwind CSS, and Radix UI

Readme

@ippis/app-layout-theme

A modern, feature-rich React component library built with TypeScript, Tailwind CSS, and Radix UI. This library provides a complete application shell (AppShell) with sidebar navigation, top bar, user menu, and more.

Features

  • 🎨 Modern Design: Built with Tailwind CSS and Radix UI primitives
  • 📱 Responsive: Mobile-first design with responsive sidebar
  • 🌓 Theme Support: Built-in light/dark mode toggle
  • Accessible: Built on Radix UI for full accessibility support
  • 🔧 TypeScript: Fully typed for better developer experience
  • 🎯 Customizable: Highly configurable with props and theming options

Installation

npm install @ippis/app-layout-theme
# or
yarn add @ippis/app-layout-theme
# or
pnpm add @ippis/app-layout-theme

Peer Dependencies

This library requires the following peer dependencies:

npm install react react-dom react-router-dom

Quick Start

1. Install Tailwind CSS

This library uses Tailwind CSS. Make sure you have Tailwind configured in your project:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

2. Configure Tailwind

Add the library's styles to your tailwind.config.js:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@ippis/app-layout-theme/dist/**/*.{js,ts,jsx,tsx}", // Add this
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

3. Import Styles

Import the library's CSS in your main entry file:

// src/main.tsx or src/index.tsx
import "@ippis/app-layout-theme/dist/style.css";
// or if using a CSS bundler
import "@ippis/app-layout-theme/dist/index.css";

4. Use the AppShell Component

import { AppShell, ShellMenuItem } from "@ippis/app-layout-theme";
import { LayoutDashboard, Users, Settings } from "lucide-react";
import { BrowserRouter } from "react-router-dom";

function App() {
  const menus: ShellMenuItem[] = [
    {
      id: "dashboard",
      label: "Dashboard",
      icon: LayoutDashboard,
      to: "/dashboard",
    },
    {
      id: "users",
      label: "Users",
      icon: Users,
      to: "/users",
    },
    {
      id: "settings",
      label: "Settings",
      icon: Settings,
      to: "/settings",
    },
  ];

  return (
    <BrowserRouter>
      <AppShell
        title="My App"
        subtitle="Application Description"
        menus={menus}
        user={{
          name: "John Doe",
          email: "[email protected]",
        }}
        theme={{ initialMode: "light" }}
      >
        <div className="p-6">
          <h1>Welcome to My App</h1>
          {/* Your app content */}
        </div>
      </AppShell>
    </BrowserRouter>
  );
}

export default App;

API Reference

AppShell

The main component that provides the application shell layout with sidebar, top bar, and footer.

Props

All available properties for the AppShell component:

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | children | ReactNode | ✅ Yes | - | Main content area rendered inside the shell | | menus | ShellMenuItem[] | ✅ Yes | - | Array of menu items for sidebar navigation. See ShellMenuItem type below. | | title | string | No | - | Application title displayed in the sidebar header | | subtitle | string | No | - | Application subtitle displayed below the title | | logo | ReactNode | No | - | Custom logo component to display in the sidebar. Can be an image, SVG, or any React component | | user | ShellUser | No | - | User information object for the user menu in the top bar. See ShellUser type below | | search | ShellSearchConfig | No | - | Search configuration object. When provided, enables the search bar in the top bar. See ShellSearchConfig type below | | showSearch | boolean | No | true | Show or hide the search bar. Set to false to hide search even if search prop is provided | | institutions | ShellInstitution[] | No | - | Array of institution objects for the institution selector dropdown. See ShellInstitution type below | | selectedInstitutionId | string | No | - | ID of the currently selected institution. Must match one of the IDs in the institutions array | | onInstitutionChange | (institutionId: string) => void | No | - | Callback function called when user selects a different institution. Receives the new institution ID | | institutionPlaceholder | string | No | - | Placeholder text for the institution selector when no institution is selected | | showInstitutionSelector | boolean | No | true | Show or hide the institution selector in the top bar | | quickActions | QuickAction[] | No | - | Array of quick action buttons displayed in the top bar. See QuickAction type below | | appLauncherItems | AppLauncherItem[] | No | - | Array of items for the app launcher menu. See AppLauncherItem type below | | sidebarHeader | ReactNode | No | - | Custom content to display in the sidebar header. Overrides the default header with title/subtitle | | sidebarFooter | ReactNode | No | - | Custom content to display in the sidebar footer (below menu items) | | footer | ReactNode | No | - | Custom footer content. Note: Use footerContent instead for better control | | footerContent | ReactNode | No | - | Custom footer content that replaces the default footer. If not provided, shows default footer with copyright and links | | showFooter | boolean | No | true | Show or hide the footer at the bottom of the shell | | pagination | PaginationConfig | No | - | Pagination configuration object. When provided, displays pagination controls above the footer. See PaginationConfig type below | | theme | ShellThemeOptions | No | - | Theme configuration object for light/dark mode. See ShellThemeOptions type below | | actions | ReactNode | No | - | Custom action buttons or components to display in the top bar (next to quick actions) | | linkComponent | ComponentType<ShellLinkComponentProps> | No | - | Custom link component for menu navigation. Useful if you're using a different routing library. See ShellLinkComponentProps type below | | onMenuSelect | (item: ShellMenuItem) => void | No | - | Callback function called when a menu item is clicked. Receives the clicked menu item object | | className | string | No | - | Additional CSS classes to apply to the root AppShell container |

Type Definitions

ShellMenuItem

Menu item configuration for sidebar navigation. Supports nested menus with children.

type ShellMenuItem = {
  id: string;                    // Unique identifier for the menu item (required)
  label: string;                 // Display text for the menu item (required)
  description?: string;          // Optional description text shown below the label
  icon?: ComponentType<{ className?: string }>; // Optional icon component (e.g., from lucide-react)
  to?: string;                   // React Router path for internal navigation
  href?: string;                 // External URL for external links
  onSelect?: () => void;         // Callback function called when item is clicked
  badge?: ReactNode;             // Optional badge component (e.g., notification count)
  disabled?: boolean;            // Disable the menu item (default: false)
  children?: ShellMenuItem[];    // Nested menu items for sub-menus
  meta?: Record<string, unknown>; // Optional metadata object for custom data
};

Example:

const menus: ShellMenuItem[] = [
  {
    id: "dashboard",
    label: "Dashboard",
    icon: LayoutDashboard,
    to: "/dashboard",
  },
  {
    id: "settings",
    label: "Settings",
    icon: Settings,
    children: [
      {
        id: "settings-profile",
        label: "Profile",
        to: "/settings/profile",
      },
      {
        id: "settings-account",
        label: "Account",
        to: "/settings/account",
      },
    ],
  },
];

ShellUser

User information object for the user menu in the top bar.

type ShellUser = {
  name: string;                  // User's full name (required)
  email?: string;                // User's email address
  avatarUrl?: string;            // URL to user's avatar image
  extras?: ReactNode;            // Additional content to display in user menu header
  menuItems?: ShellUserMenuItem[]; // Custom menu items for the user dropdown
};

Example:

const user: ShellUser = {
  name: "John Doe",
  email: "[email protected]",
  avatarUrl: "/avatars/john.jpg",
  menuItems: [
    {
      id: "profile",
      label: "Profile",
      icon: User,
      href: "/profile",
    },
    {
      id: "logout",
      label: "Log out",
      icon: LogOut,
      onSelect: () => handleLogout(),
      danger: true,
    },
  ],
};

ShellUserMenuItem

Menu item for the user dropdown menu.

type ShellUserMenuItem = {
  id: string;                    // Unique identifier (required)
  label: string;                 // Display text (required)
  icon?: ComponentType<{ className?: string }>; // Optional icon
  description?: string;          // Optional description
  href?: string;                 // External URL
  onSelect?: () => void;         // Callback when clicked
  danger?: boolean;              // Style as dangerous action (red text)
};

ShellSearchConfig

Configuration object for the search functionality.

type ShellSearchConfig = {
  placeholder?: string;          // Placeholder text for search input (default: "Search...")
  value?: string;                // Controlled search value
  defaultValue?: string;         // Uncontrolled default value
  debounceMs?: number;           // Debounce delay for onChange in milliseconds
  onChange?: (value: string) => void; // Callback when search value changes
  onSubmit?: (value: string) => void;  // Callback when user submits search (Enter key)
  render?: (props: ShellSearchRenderProps) => ReactNode; // Custom render function
};

Example:

const searchConfig: ShellSearchConfig = {
  placeholder: "Search modules, actions or pages",
  value: searchTerm,
  onChange: (value) => setSearchTerm(value),
  onSubmit: (value) => {
    console.log("Searching for:", value);
    // Perform search
  },
  debounceMs: 300,
};

ShellInstitution

Institution object for the institution selector.

type ShellInstitution = {
  id: string;                    // Unique identifier (required)
  name: string;                  // Full institution name (required)
  acronym: string;               // Short acronym (required)
};

Example:

const institutions: ShellInstitution[] = [
  {
    id: "1",
    name: "Ministry of Public Service and Labour",
    acronym: "MIFOTRA",
  },
  {
    id: "2",
    name: "Ministry of Finance and Economic Planning",
    acronym: "MINECOFIN",
  },
];

QuickAction

Quick action button configuration for the top bar.

type QuickAction = {
  id: string;                    // Unique identifier (required)
  icon: ComponentType<{ className?: string }>; // Icon component (required)
  label?: string;                // Optional label text
  tooltip?: string;              // Tooltip text shown on hover
  onSelect?: () => void;         // Callback when clicked
  panel?: ReactNode;             // Panel content shown when action is clicked (sheet/modal)
  variant?: "default" | "primary"; // Visual variant (default: "default")
};

Example:

const quickActions: QuickAction[] = [
  {
    id: "notifications",
    icon: Bell,
    label: "Notifications",
    tooltip: "View notifications",
    panel: <NotificationsPanel />,
  },
  {
    id: "help",
    icon: HelpCircle,
    label: "Help",
    tooltip: "Get help",
    panel: <HelpPanel />,
    variant: "primary",
  },
];

AppLauncherItem

Item for the app launcher menu.

type AppLauncherItem = {
  id: string;                    // Unique identifier (required)
  label: string;                 // Display text (required)
  icon?: ComponentType<{ className?: string }>; // Optional icon
  description?: string;          // Optional description
  href?: string;                 // URL to navigate to
  onSelect?: () => void;         // Callback when clicked
};

Example:

const appLauncherItems: AppLauncherItem[] = [
  {
    id: "dashboard",
    label: "Dashboard",
    icon: LayoutDashboard,
    href: "/dashboard",
  },
  {
    id: "analytics",
    label: "Analytics",
    icon: BarChart,
    description: "View analytics and reports",
    href: "/analytics",
  },
];

PaginationConfig

Pagination configuration object.

type PaginationConfig = {
  page: number;                        // Current page number (1-indexed)
  totalPages: number;                  // Total number of pages
  onChange: (page: number) => void;    // Callback when page changes
  label?: string;                      // Optional label text
  pageSize?: number;                   // Current page size
  pageSizeOptions?: number[];          // Available page sizes
  onPageSizeChange?: (size: number) => void; // Callback when size changes
  totalItems?: number;                 // Total number of records
  showNumbers?: boolean;               // Toggle numbered buttons
  isLoading?: boolean;                 // Disable controls + show spinner
};

Example:

const pagination: PaginationConfig = {
  page: currentPage,
  totalPages,
  onChange: (page) => {
    setCurrentPage(page);
    fetchData({ page, size: pageSize });
  },
  label: "Applications",
  pageSize,
  pageSizeOptions: [10, 25, 50],
  onPageSizeChange: (size) => {
    setPageSize(size);
    fetchData({ page: 1, size });
  },
  totalItems,
  isLoading,
};

Using useShellPagination

For most screens, you can register pagination controls via the useShellPagination hook instead of manually passing pagination props to AppShell.

import { useEffect, useState } from "react";
import { useShellPagination } from "@/theme/shell";

export const ApplicationsScreen = () => {
  const [rows, setRows] = useState([]);

  const {
    page,
    pageSize,
    setTotalItems,
    setLoading,
    isLoading,
  } = useShellPagination({
    initialPageSize: 20,
    pageSizeOptions: [10, 20, 50],
    onPageChange: (nextPage, size) => {
      fetchApplications(nextPage, size);
    },
    onPageSizeChange: (_, size) => {
      fetchApplications(1, size);
    },
  });

  const fetchApplications = async (nextPage = page, size = pageSize) => {
    setLoading(true);
    try {
      const response = await api.getApplications({ page: nextPage, size });
      setRows(response.items);
      setTotalItems(response.total);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchApplications(page, pageSize);
  }, [page, pageSize]);

  return <ApplicationsTable data={rows} loading={isLoading} />;
};

Key tips:

  • Mounting useShellPagination automatically renders the footer controls; unmounting removes them.
  • Call setTotalItems when the backend returns the total count so the footer can display the range.
  • Use setLoading (or pass the loading option) to show the spinner and temporarily disable controls during fetches.

ShellThemeOptions

Theme configuration for light/dark mode.

type ShellThemeOptions = {
  initialMode?: "light" | "dark"; // Initial theme mode (default: "light")
  onModeChange?: (mode: "light" | "dark") => void; // Callback when theme changes
};

Example:

const theme: ShellThemeOptions = {
  initialMode: "dark",
  onModeChange: (mode) => {
    console.log("Theme changed to:", mode);
    // Save theme preference
    localStorage.setItem("theme", mode);
  },
};

ShellLinkComponentProps

Props for custom link components.

type ShellLinkComponentProps = {
  to?: string;                   // React Router path
  href?: string;                 // External URL
  className?: string;            // CSS classes
  children: ReactNode;           // Link content (required)
  onClick?: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void; // Click handler
};

Example:

// Custom link component for Next.js
const CustomLink = ({ to, href, className, children, onClick }: ShellLinkComponentProps) => {
  if (to) {
    return (
      <Link href={to} className={className} onClick={onClick}>
        {children}
      </Link>
    );
  }
  return (
    <a href={href} className={className} onClick={onClick}>
      {children}
    </a>
  );
};

Examples

Complete Example with All Properties

Here's a comprehensive example showing all available properties:

import { useState } from "react";
import { AppShell, ShellMenuItem } from "@ippis/app-layout-theme";
import { 
  LayoutDashboard, 
  Users, 
  Settings, 
  FileText,
  Bell,
  HelpCircle,
  User,
  LogOut,
  BarChart
} from "lucide-react";
import { BrowserRouter } from "react-router-dom";
import { Button } from "@/components/ui/button"; // Adjust import path as needed

function App() {
  const [searchTerm, setSearchTerm] = useState("");
  const [currentPage, setCurrentPage] = useState(1);
  const [selectedInstitution, setSelectedInstitution] = useState("1");

  // Menu configuration
  const menus: ShellMenuItem[] = [
    {
      id: "dashboard",
      label: "Dashboard",
      icon: LayoutDashboard,
      to: "/dashboard",
      badge: "New",
    },
    {
      id: "users",
      label: "Users",
      icon: Users,
      to: "/users",
      children: [
        {
          id: "users-active",
          label: "Active Users",
          to: "/users/active",
        },
        {
          id: "users-pending",
          label: "Pending",
          to: "/users/pending",
          badge: 5,
        },
      ],
    },
    {
      id: "documents",
      label: "Documents",
      icon: FileText,
      to: "/documents",
    },
    {
      id: "settings",
      label: "Settings",
      icon: Settings,
      to: "/settings",
      disabled: false,
    },
  ];

  // Institution configuration
  const institutions = [
    {
      id: "1",
      name: "Ministry of Public Service and Labour",
      acronym: "MIFOTRA",
    },
    {
      id: "2",
      name: "Ministry of Finance and Economic Planning",
      acronym: "MINECOFIN",
    },
  ];

  // User configuration
  const user = {
    name: "John Doe",
    email: "[email protected]",
    avatarUrl: "/avatars/john.jpg",
    menuItems: [
      {
        id: "profile",
        label: "Profile",
        icon: User,
        href: "/profile",
      },
      {
        id: "logout",
        label: "Log out",
        icon: LogOut,
        onSelect: () => {
          console.log("Logging out...");
          // Handle logout
        },
        danger: true,
      },
    ],
  };

  // Quick actions configuration
  const quickActions = [
    {
      id: "notifications",
      icon: Bell,
      label: "Notifications",
      tooltip: "View notifications",
      panel: (
        <div className="p-4 space-y-2">
          <h3 className="font-semibold mb-2">Notifications</h3>
          <div className="space-y-2">
            <div className="p-2 bg-slate-100 dark:bg-slate-800 rounded">
              <p className="text-sm">New message received</p>
              <p className="text-xs text-slate-500">2 minutes ago</p>
            </div>
            <div className="p-2 bg-slate-100 dark:bg-slate-800 rounded">
              <p className="text-sm">Task assigned to you</p>
              <p className="text-xs text-slate-500">1 hour ago</p>
            </div>
          </div>
        </div>
      ),
    },
    {
      id: "help",
      icon: HelpCircle,
      label: "Help",
      tooltip: "Get help and support",
      panel: (
        <div className="p-4">
          <h3 className="font-semibold mb-2">Help & Support</h3>
          <p className="text-sm text-slate-600 dark:text-slate-400">
            Need assistance? Contact our support team or check the documentation.
          </p>
          <Button className="mt-4" variant="outline">
            Contact Support
          </Button>
        </div>
      ),
      variant: "primary" as const,
    },
  ];

  // App launcher items
  const appLauncherItems = [
    {
      id: "dashboard",
      label: "Dashboard",
      icon: LayoutDashboard,
      href: "/dashboard",
    },
    {
      id: "analytics",
      label: "Analytics",
      icon: BarChart,
      description: "View analytics",
      href: "/analytics",
    },
  ];

  return (
    <BrowserRouter>
      <AppShell
        // Required props
        menus={menus}
        
        // Basic configuration
        title="My Application"
        subtitle="Application Description"
        logo={<img src="/logo.png" alt="Logo" />}
        
        // User configuration
        user={user}
        
        // Search configuration
        search={{
          placeholder: "Search modules, actions or pages",
          value: searchTerm,
          onChange: setSearchTerm,
          onSubmit: (value) => {
            console.log("Searching for:", value);
            // Perform search
          },
          debounceMs: 300,
        }}
        showSearch={true}
        
        // Institution selector
        institutions={institutions}
        selectedInstitutionId={selectedInstitution}
        onInstitutionChange={(id) => {
          setSelectedInstitution(id);
          console.log("Institution changed to:", id);
        }}
        institutionPlaceholder="Select institution"
        showInstitutionSelector={true}
        
        // Quick actions
        quickActions={quickActions}
        
        // App launcher
        appLauncherItems={appLauncherItems}
        
        // Custom sidebar content
        sidebarHeader={
          <div className="p-4 border-b border-slate-200 dark:border-slate-800">
            <div className="flex items-center gap-2">
              <div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center text-white font-bold">
                ALT
              </div>
              <div>
                <h2 className="font-semibold text-sm">App Layout Theme</h2>
                <p className="text-xs text-slate-500">Custom Header</p>
              </div>
            </div>
          </div>
        }
        sidebarFooter={
          <div className="p-4 space-y-2">
            <Button
              variant="outline"
              size="sm"
              className="w-full justify-center gap-2"
              onClick={() => window.open("https://github.com", "_blank")}
            >
              <HelpCircle className="h-4 w-4" />
              Support
            </Button>
          </div>
        }
        
        // Footer configuration
        footerContent={
          <footer className="text-sm text-center py-3 text-slate-600 dark:text-slate-400">
            © 2025 @ippis/app-layout-theme |{" "}
            <a
              href="#"
              className="hover:underline text-blue-600 dark:text-blue-400"
              onClick={(e) => {
                e.preventDefault();
                alert("Privacy policy");
              }}
            >
              Privacy
            </a>{" "}
            |{" "}
            <a
              href="#"
              className="hover:underline text-blue-600 dark:text-blue-400"
              onClick={(e) => {
                e.preventDefault();
                alert("Terms of service");
              }}
            >
              Terms
            </a>
          </footer>
        }
        showFooter={true}
        
        // Pagination
        pagination={{
          page: currentPage,
          totalPages: 10,
          onChange: (page) => {
            setCurrentPage(page);
            // Fetch data for new page
          },
          label: "Page",
        }}
        
        // Theme configuration
        theme={{
          initialMode: "light",
          onModeChange: (mode) => {
            console.log("Theme changed to:", mode);
            localStorage.setItem("theme", mode);
          },
        }}
        
        // Custom actions
        actions={
          <Button
            variant="secondary"
            size="sm"
            onClick={() => alert("Custom action clicked!")}
          >
            Quick Action
          </Button>
        }
        
        // Menu callbacks
        onMenuSelect={(item) => {
          console.log("Menu item selected:", item);
        }}
        
        // Styling
        className="custom-app-shell"
      >
        <div className="p-6 space-y-6">
          <div>
            <h1 className="text-3xl font-bold mb-2">Welcome to My Application</h1>
            <p className="text-slate-600 dark:text-slate-400">
              This is a comprehensive demonstration of all AppShell properties.
              The AppShell component provides a complete application shell with sidebar,
              top bar, user menu, search, and more.
            </p>
          </div>

          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            <div className="p-6 bg-white dark:bg-slate-800 rounded-lg shadow border border-slate-200 dark:border-slate-700">
              <div className="flex items-center gap-3 mb-3">
                <div className="w-10 h-10 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
                  <LayoutDashboard className="h-5 w-5 text-blue-600 dark:text-blue-400" />
                </div>
                <h2 className="text-xl font-semibold">Dashboard</h2>
              </div>
              <p className="text-slate-600 dark:text-slate-400">
                View your application dashboard with key metrics and insights.
              </p>
            </div>

            <div className="p-6 bg-white dark:bg-slate-800 rounded-lg shadow border border-slate-200 dark:border-slate-700">
              <div className="flex items-center gap-3 mb-3">
                <div className="w-10 h-10 bg-green-100 dark:bg-green-900 rounded-lg flex items-center justify-center">
                  <Users className="h-5 w-5 text-green-600 dark:text-green-400" />
                </div>
                <h2 className="text-xl font-semibold">User Management</h2>
              </div>
              <p className="text-slate-600 dark:text-slate-400">
                Manage users, roles, and permissions with ease.
              </p>
            </div>

            <div className="p-6 bg-white dark:bg-slate-800 rounded-lg shadow border border-slate-200 dark:border-slate-700">
              <div className="flex items-center gap-3 mb-3">
                <div className="w-10 h-10 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center">
                  <FileText className="h-5 w-5 text-purple-600 dark:text-purple-400" />
                </div>
                <h2 className="text-xl font-semibold">Documents</h2>
              </div>
              <p className="text-slate-600 dark:text-slate-400">
                Access and manage your documents and files.
              </p>
            </div>
          </div>

          <div className="p-6 bg-white dark:bg-slate-800 rounded-lg shadow border border-slate-200 dark:border-slate-700">
            <h2 className="text-xl font-semibold mb-4">Features Demonstrated</h2>
            <ul className="space-y-2 text-slate-600 dark:text-slate-400">
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Sidebar navigation with nested menus and badges
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Institution selector in the top bar
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Search functionality with debouncing
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                User menu with custom menu items
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Quick actions with panels (notifications, help)
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                App launcher menu
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Pagination controls
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Light/dark theme toggle
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Custom sidebar header and footer
              </li>
              <li className="flex items-center gap-2">
                <span className="w-2 h-2 bg-blue-600 rounded-full"></span>
                Custom footer content
              </li>
            </ul>
          </div>
        </div>
      </AppShell>
    </BrowserRouter>
  );
}

export default App;

Basic Usage

import { AppShell } from "@ippis/app-layout-theme";

<AppShell
  title="My App"
  menus={menus}
  user={{ name: "John Doe", email: "[email protected]" }}
>
  <div>Your content here</div>
</AppShell>

With Custom Logo

<AppShell
  title="My App"
  logo={<img src="/logo.png" alt="Logo" />}
  menus={menus}
>
  <div>Content</div>
</AppShell>

With Institution Selector

<AppShell
  title="My App"
  menus={menus}
  institutions={[
    { id: "1", name: "Organization 1", acronym: "ORG1" },
    { id: "2", name: "Organization 2", acronym: "ORG2" },
  ]}
  selectedInstitutionId="1"
  onInstitutionChange={(id) => console.log("Changed to:", id)}
>
  <div>Content</div>
</AppShell>

With Search

<AppShell
  title="My App"
  menus={menus}
  search={{
    placeholder: "Search...",
    value: searchTerm,
    onChange: setSearchTerm,
    onSubmit: (value) => {
      console.log("Searching for:", value);
    },
  }}
>
  <div>Content</div>
</AppShell>

With Quick Actions

import { Bell, HelpCircle } from "lucide-react";

<AppShell
  title="My App"
  menus={menus}
  quickActions={[
    {
      id: "notifications",
      icon: Bell,
      label: "Notifications",
      tooltip: "View notifications",
      panel: <NotificationsPanel />,
    },
    {
      id: "help",
      icon: HelpCircle,
      label: "Help",
      tooltip: "Get help",
      panel: <HelpPanel />,
    },
  ]}
>
  <div>Content</div>
</AppShell>

With Pagination

<AppShell
  title="My App"
  menus={menus}
  pagination={{
    page: currentPage,
    totalPages: 10,
    onChange: (page) => setCurrentPage(page),
  }}
>
  <div>Content</div>
</AppShell>

Custom Link Component

If you're using a different routing library:

import { Link } from "your-router-library";

const CustomLink = ({ to, href, className, children, onClick }) => {
  if (to) {
    return (
      <Link to={to} className={className} onClick={onClick}>
        {children}
      </Link>
    );
  }
  return (
    <a href={href} className={className} onClick={onClick}>
      {children}
    </a>
  );
};

<AppShell
  title="My App"
  menus={menus}
  linkComponent={CustomLink}
>
  <div>Content</div>
</AppShell>

Styling

The library uses Tailwind CSS for styling. You can customize the appearance by:

  1. CSS Variables: Override CSS variables in your global CSS:
:root {
  --primary: 226.6667 66.0550% 42.7451%;
  --primary-foreground: 0 0% 100%;
  /* ... other variables */
}
  1. Tailwind Config: Extend the theme in your tailwind.config.js:
export default {
  theme: {
    extend: {
      colors: {
        primary: "hsl(var(--primary))",
        // ... other colors
      },
    },
  },
}

TypeScript

The library is fully typed. Import types as needed:

import type {
  ShellMenuItem,
  ShellUser,
  ShellSearchConfig,
  AppShellProps,
} from "@ippis/app-layout-theme";

Browser Support

  • Chrome (last 2 versions)
  • Firefox (last 2 versions)
  • Safari (last 2 versions)
  • Edge (last 2 versions)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Publishing

If you want to publish this library to npm, see PUBLISHING.md for detailed instructions.

Quick steps:

  1. Update package.json with your repository URLs
  2. Build the library: npm run build:lib
  3. Test: npm publish --dry-run
  4. Publish: npm publish

Support

For issues and questions, please open an issue on the GitHub repository.