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
89 changes: 89 additions & 0 deletions apps/v4/app/components/docs/examples/drawer-demo.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { on } from '@ember/modifier';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';

export default class DrawerDemo extends Component {
@tracked goal = 350;

handleDecrement = () => {
this.goal = Math.max(200, Math.min(400, this.goal - 10));
};

handleIncrement = () => {
this.goal = Math.max(200, Math.min(400, this.goal + 10));
};

<template>
<Drawer>
<DrawerTrigger @asChild={{true}}>
<Button @variant="outline">Open Drawer</Button>
</DrawerTrigger>
<DrawerContent>
<div class="mx-auto w-full max-w-sm">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="p-4 pb-0">
<div class="flex items-center justify-center space-x-2">
<Button
@class="h-8 w-8 shrink-0 rounded-full"
@disabled={{this.isMinGoal}}
@size="icon"
@variant="outline"
{{on "click" this.handleDecrement}}
>
<span class="sr-only">Decrease</span>
</Button>
<div class="flex-1 text-center">
<div class="text-7xl font-bold tracking-tighter">
{{this.goal}}
</div>
<div
class="text-[0.70rem] text-muted-foreground uppercase"
>Calories/day</div>
</div>
<Button
@class="h-8 w-8 shrink-0 rounded-full"
@disabled={{this.isMaxGoal}}
@size="icon"
@variant="outline"
{{on "click" this.handleIncrement}}
>
<span class="sr-only">Increase</span>
+
</Button>
</div>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose @asChild={{true}}>
<Button @variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</div>
</DrawerContent>
</Drawer>
</template>

get isMinGoal() {
return this.goal <= 200;
}

get isMaxGoal() {
return this.goal >= 400;
}
}
119 changes: 119 additions & 0 deletions apps/v4/app/components/docs/examples/drawer-dialog.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';

import type { TOC } from '@ember/component/template-only';
import type Owner from '@ember/owner';

export default class DrawerDialogDemo extends Component {
@tracked open = false;
@tracked isDesktop = window.matchMedia('(min-width: 768px)').matches;

constructor(owner: Owner, args: Record<string, never>) {
super(owner, args);
this.mediaQuery = window.matchMedia('(min-width: 768px)');
this.handleMediaChange(this.mediaQuery);
this.mediaQuery.addEventListener('change', this.handleMediaChange);
}

willDestroy(): void {
super.willDestroy();
this.mediaQuery?.removeEventListener('change', this.handleMediaChange);
}

private mediaQuery: MediaQueryList | null = null;

handleMediaChange = (e: MediaQueryListEvent | MediaQueryList) => {
this.isDesktop = e.matches;
};

setOpen = (value: boolean) => {
this.open = value;
};

<template>
{{#if this.isDesktop}}
<Dialog @onOpenChange={{this.setOpen}} @open={{this.open}}>
<DialogTrigger>
<Button @variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent @class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<ProfileForm />
</DialogContent>
</Dialog>
{{else}}
<Drawer @onOpenChange={{this.setOpen}} @open={{this.open}}>
<DrawerTrigger @asChild={{true}}>
<Button @variant="outline">Edit Profile</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader @class="text-left">
<DrawerTitle>Edit profile</DrawerTitle>
<DrawerDescription>
Make changes to your profile here. Click save when you're done.
</DrawerDescription>
</DrawerHeader>
<ProfileForm @class="px-4" />
<DrawerFooter @class="pt-2">
<DrawerClose @asChild={{true}}>
<Button @variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
{{/if}}
</template>
}

interface ProfileFormSignature {
Element: HTMLFormElement;
Args: {
class?: string;
};
Blocks: {
default: [];
};
}

const ProfileForm: TOC<ProfileFormSignature> = <template>
<form class={{cn "grid items-start gap-6" @class}} ...attributes>
<div class="grid gap-3">
<Label @for="email">Email</Label>
<Input id="email" type="email" value="shadcn@example.com" />
</div>
<div class="grid gap-3">
<Label @for="username">Username</Label>
{{! template-lint-disable no-potential-path-strings }}
<Input id="username" value="@shadcn" />
</div>
<Button type="submit">Save changes</Button>
</form>
</template>;
46 changes: 46 additions & 0 deletions apps/v4/app/components/docs/examples/drawer-scrollable-content.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';

const paragraphs = Array.from({ length: 10 });

<template>
<Drawer @direction="right">
<DrawerTrigger @asChild={{true}}>
<Button @variant="outline">Scrollable Content</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
{{#each paragraphs}}
<p class="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</p>
{{/each}}
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose @asChild={{true}}>
<Button @variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</template>
55 changes: 55 additions & 0 deletions apps/v4/app/components/docs/examples/drawer-sides.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';

const DRAWER_SIDES = ['top', 'right', 'bottom', 'left'] as const;
const paragraphs = Array.from({ length: 10 });

<template>
<div class="flex flex-wrap gap-2">
{{#each DRAWER_SIDES as |side|}}
<Drawer @direction={{side}}>
<DrawerTrigger @asChild={{true}}>
<Button @class="capitalize" @variant="outline">{{side}}</Button>
</DrawerTrigger>
<DrawerContent
@class="data-[vaul-drawer-direction=bottom]:max-h-[50vh] data-[vaul-drawer-direction=top]:max-h-[50vh]"
>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>
Set your daily activity goal.
</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
{{#each paragraphs}}
<p class="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
{{/each}}
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose @asChild={{true}}>
<Button @variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
{{/each}}
</div>
</template>
81 changes: 81 additions & 0 deletions apps/v4/app/content/docs/components/drawer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
title: Drawer
description: A drawer component for Ember.
---

<ComponentPreview name="drawer-demo" />

## Installation

### CLI

```bash
npx shadcn-ember@latest add drawer
```

### Manual

**Install the following dependencies:**

```bash
pnpm add ember-provide-consume-context ember-modifier
```

**Copy and paste the drawer component into your project:**

<ComponentSource name="drawer" />

**Update the import paths to match your project setup.**

## Usage

```gts showLineNumbers
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';
```

```hbs showLineNumbers
<Drawer>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>
<Button @variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
```

## Examples

### Scrollable Content

Keep actions visible while the content scrolls.

<ComponentPreview name="drawer-scrollable-content" />

### Sides

Use the `@direction` argument to set the side of the drawer. Available options are `top`, `right`, `bottom`, and `left`.

<ComponentPreview name="drawer-sides" />

### Responsive Dialog

You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.

<ComponentPreview name="drawer-dialog" />
Loading
Loading