Credenza

repository·main·Indexed 21 days ago

https://github.com/redpangilinan/credenza

A responsive modal component built for shadcn/ui that automatically switches between a Dialog (desktop) and a Drawer (mobile) based on a 768px breakpoint. It provides a set of root and content components including CredenzaTrigger, CredenzaContent, CredenzaHeader, CredenzaBody, and CredenzaFooter to ensure a consistent UI pattern across different screen sizes.

Tokens
4.1K
Snippets
12
Records
16
Agent score
75%

What's inside Credenza

  1. How Credenza works: Responsive Modal and Drawer

    main

    Credenza is a responsive component that automatically switches its UI pattern based on the screen size. It uses a MOBILE_BREAKPOINT of 768px to determine behavior:

    • Desktop (>= 768px): Renders as a standard Dialog (modal).
    • Mobile (< 768px): Renders as a Drawer (bottom sheet).

    It uses a CredenzaContext to share the isMobile state across all sub-components (CredenzaTrigger, CredenzaContent, etc.), ensuring that the entire component tree switches to the appropriate primitive (Dialog vs. Drawer) simultaneously.

  2. Install Credenza via shadcn registry (Recommended)

    main

    The easiest way to install Credenza is by using the shadcn CLI to add it directly from the registry. This handles the component installation automatically.

    pnpm dlx shadcn@latest add https://credenza.rdev.pro/r/credenza.json

    Or using npm:

    npx shadcn@latest add https://credenza.rdev.pro/r/credenza.json
  3. Manual Installation of Credenza

    main

    If you prefer manual installation, follow these steps:

    1. Install shadcn/ui dependencies: Copy the dialog and drawer components from shadcn/ui.

      npx shadcn@latest add dialog drawer

      If you are not using the CLI, manually copy dialog.tsx and drawer.tsx from shadcn/ui. If you use the drawer component, you must also install vaul:

      npm install vaul
    2. Add the useIsMobile hook: Copy the useIsMobile hook implementation to your hooks directory.

    3. Add the credenza component: Copy the credenza.tsx source code to your components directory.

    4. Configure imports: Update the import paths in credenza.tsx to match your project's directory structure (e.g., @/components/ui/... or @/hooks/...).

    5. Enable background scaling (Optional): To enable background scaling effects, wrap your application in a div with the vaul-drawer-wrapper attribute. Ensure the background color matches your theme.

      <div vaul-drawer-wrapper="" className="bg-background">{children}</div>
  4. Use Credenza with Controlled State

    main

    You can control the visibility of the Credenza component manually using the open and onOpenChange props. This is useful when you need to trigger the modal from external logic or buttons outside the Credenza component.

    import React from "react"
    import { Button } from "@/components/ui/button"
    import {
      Credenza,
      CredenzaClose,
      CredenzaContent,
      CredenzaDescription,
      CredenzaFooter,
      CredenzaHeader,
      CredenzaTitle,
    } from "@/components/ui/credenza"
    
    function StateModal() {
      const [open, setOpen] = React.useState(false)
    
      const handleOpen = () => {
        setOpen(true)
      }
    
      return (
        <>
          <Button onClick={handleOpen}>Open with State</Button>
    
          <Credenza open={open} onOpenChange={setOpen}>
            <CredenzaContent>
              <CredenzaHeader>
                <CredenzaTitle>Credenza</CredenzaTitle>
                <CredenzaDescription>
                  A responsive modal component for shadcn/ui.
                </CredenzaDescription>
              </CredenzaHeader>
              <CredenzaBody>This modal got triggered using state</CredenzaBody>
              <CredenzaFooter>
                <CredenzaClose asChild>
                  <Button>Close</Button>
                </CredenzaClose>
              </CredenzaFooter>
            </CredenzaContent>
          </Credenza>
        </>
      )
    }
  5. Use Credenza with a Trigger

    main

    The most common way to use Credenza is by providing a CredenzaTrigger. This allows the component to manage its own open/close state via the trigger element.

    import {
      Credenza,
      CredenzaBody,
      CredenzaClose,
      CredenzaContent,
      CredenzaDescription,
      CredenzaFooter,
      CredenzaHeader,
      CredenzaTitle,
      CredenzaTrigger,
    } from "@/components/ui/credenza"
    
    <Credenza>
      <CredenzaTrigger asChild>
        <button>Open modal</button>
      </CredenzaTrigger>
      <CredenzaContent>
        <CredenzaHeader>
          <CredenzaTitle>Credenza</CredenzaTitle>
          <CredenzaDescription>
            A responsive modal component for shadcn/ui.
          </CredenzaDescription>
        </CredenzaHeader>
        <CredenzaBody>
          This component is built using shadcn/ui&apos;s dialog and drawer
          component, which is built on top of Vaul.
        </CredenzaBody>
        <CredenzaFooter>
          <CredenzaClose asChild>
            <button>Close</button>
          </CredenzaClose>
        </CredenzaFooter>
      </CredenzaContent>
    </Credenza>
  6. Credenza Component API Reference

    main

    The following components make up the Credenza API. They all adapt their behavior based on whether the user is on mobile or desktop.

    Root Components

    • Credenza: The root provider. Accepts open?: boolean and onOpenChange?: (open: boolean) => void for controlled usage.
    • CredenzaTrigger: The element that triggers the modal/drawer. Supports asChild prop.
    • CredenzaClose: The element that closes the modal/drawer. Supports asChild prop.

    Content Components

    • CredenzaContent: The main container for the modal/drawer content.
    • CredenzaHeader: Container for header elements.
    • CredenzaTitle: The title text component.
    • CredenzaDescription: The description text component.
    • CredenzaBody: A specialized container for the main body content. It applies responsive padding: px-4 on mobile and md:px-0 on desktop.
    • CredenzaFooter: Container for footer elements.
  7. Prettier configuration for Credenza

    main

    Credenza uses Prettier for code formatting with specific rules for code style and import sorting. The configuration enforces lf line endings, removes semicolons, uses double quotes, and sets a tab width of 2. It also utilizes plugins to automatically sort imports and organize Tailwind CSS classes.

    Key formatting rules:

    • endOfLine: lf
    • semi: false
    • singleQuote: false
    • tabWidth: 2
    • trailingComma: es5
    module.exports = {
      endOfLine: "lf",
      semi: false,
      singleQuote: false,
      tabWidth: 2,
      trailingComma: "es5",
      // ...
    }
  8. Configure Button variants and sizes

    main

    The Button component provides the following visual configurations through its variant and size props:

    /* Variants */
    variant: {
      default: "bg-primary text-primary-foreground hover:bg-primary/90",
      destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
      outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
      secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
      ghost: "hover:bg-accent hover:text-accent-foreground",
      link: "text-primary underline-offset-4 hover:underline"
    }
    
    /* Sizes */
    size: {
      default: "h-10 px-4 py-2",
      sm: "h-9 rounded-md px-3",
      lg: "h-11 rounded-md px-8",
      icon: "h-10 w-10"
    }
  9. Import sorting order and plugins

    main

    The project uses @ianvs/prettier-plugin-sort-imports to enforce a specific import order. Imports are parsed using typescript, jsx, and decorators-legacy plugins.

    The defined import order is:

    1. react (and sub-paths)
    2. next (and sub-paths)
    3. <THIRD_PARTY_MODULES>
    4. (Empty line)
    5. types
    6. @/env
    7. @/types/*
    8. @/config/*
    9. @/lib/*
    10. @/hooks/*
    11. @/components/ui/*
    12. @/components/*
    13. @/styles/*
    14. @/app/*
    15. (Empty line)
    16. Relative imports ^[./]

    Additionally, prettier-plugin-tailwindcss is used to automatically sort Tailwind CSS classes.

    importOrder: [
      "^(react/(.*)$)|^(react$)",
      "^(next/(.*)$)|^(next$)",
      "<THIRD_PARTY_MODULES>",
      "",
      "^types$",
      "^@/env(.*)$",
      "^@/types/(.*)$",
      "^@/config/(.*)$",
      "^@/lib/(.*)$",
      "^@/hooks/(.*)$",
      "^@/components/ui/(.*)$",
      "^@/components/(.*)$",
      "^@/styles/(.*)$",
      "^@/app/(.*)$",
      "",
      "^[./]",
    ],
    importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"],
    plugins: [
      "@ianvs/prettier-plugin-sort-imports",
      "prettier-plugin-tailwindcss",
    ],
  10. Use the ThemeProvider for dark/light mode management

    main

    The ThemeProvider component is a client-side wrapper around next-themes used to manage and persist dark and light mode themes within the application. It accepts all standard ThemeProviderProps from the next-themes package, allowing you to configure attributes like attribute, defaultTheme, and enableSystem.

    import { ThemeProvider } from "./components/theme-provider"
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en" suppressHydrationWarning>
          <body>
            <ThemeProvider 
              attribute="class" 
              defaultTheme="system" 
              enableSystem
            >
              {children}
            </ThemeProvider>
          </body>
        </html>
      )
    }
  11. Use the Icons component for UI iconography

    main

    The Icons object provides a collection of Lucide React components mapped to specific keys. You can use these icons in your UI by accessing them via the Icons export. The available icon keys are logo, sun, moon, link, and coffee.

    import { Icons } from "./components/icons"
    
    function MyComponent() {
      return (
        <div>
          <Icons.logo className="w-4 h-4" />
          <Icons.sun />
        </div>
      )
    }
  12. Use the useIsMobile hook to detect mobile devices

    main

    The useIsMobile hook provides a boolean value indicating whether the current viewport width is below the defined mobile breakpoint. It uses a media query listener to reactively update the state when the window is resized.

    By default, the mobile breakpoint is set to 768px. The hook returns true if the window width is less than 768px, and false otherwise.

    import { useIsMobile } from "./hooks/use-mobile"
    
    function MyComponent() {
      const isMobile = useIsMobile()
    
      return (
        <div>
          {isMobile ? "You are on mobile" : "You are on desktop"}
        </div>
      )
    }