익스클루시브

Toast Physics <mv-toast-physics>

실제 강체처럼 동작하는 알림입니다. 모서리 트레이로 떨어져 중력에 따라 쌓이고, 우선순위가 질량이 됩니다(오류는 더 무겁고 넓으며, 쿵 하고 떨어져 다른 카드들을 흔든 뒤 가벼운 카드 아래로 가라앉습니다).

작동 방식

실제 강체처럼 동작하는 알림: 모서리 트레이로 떨어져 중력에 따라 쌓이며, 우선순위가 질량이 됩니다(오류는 더 무겁고 넓으며, 쿵 하고 떨어져 다른 카드를 흔든 뒤 가벼운 카드 아래로 가라앉습니다). 카드를 잡아 던지면 닫히고, 만료되면 서서히 사라지며 더미가 빈자리를 메웁니다. toast()와 같은 API 철학을 따릅니다.

카테고리오버레이
유형Web Component (<mv-toast-physics>)
상태안정
Keywordsexclusive, toast, notification, physics, gravity, fling, rigid-body, priority, live-region, tactile

When to use

  • Notifications are part of a playful product identity and a tactile, physics-driven pile adds delight
  • Error notifications should visibly outweigh info messages and stay in view longer

Avoid when

  • Notifications need promise-based loading states or in-place updates by id → use Toast instead
  • A sober business app sends frequent notifications, where the motion becomes noise → use Toast instead

설치

node scripts/add.mjs toast-physics --out ./src/marvelous

Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["toast-physics"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/physics.js, components/toast-physics/toast-physics.js, components/toast-physics/toast-physics.css.

사용법

기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:

<div id="tp-demo" style="display:grid;gap:1rem;justify-items:center;text-align:center">
  <div style="display:grid;gap:.25rem">
    <strong style="font-size:var(--mv-text-base)">Stone &amp; Linen Studio · back office</strong>
    <span style="font-size:var(--mv-text-sm);color:var(--mv-fg-muted)">Notifications drop into the tray at the bottom right. Errors weigh more.</span>
  </div>
  <div style="display:flex;flex-wrap:wrap;gap:.5rem;justify-content:center;max-width:36rem">
    <button class="mv-button" data-variant="outline" type="button" data-t="success">Order shipped</button>
    <button class="mv-button" data-variant="outline" type="button" data-t="info">New customer review</button>
    <button class="mv-button" data-variant="outline" type="button" data-t="warning">Low stock</button>
    <button class="mv-button" data-variant="destructive" type="button" data-t="error">Payment declined</button>
    <button class="mv-button" data-variant="secondary" type="button" data-t="burst">Burst ×4</button>
  </div>
  <p style="margin:0;font-size:.8125rem;color:var(--mv-fg-muted)">Grab a card and <strong style="color:var(--mv-fg);font-weight:500">fling it</strong> to dismiss · <strong style="color:var(--mv-fg);font-weight:500">Alt + T</strong> then <strong style="color:var(--mv-fg);font-weight:500">Delete</strong> from the keyboard</p>
  <mv-toast-physics position="bottom-right"></mv-toast-physics>
</div>
<script type="module">
  // In an app: import { toast } from "./components/toast-physics/toast-physics.js";
  const { toast } = await customElements.whenDefined("mv-toast-physics");
  const n = () => 4800 + Math.floor(Math.random() * 400);
  const demos = {
    success: () => toast.success(`Order #${n()} shipped`, { description: "Parcelway · arriving Thursday, Sep 24" }),
    info: () => toast.info("New customer review ★★★★★", { description: "“Gorgeous mugs, beautifully packed.” (Hannah K.)" }),
    warning: () => toast.warning("Low stock: Stoneware Mug 12 oz", { description: "Only 3 left. Time to restock.", action: { label: "Reorder", onClick: () => toast.success("Purchase order sent to the studio") } }),
    error: () => toast.error("Payment declined", { description: "Order #4827 · card expired, the customer has been notified.", action: { label: "Retry", onClick: () => {} } }),
    burst: () => ["info", "success", "warning", "success"].forEach((k, i) => setTimeout(demos[k], i * 170)),
  };
  document.getElementById("tp-demo").addEventListener("click", (e) => {
    const k = e.target.closest("[data-t]")?.dataset.t;
    if (k) demos[k]();
  });
  // Arrival: two light cards, then an error that sinks beneath them.
  setTimeout(() => toast.info("New customer review ★★★★★", { description: "“Gorgeous mugs, beautifully packed.” (Hannah K.)", duration: 16000 }), 150);
  setTimeout(() => toast.success("Order #4821 shipped", { description: "Parcelway · arriving Thursday, Sep 24", duration: 16000 }), 450);
  setTimeout(() => toast.error("Payment declined", { description: "Order #4827 · card expired, the customer has been notified.", duration: 20000 }), 1000);
</script>

API

Attributes

Name유형DefaultDescription
positionbottom-right | bottom-left | bottom-center | top-right | top-left | top-centerbottom-rightTray corner. At the top, gravity flips: cards rise and stack against the top edge.
maxnumber5Number of cards in the tray; beyond that, the oldest of the lightest cards is removed.
durationnumber6000Default duration (ms); errors stay 1.6× longer.
gravitynumber1Gravity multiplier (1 ≈ 2200 px/s²).
labelstringNotificationsAccessible name of the region.

Properties

Name유형Description
toastsobject[]Read-only: notifications in the tray, top to bottom.

Methods

NameDescription
toast(message, options) → idExported from toast-physics.js (also MvToastPhysics.toast). options: type (default | success | info | warning | error), description, duration (ms or Infinity), id (same id = updated in place, with a little hop), action { label, onClick(e, { id, dismiss }) }, important (assertive announcement), closeLabel, toaster (selector), onDismiss, onAutoClose. Creates a <mv-toast-physics> if needed.
toast.success / .info / .warning / .error(message, options)Typed shortcuts.
toast.dismiss(id?)Dismisses one notification, or all of them.
element.add(options) / element.dismiss(id?)Same operations on a specific tray.

Events

NameDescription
mv-toastNew notification. detail: { id, type }.
mv-dismissdetail: { id, reason } (timeout, fling, close, keyboard, action, overflow, dismiss).
mv-settleThe pile is at rest. detail: { order } (ids top to bottom).

CSS classes

NameDescription
mv-toast-physics-item / -icon / -content / -title / -description / -action / -closeGenerated parts; data-type on the card, data-dragging while grabbed, data-sinking while a heavy card sinks.

CSS variables

NameDefaultDescription
--mv-toast-physics-width22remTray width (an error takes 100%, a warning 95%, the rest 90%).
--mv-toast-physics-heightmin(68vh, 34rem)Tray height (drop height).
--mv-toast-physics-offset1.25remDistance from the screen edges.
--mv-toast-physics-bgvar(--mv-surface-raised)Card background.

Accessibility

A region named “Notifications (Alt+T)” in the top layer; each new card is announced in a polite live region (assertive for errors and important:true), separate from the list so reordering is never re-announced. Cards form a focusable list whose DOM order follows the visual order: Alt+T moves focus there, ↑/↓/Home/End navigate, Delete, Backspace or Escape dismiss the card and focus moves to its neighbor, then back to the original element. The close button shows on hover, on focus and always on touch. Timers pause on hover, on keyboard focus, while grabbed and when the tab is hidden (the duration fuse too). Reduced motion: a classic stacked list, heaviest at the bottom, with no physics or fling. Forced colors: cards keep their system border, the fuse is hidden. Moving the tray in the DOM keeps the cards and their remaining time. Known limits: a Node passed as message or description is moved into the card, not copied; the region label and the keyboard hint are in English (label attribute, closeLabel option); on a narrow screen the tray spans the width and covers the page corner while cards are shown.

이 페이지는 AI로 번역되었습니다. 번역 문제 신고