import { formatDate } from "@godxjp/ui/datetime"; import { Avatar, AvatarFallback, Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@godxjp/ui/data-display"; import { Button, Text } from "@godxjp/ui/general"; import { Flex, PageContainer, Separator } from "@godxjp/ui/layout"; /** * Separator · Radix UI Separator wrapper for tokenized dividers. Default is * horizontal (section breaks). Set orientation="vertical" only when the parent * gives it a stable height (e.g. a row Flex with align="stretch"). decorative * defaults to true · set false only when the divider carries semantic meaning for * a11y. * * `label` INTERRUPTS the rule: a message stream's day divider, a "new messages" * watermark, an auth conjunction. A labelled rule flips `decorative` to false, so it is a real * role="separator" NAMED by the label (the visible node is aria-hidden, so the string is announced * exactly once). `labelAlign` is a controlled vocabulary value — start | center | end, logical, so * it flips under dir="rtl" — and `tone` moves the rule AND the label together, never colour alone. * * Composed only from real @godxjp/ui components; every date below is formatted with the package's * Intl/CLDR date subsystem on the active locale, never hand-built. */ /** Fixed ISO-8601 instants so the page is deterministic across runs and locales. */ const YESTERDAY_ISO = "2026-08-21"; const TODAY_ISO = "2026-08-22"; const LABEL_ALIGNMENTS = ["start", "center", "end"] as const; const TONES = ["default", "muted", "primary", "success", "warning", "destructive", "info"] as const; /** Stream fixtures — plain data, split by the calendar boundary each divider marks. */ const BEFORE_YESTERDAY = [ { initial: "佐", name: "佐藤 智", time: "09:12", body: "おはようございます。昨夜のデプロイは無事完了しました。", }, { initial: "Ng", name: "Nguyễn Minh", time: "09:20", body: "Cảm ơn anh. Em sẽ theo dõi log trong buổi sáng.", }, ]; const YESTERDAY = [ { initial: "佐", name: "佐藤 智", time: "14:03", body: "請求バッチの再実行を 15:00 に予定しています。", }, ]; const TODAY = [ { initial: "Ng", name: "Nguyễn Minh", time: "08:41", body: "Batch đã chạy xong, không có bản ghi lỗi nào.", }, { initial: "佐", name: "佐藤 智", time: "09:02", body: "ありがとうございます。今週分のレポートにまとめます。", }, ]; export default function Demo() { return ( 水平区切り(既定) orientation を省略すると horizontal。セクション間やフォームグループの分割に使う。 raw border div は使わない。 基本情報 取引先名・登録番号・請求先住所 支払条件 支払サイト・通貨・消費税区分 銀行口座 振込先金融機関・口座番号 垂直区切り · ツールバーグループ orientation="vertical" は親が安定した高さを与えているときのみ使用。 align="stretch" の Flex 行で自然な高さを継承する。 decorative={false} · セマンティック区切り decorative={false} にすると role="separator" が付与され スクリーンリーダーが読み上げる。意味のある区切りにのみ使う。 承認済み請求書 12件 保留中請求書 4件 却下済み請求書 2件 {/* ── — the real screen the labelled rule exists for ─────────────────────────── */} 実画面 · チャンネルの投稿ストリーム 日付が変わる境目に label 付きの区切りを置き、未読の先頭に "新しいメッセージ" の透かしを置く(Slack / Mattermost の慣例)。日付は必ず Intl / CLDR の日付サブシステムで整形する。未読の透かしは tone="primary" で線とラベルの両方が動くので、色だけの区別にならない。 {BEFORE_YESTERDAY.map((post) => ( {post.initial} {post.name} {post.time} {post.body} ))} {/* Day divider — the label is the calendar boundary, formatted on the active locale. */} {YESTERDAY.map((post) => ( {post.initial} {post.name} {post.time} {post.body} ))} {/* Unread watermark — content, not decoration, so it is announced. */} {TODAY.map((post) => ( {post.initial} {post.name} {post.time} {post.body} ))} labelAlign · start | center | end boolean ではなく controlled vocabulary。短い側の線だけが --separator-label-inset で測られるグリッドトラックなので、dir="rtl" では start と end が自動的に入れ替わる。 {LABEL_ALIGNMENTS.map((align) => ( labelAlign="{align}" ))} dir="rtl" · labelAlign="start"(短い側が右に反転)
tone · 線とラベルが一緒に動く tone は線の色だけでなくラベルの色も変える。色だけの区別にしないことで forced-colors や色覚特性のある読者にも意味が残る(WCAG 1.4.1)。 {TONES.map((tone) => ( tone="{tone}" ))} content stress · 長いラベル・空ラベル 両側の線は minmax(0, 1fr) なので、翻訳が伸びても線が潰れるだけでコンテナからはみ出さない。 空文字や空白だけの label はラベル無しと同じ扱いになり、素の線に戻る。 label=" "(空白のみ)→ 素の区切り線
); }