import { useState } from "react"; import { Dialog, DialogAction, DialogCancel, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@godxjp/ui/feedback"; import { FormField, Textarea } from "@godxjp/ui/data-entry"; import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@godxjp/ui/data-display"; import { Button, Text } from "@godxjp/ui/general"; import { Flex, PageContainer } from "@godxjp/ui/layout"; /** Every branch of DialogHeader's `tone`, in the order the vocabulary declares them. */ const headerTones = [ "default", "success", "warning", "destructive", "info", "muted", "neutral", ] as const; /** Order lines behind the non-modal payment dialog — they stay editable while it is open. */ const initialLines = [ { id: "coffee", name: "ブレンドコーヒー", unitPrice: 480, quantity: 2 }, { id: "sandwich", name: "ミックスサンド", unitPrice: 650, quantity: 1 }, { id: "cake", name: "チーズケーキ", unitPrice: 520, quantity: 1 }, ]; const yen = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }); /** * Dialog · compound controlled modal for form-style flows. Always control via * open + onOpenChange. Include DialogHeader > DialogTitle (required for a11y). * Use DialogFooter for primary/cancel actions. */ export default function Demo() { const [createOpen, setCreateOpen] = useState(true); const [detailOpen, setDetailOpen] = useState(false); const [toneOpen, setToneOpen] = useState(false); const [headerTone, setHeaderTone] = useState<(typeof headerTones)[number]>("default"); const [terminateOpen, setTerminateOpen] = useState(false); const [reason, setReason] = useState(""); const [paymentOpen, setPaymentOpen] = useState(false); const [lines, setLines] = useState(initialLines); const total = lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0); const changeQuantity = (id: string, delta: number) => { setLines((current) => current.map((line) => line.id === id ? { ...line, quantity: Math.max(0, line.quantity + delta) } : line, ), ); }; return ( Form-style modal (pre-opened) Controlled via open + onOpenChange. DialogTitle is required for screen-reader accessibility. Hold open while pending to prevent double-submit. {/* data-axe-open: check:frame-axe presses this before its overlay scan. */} 新規仕訳を作成 借方・貸方科目と金額を入力して保存してください。 借方科目 売掛金 貸方科目 売上高 金額 ¥ 120,000 Read-only detail pop-up No footer action buttons needed, just a close trigger. Suitable for audit trail, attachment preview, or approval history. 取引 #JE-2024-0042 2024年3月31日 · 月次決算仕訳 借方 売掛金 ¥ 240,000 貸方 売上高 ¥ 240,000 摘要 3月売上計上 DialogHeader tone band (every branch) DialogHeader also takes a prop-driven form: title, subtitle and an extra slot, plus tone. tone tints only the header band and leaves the text at the foreground colour, so the dialog never turns into a loud solid fill. default paints no band at all; muted and neutral deliberately share the same quiet band. Close the pre-opened dialog above with Escape, then pick a branch: the same dialog re-opens with that header. {headerTones.map((tone) => ( ))} {headerTone}} /> 帯の色だけが tone に追従し、見出しと本文の文字色は変わりません。extra スロットは帯の右端に固定され、閉じるボタンとは重なりません。 Destructive confirmation with a required field variant is the one prop that decides three things that always travel together: the ARIA role, whether an outside click dismisses, and the primary action emphasis. variant="destructive" renders role="alertdialog", ignores an outside click and tints DialogAction, while the surface stays a plain Dialog, so a required input can sit inside it. That combination used to need a rewrite: Dialog had the form but lost the role, AlertDialog had the role but its parts assume a two-button body. Escape still closes, exactly as the AlertDialog family already does.