# V sheet 7

> Team Invite

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-sheet-7).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-sheet-7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-sheet-7-light-54f0fa38da63c52c.png
- Registry item: https://ui.cnippet.dev/r/v-sheet-7.json
- Categories: sheet
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-sheet-7.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/sheet`

## Files

### `registry/default/variants/v-sheet-7.tsx`

```tsx
"use client";

import { SendIcon, UserPlusIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { Avatar, AvatarFallback } from "@/registry/default/ui/avatar";
import { Button } from "@/registry/default/ui/button";
import { Field, FieldLabel } from "@/registry/default/ui/field";
import { Input } from "@/registry/default/ui/input";
import {
  Sheet,
  SheetClose,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetPanel,
  SheetPopup,
  SheetTitle,
  SheetTrigger,
} from "@/registry/default/ui/sheet";

type Role = "Admin" | "Editor" | "Viewer";
type Invite = { id: number; email: string; role: Role; initials: string };

const roles: Role[] = ["Admin", "Editor", "Viewer"];

const roleBadge: Record<Role, string> = {
  Admin: "bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-400",
  Editor:
    "bg-violet-100 text-violet-700 dark:bg-violet-950 dark:text-violet-400",
  Viewer: "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300",
};

const pending: Invite[] = [
  { email: "sarah@example.com", id: 1, initials: "SC", role: "Editor" },
  { email: "marcus@example.com", id: 2, initials: "MK", role: "Viewer" },
];

export function Pattern() {
  const [email, setEmail] = useState("");
  const [role, setRole] = useState<Role>("Editor");
  const [invites, setInvites] = useState<Invite[]>(pending);

  const send = () => {
    if (!email.trim()) return;
    const initials = email.slice(0, 2).toUpperCase();
    setInvites((prev) => [
      ...prev,
      { email: email.trim(), id: Date.now(), initials, role },
    ]);
    setEmail("");
  };

  const remove = (id: number) =>
    setInvites((prev) => prev.filter((i) => i.id !== id));

  return (
    <div className="flex items-center justify-center">
      <Sheet>
        <SheetTrigger render={<Button variant="outline" />}>
          <UserPlusIcon aria-hidden="true" />
          Invite Members
        </SheetTrigger>
        <SheetPopup>
          <SheetHeader>
            <SheetTitle>Invite Team Members</SheetTitle>
            <SheetDescription>
              Send invitations to collaborate on this project.
            </SheetDescription>
          </SheetHeader>
          <SheetPanel className="space-y-5">
            <div className="space-y-3">
              <Field>
                <FieldLabel>Email address</FieldLabel>
                <Input
                  onChange={(e) => setEmail(e.target.value)}
                  onKeyDown={(e) => e.key === "Enter" && send()}
                  placeholder="colleague@company.com"
                  type="email"
                  value={email}
                />
              </Field>
              <Field>
                <FieldLabel>Role</FieldLabel>
                <div className="flex gap-2">
                  {roles.map((r) => (
                    <button
                      className={`flex-1 rounded-md border px-3 py-1.5 text-sm transition-colors ${
                        role === r
                          ? "border-primary bg-primary text-primary-foreground"
                          : "hover:bg-muted"
                      }`}
                      key={r}
                      onClick={() => setRole(r)}
                    >
                      {r}
                    </button>
                  ))}
                </div>
              </Field>
              <Button className="w-full" onClick={send}>
                <SendIcon className="size-3.5" />
                Send Invitation
              </Button>
            </div>

            {invites.length > 0 && (
              <div className="space-y-2">
                <p className="font-semibold text-[10px] text-muted-foreground uppercase tracking-wider">
                  Pending ({invites.length})
                </p>
                <div className="space-y-2">
                  {invites.map((invite) => (
                    <div
                      className="flex items-center gap-3 rounded-lg border p-2.5"
                      key={invite.id}
                    >
                      <Avatar className="size-8">
                        <AvatarFallback className="text-xs">
                          {invite.initials}
                        </AvatarFallback>
                      </Avatar>
                      <div className="min-w-0 flex-1">
                        <p className="truncate text-sm">{invite.email}</p>
                      </div>
                      <span
                        className={`rounded px-1.5 py-0.5 font-medium text-[10px] ${roleBadge[invite.role]}`}
                      >
                        {invite.role}
                      </span>
                      <Button
                        aria-label="Remove invite"
                        className="size-6"
                        onClick={() => remove(invite.id)}
                        size="icon"
                        variant="ghost"
                      >
                        <XIcon className="size-3" />
                      </Button>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </SheetPanel>
          <SheetFooter>
            <SheetClose render={<Button variant="ghost" />}>Cancel</SheetClose>
            <Button>Done</Button>
          </SheetFooter>
        </SheetPopup>
      </Sheet>
    </div>
  );
}
```
