Detent web components
By Shane Logsdon

Commerce & auth

Money Card

Implemented

Interactive financial wallet card with quick-send contacts and animated transfer sheets

detent-money-card

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-data
Group
Commerce & auth

14.8 KB gzip for the class module

Variants

Sample data; nothing is fetched or sent. The card above the Variants heading shows the component's documented defaults (five quick-send contacts, the last transaction and the send/receive sheets). This one is filled through the property-only `user`, `contacts` and `transactions` surface in euros. Open the settings drawer to mask every amount, and the receive sheet to copy the payment link; both emit detent-send/detent-request with { req: { contact, amount, note } } and the demo only logs them.

Recorded states

  • pressed recorded state; no setter
  • error recorded state; no setter
  • closed recorded state; no setter
  • dragging recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/money-card/define.js';

// or register it yourself
import { DetentMoneyCard } from '/components/money-card/money-card.js';

Attributes

  • balance
  • currency
  • locale
  • payment-link

Properties

  • user
  • contacts
  • transactions
  • onSend
  • onRequest
  • onViewAll

Slots

  • default
  • src

Events

  • detent-send — {}
  • detent-request — {}
  • detent-view-all — {}
  • detent-close — {}
  • detent-exited — {}
  • detent-submit — {}

Example

Runnable example: site/examples/money-card.ts. The snippet below is generated from the catalogued attributes.

<detent-money-card
  balance="52002.5"
  currency="USD"
  locale="en-US"
  payment-link="pay.example/ana"
></detent-money-card>

Property-only values

const element = document.querySelector('detent-money-card');
if (element !== null) {
  // user: { name: string; avatar: string }
  element.user = undefined;
  // contacts: Contact[]
  element.contacts = [];
  // transactions: Transaction[]
  element.transactions = [];
  // onSend: (req: TransferRequest) => Promise<void>
  element.onSend = () => {};
  // onRequest: (req: TransferRequest) => Promise<void>
  element.onRequest = () => {};
  // onViewAll: () => void
  element.onViewAll = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/MoneyCard.tsx sha256 3e7030d1f578…

Native files

The native module and its artifacts:

  • src/components/money-card/money-card.ts
  • src/components/money-card/styles.css
  • src/components/money-card/define.ts
  • docs/components/money-card.md
  • site/examples/money-card.ts
  • tests/browser/money-card.spec.ts
  • evidence/components/money-card.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
balancebalancenumber52002.5Kebab-case attribute; camelCase property.
currencycurrencystring'USD'Kebab-case attribute; camelCase property.
localelocalestring'en-US'Kebab-case attribute; camelCase property.
payment-linkpaymentLinkstring'pay.example/ana'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
user{ name: string; avatar: string }DEFAULT_USERProperty-only: assign from script; not accepted as an attribute.
contactsContact[]DEFAULT_CONTACTSProperty-only: assign from script; not accepted as an attribute.
transactionsTransaction[]DEFAULT_TRANSACTIONSProperty-only: assign from script; not accepted as an attribute.
onSend(req: TransferRequest) => Promise<void>undefinedProperty-only: assign from script; not accepted as an attribute.
onRequest(req: TransferRequest) => Promise<void>undefinedProperty-only: assign from script; not accepted as an attribute.
onViewAll() => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-sendonSend(req: TransferRequest) => Promise<void>Composed, bubbles; fires only for a user action.
detent-requestonRequest(req: TransferRequest) => Promise<void>Composed, bubbles; fires only for a user action.
detent-view-allonViewAll() => voidComposed, bubbles; fires only for a user action.
detent-closeonClose() => voidComposed, bubbles; fires only for a user action.
detent-exitedonExited() => voidComposed, bubbles; fires only for a user action.
detent-submitonSubmit(req: TransferRequest) => Promise<void>Composed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.
srcConsumer content; rendered through a slot, never re-created.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React portals replaced with native dialog/top-layer and DOM insertion.
  • detent-submit is recorded but never dispatched: upstream onSubmit is TransferSheet.params.onSubmit, the same user action as detent-send/detent-request, and emitting both would be two events for one action (PC-6.6).
  • detent-send and detent-request are fire-and-forget: the upstream promise success screen and rejection message are consumer-owned, and the sheet stays open until the consumer updates it.
  • React nodes become strings (user.avatar, contact.avatar, Transaction.icon); BallIcon() returns a local SVG and the icon value "ball" renders it.
  • Image values are checked against the R-3 policy: data: and javascript: values never become an img src.
  • Debit amounts keep the minus sign (-0.00) where upstream printed 0.00 (PC-12.6).
  • The balance panel uses ink #101828 on a lightened blue gradient because the upstream white at 85% failed 4.5:1.
  • Masking also covers the transfer hint and the live region, so no hidden balance survives anywhere in the shadow tree.
  • mode/open/title/labelId/initialContactId are internal Sheet/TransferSheet params, not host API: observedAttributes is exactly balance, currency, locale and payment-link.
  • Framer springs become a cubic-bezier(0.23,1,0.32,1) transition plus one rAF counter (PC-10.4 substitution; the motion gate stays open).
  • className is replaced by documented CSS parts and custom properties; BallIcon and the sheet icons are local SVGs.

What it does

A wallet card with a balance panel, quick-send contacts, a transfer sheet with keypad and contact picker, an activity list and a settings drawer.

Upstream behaviour
  • Animated balance counter utilizing MotionValue and custom formatters for fluid number interpolation
  • Interactive send and receive sheets with built-in keypad, contact selector, note input, and error handling
  • Quick-send contact avatars straddling the card boundary with spring-press tactile response
  • Animated transaction feed displaying directional amount badges and expandable activity list
  • Settings drawer with toggleable balance masking and copyable personal payment link
  • Container query architecture ensuring flawless responsiveness across all screen sizes
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React portals replaced with native dialog/top-layer and DOM insertion.
  • detent-submit is recorded but never dispatched: upstream onSubmit is TransferSheet.params.onSubmit, the same user action as detent-send/detent-request, and emitting both would be two events for one action (PC-6.6).
  • detent-send and detent-request are fire-and-forget: the upstream promise success screen and rejection message are consumer-owned, and the sheet stays open until the consumer updates it.
  • React nodes become strings (user.avatar, contact.avatar, Transaction.icon); BallIcon() returns a local SVG and the icon value "ball" renders it.
  • Image values are checked against the R-3 policy: data: and javascript: values never become an img src.
  • Debit amounts keep the minus sign (-0.00) where upstream printed 0.00 (PC-12.6).
  • The balance panel uses ink #101828 on a lightened blue gradient because the upstream white at 85% failed 4.5:1.
  • Masking also covers the transfer hint and the live region, so no hidden balance survives anywhere in the shadow tree.
  • mode/open/title/labelId/initialContactId are internal Sheet/TransferSheet params, not host API: observedAttributes is exactly balance, currency, locale and payment-link.
  • Framer springs become a cubic-bezier(0.23,1,0.32,1) transition plus one rAF counter (PC-10.4 substitution; the motion gate stays open).
  • className is replaced by documented CSS parts and custom properties; BallIcon and the sheet icons are local SVGs.
Previous: Metric Sparkline Next: Morphing Blob

Docs

Components

Project