Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ When adding a new item, you will create/edit these files:
| `mc-skeleton` | ✅ | ✅ |
| `mc-badge` | ✅ | ✅ |
| `mc-avatar` | ✅ | ✅ |
| `mc-drawer` | ✅ | |
| `mc-drawer` | ✅ | |
| `mc-hover-card` | ✅ | ✅ |
| `mc-slider` | ✅ | |
| `mc-carousel` | ✅ | ✅ |
Expand Down
29 changes: 29 additions & 0 deletions packages/web/__registry__/index.tsx

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/web/__registry__/registry.autogenerated.json
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,19 @@
],
"type": "registry:component"
},
{
"name": "mc-drawer",
"title": "MicroClub Drawer",
"description": "A drawer component for MicroClub UI",
"dependencies": ["vaul"],
"files": [
{
"path": "registry/ui/mc-drawer.tsx",
"type": "registry:component"
}
],
"type": "registry:component"
},
{
"name": "mc-hover-card",
"title": "MicroClub Hover Card",
Expand Down
4 changes: 4 additions & 0 deletions packages/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@
--blur-lg: 7px;
--blur-xl: 9px;

--spacing-drawer: 26.6875rem;
--spacing-drawer-nav: 4.5rem;
--spacing-drawer-item: 4.125rem;

--font-dm-sans: var(--font-dm-sans);
--font-plus-jakarta-sans: var(--font-plus-jakarta-sans);
--font-heading: var(--font-sans);
Expand Down
6 changes: 3 additions & 3 deletions packages/web/content/docs/components/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ description: Explore all available UI components in the MicroClub UI library.
<Card title="MC Avatar" href="/docs/components/mc-avatar">
A component for displaying user profile images.
</Card>
<Card title="MC Drawer" href="/docs/components/mc-drawer">
A slide-out panel for navigation or content.
</Card>
<Card title="MC Hover Card" href="/docs/components/mc-hover-card">
A card that appears on hover with additional information.
</Card>
Expand All @@ -116,9 +119,6 @@ description: Explore all available UI components in the MicroClub UI library.
<Card title="MC Progress" href="/docs/components/mc-progress">
A component that displays progress of an operation.
</Card>
<Card title="MC Drawer" href="/docs/components/mc-drawer">
A slide-out panel for navigation or content.
</Card>
<Card title="MC Slider" href="/docs/components/mc-slider">
A component for selecting a value from a range.
</Card>
Expand Down
214 changes: 210 additions & 4 deletions packages/web/content/docs/components/mc-drawer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,213 @@ title: MC Drawer
description: A slide-out panel for navigation or content.
---

<Callout type="warn">
This component is coming soon. Check our [contributing guide](/docs/introduction#contributing) for
more information.
</Callout>
## Overview

The `mc-drawer` component is a responsive slide-out panel for navigation, settings, forms, and
secondary workflows. It is built on top of **vaul**, with MicroClub styling for overlays, side
panels, bottom sheets, nav rows, headers, footers, and accessible close controls.

Use it when content should stay close to the current page without taking the user into a full
route change.

## Preview

<Tabs items={['preview', 'code']}>
<Tab>
<ComponentPreview name="mc-drawer-demo" />
</Tab>
<Tab>
<ComponentSource name="mc-drawer-demo" />
</Tab>
</Tabs>

## Add to Your Project

Deploy `mc-drawer` directly to your `components/ui` directory:

<Tabs items={['npm', 'pnpm', 'yarn', 'bun']}>
<Tab value="npm">npx mcoli-ui@latest add mc-drawer</Tab>
<Tab value="pnpm">pnpm dlx mcoli-ui@latest add mc-drawer</Tab>
<Tab value="yarn">yarn dlx mcoli-ui@latest add mc-drawer</Tab>
<Tab value="bun">bunx mcoli-ui@latest add mc-drawer</Tab>
</Tabs>

### Manual Setup

If you prefer manual control:

<Steps>

<Step>Install the core dependencies</Step>

<Tabs items={['npm', 'pnpm', 'yarn', 'bun']}>
<Tab value="npm">npm install vaul</Tab>
<Tab value="pnpm">pnpm add vaul</Tab>
<Tab value="yarn">yarn add vaul</Tab>
<Tab value="bun">bun add vaul</Tab>
</Tabs>

<Step>Copy the source code</Step>

<ComponentSource name="mc-drawer" />

<Step>Adjust import paths</Step>

Ensure your `@/lib/utils` or equivalent classname helper is correctly imported.

</Steps>

## Usage

```tsx
import {
McDrawer,
McDrawerContent,
McDrawerDescription,
McDrawerHeader,
McDrawerTitle,
McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export default function Example() {
return (
<McDrawer direction="right">
<McDrawerTrigger>Open drawer</McDrawerTrigger>
<McDrawerContent>
<McDrawerHeader>
<McDrawerTitle>Settings</McDrawerTitle>
<McDrawerDescription>Update your workspace preferences.</McDrawerDescription>
</McDrawerHeader>
</McDrawerContent>
</McDrawer>
);
}
```

## Examples

### Navigation Drawer

```tsx
import { Moon } from 'lucide-react';
import {
McDrawer,
McDrawerContent,
McDrawerNav,
McDrawerNavItem,
McDrawerTitle,
McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export function NavigationDrawer() {
return (
<McDrawer direction="right">
<McDrawerTrigger>Open navigation</McDrawerTrigger>
<McDrawerContent>
<McDrawerTitle className="sr-only">Navigation</McDrawerTitle>
<McDrawerNav />
<div className="flex flex-1 flex-col overflow-y-auto pt-9">
<McDrawerNavItem icon={<Moon size={16} />} hasArrow>
Account
</McDrawerNavItem>
<McDrawerNavItem icon={<Moon size={16} />} hasArrow>
Settings
</McDrawerNavItem>
</div>
</McDrawerContent>
</McDrawer>
);
}
```

### Bottom Sheet

```tsx
import {
McDrawer,
McDrawerClose,
McDrawerContent,
McDrawerDescription,
McDrawerHeader,
McDrawerTitle,
McDrawerTrigger,
} from '@/components/ui/mc-drawer';

export function ProfileDrawer() {
return (
<McDrawer direction="bottom">
<McDrawerTrigger>Edit profile</McDrawerTrigger>
<McDrawerContent>
<div className="flex flex-col gap-6 px-4 py-6">
<McDrawerHeader className="gap-2 p-0">
<McDrawerTitle>Edit profile</McDrawerTitle>
<McDrawerDescription>Save your changes before closing the drawer.</McDrawerDescription>
</McDrawerHeader>

<input
type="email"
defaultValue="m@example.com"
className="w-full rounded-lg border border-border bg-input px-4 py-3 text-sm"
/>

<McDrawerClose className="w-full rounded-lg bg-primary py-4 text-sm font-medium text-primary-foreground">
Save changes
</McDrawerClose>
</div>
</McDrawerContent>
</McDrawer>
);
}
```

## API Reference

### Root Components

| Component | Description |
| ----------------- | ------------------------------------------------------ |
| `McDrawer` | Root wrapper that manages drawer state and direction |
| `McDrawerTrigger` | Element that opens the drawer |
| `McDrawerContent` | Drawer panel rendered in a portal with overlay support |
| `McDrawerPortal` | Portal wrapper from the underlying drawer primitive |
| `McDrawerOverlay` | Fixed overlay displayed behind the drawer content |
| `McDrawerClose` | Element that closes the drawer |

### Layout Components

| Component | Description |
| --------------------- | ------------------------------------------------ |
| `McDrawerHeader` | Header region for titles and descriptions |
| `McDrawerFooter` | Footer region for actions |
| `McDrawerTitle` | Accessible drawer title |
| `McDrawerDescription` | Accessible supporting description |
| `McDrawerNav` | Top navigation row with a built-in close control |
| `McDrawerNavItem` | Full-width drawer navigation item |

### Common Props

#### `McDrawer`

| Prop | Type | Default | Description |
| -------------- | ---------------------------------------- | ----------- | -------------------------------------- |
| `direction` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Side where the drawer opens |
| `open` | `boolean` | `undefined` | Controlled open state |
| `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback fired when open state changes |

#### `McDrawerContent`

| Prop | Type | Default | Description |
| ----------- | ----------- | ----------- | ---------------------------------- |
| `className` | `string` | `undefined` | Additional classes for the panel |
| `children` | `ReactNode` | `undefined` | Content rendered inside the drawer |

#### `McDrawerNavItem`

| Prop | Type | Default | Description |
| -------------- | ----------- | ----------- | ----------------------------------------- |
| `icon` | `ReactNode` | `undefined` | Optional leading icon |
| `hasArrow` | `boolean` | `false` | Shows a trailing chevron |
| `rightElement` | `ReactNode` | `undefined` | Custom trailing element, such as a switch |
| `className` | `string` | `undefined` | Additional classes for the item |

All other relevant props from `vaul` are supported on their matching wrappers.
2 changes: 1 addition & 1 deletion packages/web/content/docs/components/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@
"mc-skeleton",
"mc-badge",
"mc-avatar",
"mc-drawer",
"mc-hover-card",
"mc-carousel",
"---Coming Soon---",
"mc-data-table",
"mc-collapsible",
"mc-progress",
"mc-drawer",
"mc-slider"
]
}
3 changes: 2 additions & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0"
"tailwind-merge": "^3.5.0",
"vaul": "^1.1.2"
},
"devDependencies": {
"@storybook/nextjs": "^10.4.1",
Expand Down
15 changes: 15 additions & 0 deletions packages/web/public/r/mc-drawer-card.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "mc-drawer-card",
"title": "MicroClub Drawer",
"description": "A drawer component for MicroClub UI",
"dependencies": ["@base-ui/react"],
"files": [
{
"path": "registry/ui/mc-drawer.tsx",
"content": "",
"type": "registry:component"
}
],
"type": "registry:component"
}
15 changes: 15 additions & 0 deletions packages/web/public/r/mc-drawer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "mc-drawer",
"title": "MicroClub Drawer",
"description": "A drawer component for MicroClub UI",
"dependencies": ["vaul"],
"files": [
{
"path": "registry/ui/mc-drawer.tsx",
"content": "'use client';\nimport * as React from 'react';\nimport { Drawer as McDrawerPrimitive } from 'vaul';\nimport { cn } from '@/lib/utils';\n\nfunction McDrawer({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Root>) {\n return <McDrawerPrimitive.Root data-slot=\"drawer\" {...props} />;\n}\n\nfunction McDrawerTrigger({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Trigger>) {\n return <McDrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\nfunction McDrawerPortal({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Portal>) {\n return <McDrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />;\n}\n\nfunction McDrawerClose({ ...props }: React.ComponentProps<typeof McDrawerPrimitive.Close>) {\n return <McDrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\nfunction McDrawerOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof McDrawerPrimitive.Overlay>) {\n return (\n <McDrawerPrimitive.Overlay\n data-slot=\"drawer-overlay\"\n className={cn(\n 'fixed inset-0 z-50 bg-foreground/20',\n 'supports-backdrop-filter:backdrop-blur-xs',\n 'data-open:animate-in data-open:fade-in-0',\n 'data-closed:animate-out data-closed:fade-out-0',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction McDrawerContent({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof McDrawerPrimitive.Content>) {\n return (\n <McDrawerPortal>\n <McDrawerOverlay />\n <McDrawerPrimitive.Content\n data-slot=\"drawer-content\"\n className={cn(\n 'group/drawer-content fixed z-50 flex flex-col',\n 'bg-popover text-popover-foreground',\n 'shadow-2xl',\n 'data-[vaul-drawer-direction=bottom]:inset-x-0',\n 'data-[vaul-drawer-direction=bottom]:bottom-0',\n 'data-[vaul-drawer-direction=bottom]:max-h-dvh',\n 'data-[vaul-drawer-direction=bottom]:rounded-t-2xl',\n 'data-[vaul-drawer-direction=bottom]:border',\n 'data-[vaul-drawer-direction=bottom]:border-border',\n 'data-[vaul-drawer-direction=top]:inset-x-0',\n 'data-[vaul-drawer-direction=top]:top-0',\n 'data-[vaul-drawer-direction=top]:max-h-dvh',\n 'data-[vaul-drawer-direction=top]:rounded-b-2xl',\n 'data-[vaul-drawer-direction=top]:border',\n 'data-[vaul-drawer-direction=top]:border-border',\n 'data-[vaul-drawer-direction=left]:inset-y-0',\n 'data-[vaul-drawer-direction=left]:left-0',\n 'data-[vaul-drawer-direction=left]:w-drawer',\n 'data-[vaul-drawer-direction=left]:max-w-full',\n 'data-[vaul-drawer-direction=left]:rounded-r-2xl',\n 'data-[vaul-drawer-direction=left]:border',\n 'data-[vaul-drawer-direction=left]:border-border',\n 'data-[vaul-drawer-direction=right]:inset-y-0',\n 'data-[vaul-drawer-direction=right]:right-0',\n 'data-[vaul-drawer-direction=right]:w-drawer',\n 'data-[vaul-drawer-direction=right]:max-w-full',\n 'data-[vaul-drawer-direction=right]:rounded-l-2xl',\n 'data-[vaul-drawer-direction=right]:border',\n 'data-[vaul-drawer-direction=right]:border-border',\n\n className\n )}\n {...props}\n >\n <div className=\"mx-auto mt-4 hidden h-1 w-10 shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block group-data-[vaul-drawer-direction=top]/drawer-content:block\" />\n {children}\n </McDrawerPrimitive.Content>\n </McDrawerPortal>\n );\n}\n\nfunction McDrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn('flex flex-col gap-2 px-4 pt-0 pb-0', className)}\n {...props}\n />\n );\n}\n\nfunction McDrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-footer\"\n className={cn('mt-auto flex flex-col gap-2 p-4 pt-0', className)}\n {...props}\n />\n );\n}\n\nfunction McDrawerTitle({\n className,\n ...props\n}: React.ComponentProps<typeof McDrawerPrimitive.Title>) {\n return (\n <McDrawerPrimitive.Title\n data-slot=\"drawer-title\"\n className={cn('text-base font-semibold text-foreground leading-snug', className)}\n {...props}\n />\n );\n}\n\nfunction McDrawerDescription({\n className,\n ...props\n}: React.ComponentProps<typeof McDrawerPrimitive.Description>) {\n return (\n <McDrawerPrimitive.Description\n data-slot=\"drawer-description\"\n className={cn('text-sm text-muted-foreground leading-relaxed', className)}\n {...props}\n />\n );\n}\n\nfunction McDrawerNav({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"drawer-nav\"\n className={cn('flex h-drawer-nav items-center gap-2 px-6 py-2.5', className)}\n {...props}\n >\n <McDrawerClose\n className=\"flex h-8 w-8 items-center justify-center rounded-md text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n aria-label=\"Go back\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M12.5 15L7.5 10L12.5 5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </McDrawerClose>\n </div>\n );\n}\n\ninterface McDrawerNavItemProps extends React.ComponentProps<'button'> {\n icon?: React.ReactNode;\n hasArrow?: boolean;\n rightElement?: React.ReactNode;\n}\n\nfunction McDrawerNavItem({\n icon,\n hasArrow = false,\n rightElement,\n children,\n className,\n ...props\n}: McDrawerNavItemProps) {\n return (\n <button\n data-slot=\"drawer-nav-item\"\n className={cn(\n 'flex h-drawer-item w-full items-center justify-between rounded-xl',\n 'px-6 py-5',\n 'text-sm text-foreground',\n 'transition-colors hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50',\n className\n )}\n {...props}\n >\n <span className=\"flex items-center gap-3\">\n {icon && (\n <span className=\"shrink-0 text-foreground\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n {children}\n </span>\n\n {rightElement ??\n (hasArrow && (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M6 4L10 8L6 12\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ))}\n </button>\n );\n}\nexport {\n McDrawer,\n McDrawerPortal,\n McDrawerOverlay,\n McDrawerTrigger,\n McDrawerClose,\n McDrawerContent,\n McDrawerHeader,\n McDrawerFooter,\n McDrawerTitle,\n McDrawerDescription,\n McDrawerNav,\n McDrawerNavItem,\n};\n",
"type": "registry:component"
}
],
"type": "registry:component"
}
13 changes: 13 additions & 0 deletions packages/web/public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,19 @@
],
"type": "registry:component"
},
{
"name": "mc-drawer",
"title": "MicroClub Drawer",
"description": "A drawer component for MicroClub UI",
"dependencies": ["vaul"],
"files": [
{
"path": "registry/ui/mc-drawer.tsx",
"type": "registry:component"
}
],
"type": "registry:component"
},
{
"name": "mc-hover-card",
"title": "MicroClub Hover Card",
Expand Down
Loading
Loading