Drawer

A Dialog that slides in from the edge of the screen.

import Drawer from "@tailus-ui/Drawer";
import Button from "@tailus-ui/button/Button";
import { Text, Link } from "@tailus-ui/typography";

export const Overview = () => {
    return (
        <Drawer.Root shouldScaleBackground>
            <Drawer.Trigger asChild>
                <Button.Root variant="outlined" intent="gray">
                    <Button.Label>Open Drawer</Button.Label>
                </Button.Root>
            </Drawer.Trigger>
            <Drawer.Portal>
                <Drawer.Overlay />
                <Drawer.Content >
                    <div className="max-w-md mx-auto mt-6">
                        <Drawer.Title>Drawer for React</Drawer.Title> 
                        <Text className="my-3">
                            This component can be used as a Dialog replacement on mobile and tablet devices. You can read about why and how it was built {' '}
                            <Link href="https://emilkowal.ski/ui/building-a-drawer-component" target="_blank">here</Link>
                        </Text>
                        <Text className="mb-3">
                            It comes unstyled and has gesture-driven animations, and is made by {' '}
                            <Link href="https://emilkowal.ski" target="_blank">Emil</Link>
                        </Text>
                        <Text className="mb-8">
                            It uses {' '}
                            <Link href="https://www.radix-ui.com/docs/primitives/components/dialog" target="_blank">Radix's Dialog primitive</Link> {' '}
                            under the hood and is inspired by {' '}
                            <Link href="https://twitter.com/devongovett/status/1674470185783402496" target="_blank">this tweet.</Link>
                        </Text>
                    </div>
                </Drawer.Content>
            </Drawer.Portal>
        </Drawer.Root>
    )
}

Installation

Install the primitive and Copy-Paste the component code in a .tsx file.

npm install vaul
import { Drawer as Primitive } from "vaul"
import React from "react";
import {drawer , type DrawerProps } from "@tailus/themer"

import {
  title,
  text,
  type TitleSizeProp,
  type TextProps,
  type TextSizeProp,
  type TextAlignProp,
  type TextWeightProp
} from "@tailus/themer"


const Trigger = Primitive.Trigger;
const Portal = Primitive.Portal;
const Close = Primitive.Close;
const NestedRoot = Primitive.NestedRoot;

const DirectionContext = React.createContext<Omit<DrawerProps, "fancy" | "mixed">>({ direction: "bottom", withControler:true });

type RootProps = React.ComponentProps<typeof Primitive.Root> & DrawerProps & {
    ref?: React.Ref<React.ElementRef<typeof Primitive.Root>>;
};

const Root: React.FC<RootProps> = ({ direction, withControler, ...props }, forwardedRef) => {
    return (
        <DirectionContext.Provider value={{ direction, withControler }}>
            <Primitive.Root ref={forwardedRef} direction={direction} {...props} />
        </DirectionContext.Provider>
    );
};

const Content = React.forwardRef <
    React.ElementRef < typeof Primitive.Content > ,
    React.ComponentProps < typeof Primitive.Content > & Omit<DrawerProps, "direction"> 
    >(({
        className,
        fancy,
        mixed,
        ...props
    }, forwardedRef) => {

        const { content } = drawer()
        const {direction, withControler} = React.useContext(DirectionContext);

        if (fancy && mixed) {
            throw new Error('The fancy and mixed props cannot be used together.');
        } 

        return (
            <Primitive.Content {...props} ref={forwardedRef}
                className = {content({fancy,mixed,direction,withControler,className})}
            />
        )
    }
);

const Overlay = React.forwardRef<
  React.ElementRef<typeof Primitive.Overlay>,
  React.ComponentProps<typeof Primitive.Overlay>
  >(({ className, ...props }, forwardedRef) => {
    
    const { overlay } = drawer()

    return (
      <Primitive.Overlay
          {...props}
          ref={forwardedRef}
          className={overlay({ className })}
      />
    )
  });

const Title = React.forwardRef<
  React.ElementRef<typeof Primitive.Title>,
  React.ComponentProps<typeof Primitive.Title> & {
    size?: TitleSizeProp,
    align?: TextAlignProp,
    weight?: TextWeightProp
  }
>(({className, size="base", align, weight="medium", ...props}, forwardedRef) => (
  <Primitive.Title
    {...props}
    ref={forwardedRef}
    className={title({ size, align, weight, className })}
  />
));

const Description = React.forwardRef<
  React.ElementRef<typeof Primitive.Description>,
  React.ComponentProps<typeof Primitive.Description> & TextProps & {
    size?: TextSizeProp,
    align?: TextAlignProp,
    weight?: TextWeightProp
  }
>(({className, size, weight, align, neutral, ...props}, forwardedRef) => (
  <Primitive.Description
    {...props}
    ref={forwardedRef}
    className={text({ size, weight, align, neutral, className })}
  />
));

export default {
    Root,
    NestedRoot,
    Trigger,
    Portal,
    Close,
    Content,
    Overlay,
    Title,
    Description,
};

export {
    Root,
    Trigger,
    NestedRoot,
    Portal,
    Close,
    Content,
    Overlay,
    Title,
    Description,
};

Usage

Import all the parts and build your Drawer.

import Drawer from "@tailus-ui/Drawer";
import Button from "@tailus-ui/Button";
import { Text, Link } from "@tailus-ui/typography";
import Drawer from "@tailus-ui/Drawer";
import Button from "@tailus-ui/button/Button";
import { Text, Link } from "@tailus-ui/typography";

export const Overview = () => {
    return (
        <Drawer.Root shouldScaleBackground>
            <Drawer.Trigger asChild>
                <Button.Root variant="outlined" intent="gray">
                    <Button.Label>Open Drawer</Button.Label>
                </Button.Root>
            </Drawer.Trigger>
            <Drawer.Portal>
                <Drawer.Overlay />
                <Drawer.Content >
                    <div className="max-w-md mx-auto mt-6">
                        <Drawer.Title>Drawer for React</Drawer.Title> 
                        <Text className="my-3">
                            This component can be used as a Dialog replacement on mobile and tablet devices. You can read about why and how it was built {' '}
                            <Link href="https://emilkowal.ski/ui/building-a-drawer-component" target="_blank">here</Link>
                        </Text>
                        <Text className="mb-3">
                            It comes unstyled and has gesture-driven animations, and is made by {' '}
                            <Link href="https://emilkowal.ski" target="_blank">Emil</Link>
                        </Text>
                        <Text className="mb-8">
                            It uses {' '}
                            <Link href="https://www.radix-ui.com/docs/primitives/components/dialog" target="_blank">Radix's Dialog primitive</Link> {' '}
                            under the hood and is inspired by {' '}
                            <Link href="https://twitter.com/devongovett/status/1674470185783402496" target="_blank">this tweet.</Link>
                        </Text>
                    </div>
                </Drawer.Content>
            </Drawer.Portal>
        </Drawer.Root>
    )
}

Reference

Content

The content of the Drawer component

Prop
Type
Default
mixed
boolean
-
fancy
boolean
-
withControler
boolean
true

Title

The title of the Drawer component

Prop
Type
Default
size ~
enum
4xl
align ~
enum
left
weight ~
enum

Description

The description of the Drawer component

Prop
Type
Default
size ~
enum
base
align ~
enum
left
weight ~
enum

Examples