{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "payments",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Amount → payee → method → date → memo.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatDate } from \"@/lib/ledger/fiscal-period\";\nimport type { MoneyValue } from \"@/lib/ledger/money.types\";\nimport { AmountInput } from \"@/components/ledger/amount-input\";\nimport { Button } from \"@/components/ledger/button\";\nimport { ConfirmPaymentDialog } from \"@/components/ledger/confirm-payment-dialog\";\nimport { CurrencySelect } from \"@/components/ledger/currency-select\";\nimport { ChevronDownIcon } from \"@/components/ledger/icons\";\nimport { Money } from \"@/components/ledger/money\";\nimport { type Payee, PayeeSelect } from \"@/components/ledger/payee-select\";\nimport { type PaymentMethod, PaymentMethodList } from \"@/components/ledger/payment-method-list\";\n\nexport interface PaymentFormProps {\n  balance: MoneyValue;\n  className?: string;\n  methods: PaymentMethod[];\n  onSubmit?: (payload: {\n    amount: number;\n    currency: string;\n    payeeId: string;\n    methodId: string;\n    date: string;\n    memo: string;\n  }) => void;\n  payees: Payee[];\n  /** Computed by the caller — components never do arithmetic. */\n  quote?: (amount: number, methodId: string) => { fee: MoneyValue; arrival: Date };\n}\n\n/**\n * amount → payee → method → date → memo. That order: the amount is what the\n * user came to do, and it contextualizes everything after it.\n *\n * Validation on blur, never per keystroke. The submit button states the\n * amount. On phone the submit is bottom-anchored above the safe area and\n * above the keyboard.\n */\nexport function PaymentForm({ payees, methods, balance, quote, onSubmit, className }: PaymentFormProps) {\n  const [amount, setAmount] = React.useState<number | null>(null);\n  const [currency, setCurrency] = React.useState(balance.currency);\n  const [payeeId, setPayeeId] = React.useState<string | null>(null);\n  const [methodId, setMethodId] = React.useState(methods[0]?.id ?? \"\");\n  const [date, setDate] = React.useState(() => new Date().toISOString().slice(0, 10));\n  const [memo, setMemo] = React.useState(\"\");\n  const [reference, setReference] = React.useState(\"\");\n  const [showDetails, setShowDetails] = React.useState(false);\n  const [confirming, setConfirming] = React.useState(false);\n\n  const payee = payees.find((p) => p.id === payeeId);\n  const estimate = quote && amount != null ? quote(amount, methodId) : undefined;\n  const ready = amount != null && amount > 0 && payee != null && methodId !== \"\";\n\n  return (\n    <form\n      className={cn(\"flex flex-col gap-6\", className)}\n      onSubmit={(e) => {\n        e.preventDefault();\n        if (ready) setConfirming(true);\n      }}\n    >\n      <div className=\"flex items-end gap-3\">\n        <AmountInput\n          className=\"flex-1\"\n          currency={currency}\n          max={balance}\n          onMaxClick={() => setAmount(Number(balance.amount))}\n          onValueChange={setAmount}\n          value={amount}\n        />\n        <CurrencySelect\n          className=\"mb-1\"\n          onValueChange={setCurrency}\n          recents={[\"USD\", \"EUR\", \"GBP\"]}\n          value={currency}\n        />\n      </div>\n\n      {/* Fee and arrival update live below the amount, with a crossfade —\n          never a count-up. */}\n      {estimate ? (\n        <p\n          className=\"ledger-crossfade -mt-4 flex items-center gap-2 text-caption text-text-secondary\"\n          key={`${amount}-${methodId}`}\n        >\n          Fee <Money size=\"xs\" tone=\"inherit\" value={estimate.fee} />\n          <span aria-hidden=\"true\">·</span>\n          Arrives {formatDate(estimate.arrival)}\n        </p>\n      ) : null}\n\n      <Field label=\"Recipient\">\n        <PayeeSelect onValueChange={setPayeeId} payees={payees} value={payeeId} />\n      </Field>\n\n      <Field label=\"Pay from\">\n        <PaymentMethodList methods={methods} onValueChange={setMethodId} value={methodId} />\n      </Field>\n\n      <Field label=\"Send on\">\n        <input\n          className=\"numeric h-10 w-full rounded-sm border border-border bg-surface px-3 text-body outline-none focus-visible:border-accent\"\n          onChange={(e) => setDate(e.target.value)}\n          type=\"date\"\n          value={date}\n        />\n      </Field>\n\n      {/* Progressive disclosure: memo, reference and attachments. */}\n      <div>\n        <button\n          aria-expanded={showDetails}\n          className=\"flex items-center gap-1 text-label font-medium text-accent\"\n          onClick={() => setShowDetails((v) => !v)}\n          type=\"button\"\n        >\n          <ChevronDownIcon\n            aria-hidden=\"true\"\n            className={cn(\n              \"size-4 transition-transform duration-2 ease-standard\",\n              showDetails && \"rotate-180\",\n            )}\n          />\n          Add details\n        </button>\n        {showDetails ? (\n          <div className=\"mt-3 space-y-4\">\n            <Field label=\"Memo\">\n              <input\n                className=\"h-10 w-full rounded-sm border border-border bg-surface px-3 text-body outline-none focus-visible:border-accent\"\n                maxLength={140}\n                onChange={(e) => setMemo(e.target.value)}\n                value={memo}\n              />\n            </Field>\n            <Field label=\"Reference\">\n              <input\n                className=\"reference-numeric h-10 w-full rounded-sm border border-border bg-surface px-3 text-body outline-none focus-visible:border-accent\"\n                onChange={(e) => setReference(e.target.value)}\n                value={reference}\n              />\n            </Field>\n          </div>\n        ) : null}\n      </div>\n\n      <div\n        className=\"sticky bottom-0 -mx-6 border-t border-border-subtle bg-surface px-6 pt-4 md:static md:mx-0 md:border-0 md:px-0 md:pt-0\"\n        style={{ paddingBottom: \"max(16px, env(safe-area-inset-bottom))\" }}\n      >\n        <Button className=\"w-full\" disabled={!ready} size=\"lg\" type=\"submit\" variant=\"primary\">\n          {amount != null && amount > 0 ? (\n            <>\n              Send <Money size=\"sm\" tone=\"inherit\" value={{ amount, currency }} />\n            </>\n          ) : (\n            \"Send payment\"\n          )}\n        </Button>\n      </div>\n\n      {ready && payee ? (\n        <ConfirmPaymentDialog\n          amount={{ amount: amount ?? 0, currency }}\n          arrivalDate={estimate?.arrival ?? new Date()}\n          onConfirm={() =>\n            onSubmit?.({\n              amount: amount ?? 0,\n              currency,\n              date,\n              memo,\n              methodId,\n              payeeId: payee.id,\n            })\n          }\n          onOpenChange={setConfirming}\n          open={confirming}\n          payeeLast4={payee.last4}\n          payeeName={payee.name}\n        />\n      ) : null}\n    </form>\n  );\n}\n\nfunction Field({ label, children }: { label: string; children: React.ReactNode }) {\n  return (\n    <label className=\"block\">\n      <span className=\"mb-2 block text-label font-medium text-text-secondary\">{label}</span>\n      {children}\n    </label>\n  );\n}\n",
      "path": "components/ledger/payment-form.tsx",
      "target": "components/ledger/payment-form.tsx",
      "type": "registry:block"
    }
  ],
  "name": "payment-form",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/amount-input",
    "@lopifi/button",
    "@lopifi/cn",
    "@lopifi/confirm-payment-dialog",
    "@lopifi/currency-select",
    "@lopifi/fiscal-period",
    "@lopifi/icons",
    "@lopifi/money",
    "@lopifi/money.types",
    "@lopifi/payee-select",
    "@lopifi/payment-method-list"
  ],
  "title": "PaymentForm",
  "type": "registry:block"
}
