Componentticket-stub0.78 KB gzipZero dependencies
Envelope
Envelope whose flap opens with a 3D rotateX and the letter inside rises, on hover or via an open prop.
Preview
Live Preview
CLOSEDFROM: Alice
You are invited.
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add envelopeOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { Envelope } from "@/components/ui/envelope";
<Envelope sender="ALICE" recipient="BOB" open={open} onOpenChange={setOpen}>
<p>You are invited.</p>
</Envelope>Usage guidance
Use Envelope when a ticket stub surface needs Envelope whose flap opens with a 3D rotateX and the letter inside rises, on hover or via an open prop. Keep props minimal and prefer composition with sibling stubs.
Avoid Envelope for long form flows or dense data grids where a dedicated layout block fits better.
Source code
envelope.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface EnvelopeProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
sender?: string;
recipient?: string;
letter?: React.ReactNode;
}
export function Envelope({ open = false, onOpenChange, sender, recipient, letter }: EnvelopeProps) {
return (
<div
className={cn(
"relative size-32 rounded-2xl border border-border bg-card p-4 motion-reduce:transition-none",
"hover:scale-[1.02] transition-transform"
)}
onClick={() => onOpenChange && onOpenChange(!open)}
>
<style>{`@keyframes envFlapOpen { from { transform: rotateX(-85deg); opacity: 0.4; } to { transform: rotateX(0deg); opacity: 1; } }`}</style>
<div
className={cn(
"rounded-t-2xl border-b border-border bg-secondary p-3"
)}
style={{
transformOrigin: "top",
transform: open ? "rotateX(0deg)" : "rotateX(-12deg)",
animation: open ? "envFlapOpen 0.45s ease-out both" : undefined,
}}
>
<div className="flex items-center justify-between">
<span className="font-mono text-[9px] uppercase tracking-[0.15em] text-muted-foreground">
{open ? "OPEN" : "CLOSED"}
</span>
<span className="font-mono text-[9px] uppercase tracking-[0.15em] text-muted-foreground">
{sender ? `FROM: ${sender}` : "SENDER"}
</span>
</div>
</div>
<div
className="mt-3 p-3 bg-muted rounded-sm transition-transform duration-300 motion-reduce:transition-none"
style={open ? { transform: "translateY(-8px)" } : undefined}
>
{letter}
</div>
<div className="absolute -bottom-1 w-full h-0.5 bg-border/20" />
</div>
);
}Props
| Prop | Type | Description |
|---|---|---|
| open? | boolean | |
| onOpenChange? | (open: boolean) => void | |
| sender? | string | |
| recipient? | string | |
| letter? | React.ReactNode |