Skip to content

Celebrate Modal

“Week complete!” celebration dialog: pastel confetti shapes burst from the center, trophy emoji, friendly copy, three pastel stat tiles and one pill button.

soft-flat/ui/celebrate-modal
Open ↗

Source

"use client";

import type { CSSProperties, ReactNode } from "react";
import { Dialog } from "radix-ui";
import { cn } from "@/lib/utils";

export interface CelebrateModalProps {
  trigger?: ReactNode;
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
  title?: string;
  description?: string;
  stats?: { value: string; label: string }[];
  cta?: string;
  da?: string;
}

const SHAPES = ["bg-da-accent", "bg-da-secondary", "bg-da-primary/60", "bg-da-warning", "bg-da-danger/60"];

/** “All done!” celebration dialog: pastel confetti shapes burst from the center, a big emoji, friendly copy, three weekly stats and a single pill button. */
export function CelebrateModal({
  trigger,
  open,
  onOpenChange,
  title = "Week complete! 🎉",
  description = "Your team finished every task on the board. Take a breath — you earned it.",
  stats = [
    { value: "48", label: "tasks done" },
    { value: "6", label: "people" },
    { value: "0", label: "overdue" },
  ],
  cta = "Plan next week",
  da = "soft-flat",
}: CelebrateModalProps) {
  return (
    <Dialog.Root open={open} onOpenChange={onOpenChange}>
      {trigger && <Dialog.Trigger asChild>{trigger}</Dialog.Trigger>}
      <Dialog.Portal>
        <Dialog.Overlay data-da={da} className="fixed inset-0 z-50 bg-da-overlay transition-opacity duration-(--da-duration) starting:opacity-0" />
        <Dialog.Content
          data-da={da}
          className="fixed top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-[32px] bg-da-surface p-8 text-center text-da-surface-fg shadow-da-lg outline-none transition-[opacity,scale] duration-(--da-duration-slow) ease-da-emphasized starting:scale-75 starting:opacity-0 motion-reduce:transition-none"
        >
          <div aria-hidden className="pointer-events-none absolute inset-0">
            {Array.from({ length: 18 }, (_, i) => {
              const a = (i / 18) * Math.PI * 2;
              return (
                <span
                  key={i}
                  className={cn(
                    "sf-confetti absolute top-1/3 left-1/2",
                    SHAPES[i % SHAPES.length],
                    i % 3 === 0 ? "size-3 rounded-full" : i % 3 === 1 ? "h-2 w-4 rounded-full" : "size-3 rounded-[3px]",
                  )}
                  style={
                    {
                      "--cx": `${Math.cos(a) * (110 + (i % 4) * 25)}px`,
                      "--cy": `${Math.sin(a) * (90 + (i % 3) * 30)}px`,
                      animationDelay: `${(i % 5) * 30}ms`,
                    } as CSSProperties
                  }
                />
              );
            })}
          </div>
          <span aria-hidden className="relative mx-auto grid size-24 place-items-center rounded-full bg-da-warning/30 text-5xl">
            🏆
          </span>
          <Dialog.Title className="relative mt-5 font-da-display text-3xl font-extrabold tracking-da-display">{title}</Dialog.Title>
          <Dialog.Description className="relative mt-2 text-da-muted-fg">{description}</Dialog.Description>
          <dl className="relative mt-6 grid grid-cols-3 gap-2">
            {stats.map((s, i) => (
              <div
                key={s.label}
                className={cn(
                  "flex flex-col rounded-da-md py-3",
                  ["bg-da-accent text-da-accent-fg", "bg-da-secondary text-da-secondary-fg", "bg-da-primary/12"][i % 3],
                )}
              >
                <dt className="order-2 text-xs opacity-85">{s.label}</dt>
                <dd className="font-da-display text-2xl font-extrabold">{s.value}</dd>
              </div>
            ))}
          </dl>
          <Dialog.Close asChild>
            <button className="da-focus relative mt-7 inline-flex h-12 w-full items-center justify-center rounded-da-pill bg-da-primary font-semibold text-da-primary-fg shadow-da-md">
              {cta}
            </button>
          </Dialog.Close>
          <style href="sf-confetti" precedence="default">{`
            @keyframes sf-confetti { 0% { translate: 0 0; opacity: 1; scale: .4 } 70% { opacity: 1 } 100% { translate: var(--cx) var(--cy); opacity: 0; scale: 1; rotate: 200deg } }
            .sf-confetti { animation: sf-confetti 1.1s cubic-bezier(.2,.8,.2,1) both }
            @media (prefers-reduced-motion: reduce) { .sf-confetti { display: none } }
          `}</style>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

export default CelebrateModal;

modules/soft-flat/ui/celebrate-modal/index.tsx

Props

PropTypeDefaultDescription
triggerReactNode—Opener.
open / onOpenChangeboolean / fn—Control.
title / description / ctastring—Texts.
stats{ value, label }[]—Stats.
dastring—DA scope for the portal.

Other modal variants in Soft Flat

Modal in other art directions

Confirm Dialog

Destructive confirmation (Radix AlertDialog) over a hazard-striped overlay: red 'Danger zone' band, consequences list, and a type-the-name field that arms the delete button; async pending state.

BrutalistModal

Drawer

Side sheet (Radix Dialog) sliding in from the right or left in stepped motion: yellow title band with close square, scrollable body for forms, ruled footer actions.

BrutalistModal

Modal

Accessible dialog on Radix Dialog: colored title band (primary or danger), close button, scrollable body, footer action bar. Re-applies the DA scope inside the portal. Stepped drop-in entrance.

BrutalistModal

Onboarding Modal

Multi-step onboarding dialog: a colored visual panel (giant step number by default) next to title, description, segmented progress bar and Back/Next buttons; resets on open.

BrutalistModal

Bottom Sheet

Mobile-style bottom sheet (Radix Dialog): heavily frosted panel sliding up with a grab handle, title and description; becomes a floating centered card on wider screens.

GlassModal

Dropdown Menu

Frosted dropdown menu (Radix DropdownMenu) built from an items array: labels, icons, shortcuts, separators, checkbox items, nested submenus and a danger item.

GlassModal

Modal

Frosted glass sheet dialog (Radix): 40px backdrop blur, optional icon bubble, rise-and-pop entrance with overshoot, blurred overlay, round close button and footer actions.

GlassModal

Share Dialog

Glass share dialog: invite-by-email field with role select, people-with-access list, a general access switch (invited only / anyone with link) and a copy-link button with confirmation.

GlassModal

Command Palette

⌘K command palette (Radix Dialog): search input, results filtered as you type and grouped (actions, incidents, schedules), icons and shortcuts, arrow-key highlight, Enter to run, footer hints.

MinimalModal

Modal

Radix-based dialog with fade + subtle scale entrance, optional icon, title/description, scrollable body and a tinted footer action bar. Re-applies the DA scope inside the portal.

MinimalModal

Popover Card

Anchored popover (Radix Popover) with arrow, optional title + close button and any content — quick forms (snooze), definitions or details. Collision-aware placement.

MinimalModal

Sheet

Side sheet (Radix Dialog) sliding in from the right or left over a dimmed overlay: title and description header with close button, scrollable body and footer actions.

MinimalModal

Command Dialog

Command palette (Radix Dialog + listbox): a square panel near the top with a large borderless search, grouped results under mono headings, arrow-key navigation with an inverted active row and shortcut hints.

Mono CleanModal

Drawer

Right side panel (Radix Dialog) sliding in over a dimmed page: mono top bar with label and “Close”, large title, scrollable body with ruled rows and a sticky footer.

Mono CleanModal

Lightbox

Full-screen image viewer (Radix Dialog) on ink (stays dark in dark mode): the image frame centered, mono “03 / 04” counter and caption, Prev/Next text buttons and arrow-key navigation, thumbnails strip.

Mono CleanModal

Modal

Square dialog (Radix): a mono label + text “Close” in the top bar, a full-ink rule, a large title, body and right-aligned actions; opens with a quick fade and 8px rise.

Mono CleanModal

Confirm Dialog

Destructive confirmation (Radix AlertDialog): red warning icon, title, consequence text, optional “type VOID to confirm” guard, cancel + red confirm.

Neo CorporateModal

Modal

Corporate dialog (Radix): white card with header (title, description, close), divided scrollable body and a tinted footer bar for actions; quick fade + slight scale.

Neo CorporateModal

Payment Modal

Pay-invoice dialog: amount summary, card/bank method radio cards, method-specific fields, secure note and a pay button that switches to a success state.

Neo CorporateModal

Side Sheet

Detail drawer (Radix Dialog) sliding in from the right: full-height bordered panel with sticky header, scrollable body and footer actions — for record details.

Neo CorporateModal

Bottom Sheet

Bottom sheet (Radix Dialog): slides up from the bottom with a grab handle and very rounded top corners; becomes a centered card from sm up.

Organic SoftModal

Confirm Dialog

Gentle confirmation (Radix AlertDialog): a wilting-leaf illustration in a clay circle, serif question, kind consequence text, a sage “keep” action and a quiet clay confirm.

Organic SoftModal

Modal

Soft dialog (Radix): large-radius cream card that rises gently with a slight overshoot, serif title, round close button and pill actions.

Organic SoftModal

Welcome Modal

Onboarding dialog: an illustrated tinted header (sprout growing with each step), serif title and copy, leaf-shaped step dots, Back/Next pills and “Let’s begin” on the last step.

Organic SoftModal