import { useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display"; import { FormField, ToggleGroup, ToggleGroupItem } from "@godxjp/ui/data-entry"; import { Text } from "@godxjp/ui/general"; import { Flex, PageContainer } from "@godxjp/ui/layout"; /** A folder's tag facets — more chips than any rail is wide, which is the `wrap` case (gh#741). */ const folderTags = [ { value: "design", label: "設計", count: 12 }, { value: "runbook", label: "運用手順", count: 148 }, { value: "security", label: "セキュリティ", count: 37 }, { value: "onboarding", label: "オンボーディング", count: 9 }, { value: "incident", label: "障害報告", count: 64 }, { value: "contract", label: "契約", count: 21 }, { value: "billing", label: "請求", count: 83 }, { value: "release", label: "リリース", count: 156 }, { value: "meeting", label: "議事録", count: 402 }, { value: "hr", label: "人事", count: 18 }, { value: "legal", label: "法務", count: 6 }, { value: "archive", label: "アーカイブ", count: 311 }, ]; /** A one-of-N setting: a list always has A sort order, so this row may never be empty (gh#744). */ const sortOrders = [ { value: "updated", label: "更新日" }, { value: "created", label: "作成日" }, { value: "name", label: "名前" }, { value: "size", label: "サイズ" }, { value: "owner", label: "所有者" }, { value: "relevance", label: "関連度" }, ]; /** * ToggleGroup — single or multi-select toggle set. type='single' for mutually * exclusive modes; type='multiple' for independent selections. Never raw radio * buttons for this pattern. Composed only from real @godxjp/ui components. */ export default function Demo() { const [period, setPeriod] = useState("monthly"); const [formats, setFormats] = useState(["pdf"]); const [view, setView] = useState("list"); const [tags, setTags] = useState(["design", "runbook"]); const [facet, setFacet] = useState("design"); const [sort, setSort] = useState("updated"); return ( Single selection (type=“single”) Mutually exclusive · switching fiscal period. value is string, onValueChange receives string. 日次 月次 四半期 年次 disallowEmptySelection · 空を許すかどうかが role を決める ARIA には「もう一度押して選択を外す」という radio がない。選択のある radiogroup には必ずちょうど一つ checked な項目があるので、ユーザーが今押したばかりの radiogroup が空になった状態はスクリーンリーダーが読み上げられない。だから空を許すかどうかが role を決める(gh#744)。既定(空を許す)は role="group" + 各項目 aria-pressed で、同じチップをもう一度押せば選択が外れ onValueChange("") が飛ぶ。disallowEmptySelection を付けると role="radiogroup" + role="radio" / aria-checked になり、選択中の項目をもう一度押しても外れず、矢印キーが APG の radio group どおり選択ごと動く。 既定(空を許す)· タグ絞り込み · role="group" + aria-pressed。矢印キーはフォーカスだけを動かす setFacet(v as string)} aria-label="ファセット絞り込み" > {folderTags.slice(0, 6).map((tag) => ( {tag.label} ))} 選択中: {facet === "" ? "なし" : facet} disallowEmptySelection · 並び替え · role="radiogroup" + aria-checked。タブ位置は選択中の項目ひとつ、矢印キーが選択ごと動く setSort(v as string)} aria-label="並び替え" > {sortOrders.map((order) => ( {order.label} ))} 選択中: {sort === "" ? "なし" : sort} Multiple selection (type=“multiple”) Independent toggles · output format selection. value is string[], onValueChange receives string[]. setFormats(v as string[])} > PDF CSV Excel メール View density switcher Common use case: switching between display modes in a list or dashboard. 一覧 カード コンパクト variant · default / outline / soft variant は ToggleGroup にだけ指定すれば全アイテムへ伝播する(各 ToggleGroupItem への繰り返し指定は不要)。default は選択時に背景が塗られ、outline は枠線で示す。soft は休止時から --secondary で塗られるので、チップの列に使う。 variant="default" PDF CSV Excel variant="outline" PDF CSV Excel variant="soft" PDF CSV Excel shape · default / pill / sharp shape も variant / size と同じく行の決定なので、ToggleGroup に一度書けば全アイテムへ伝播する。値は Button / Badge と同じ三つで、読む radius トークンも同じ。チップの列は soft + pill。 {(["default", "pill", "sharp"] as const).map((shape) => ( shape="{shape}" PDF CSV Excel ))} wrap · 折り返すタグ絞り込みの行 wrap を付けた行は、レールに収まらなくなった時点で次の行へ折り返す(gh#741)。名前も真偽値も data-wrap 属性も Flex と同じ。既定は false で、variant によって既定を変えることはしない。320px のレールで測ると 4 項目のセグメントは wrap の有無に関わらず 1 行 32px のままで、変わるのは元から溢れていた行だけだからである。折り返しても gap は 1 つなので、行間と列間は同じ 4px になる。 wrap · variant="soft" shape="pill" size="xs" · タグが 12 個あっても ToggleGroup は 1 つのまま(value / onValueChange も 1 つ、矢印キーは折り返した行をまたいで DOM 順に進む) setTags(v as string[])} aria-label="タグで絞り込み" > {folderTags.map((tag) => ( {tag.label} ))} 選択中: {tags.length === 0 ? "なし" : tags.join(" / ")} wrap なし(既定)· セグメントは 1 行 32px のまま、何も変わらない 日次 月次 四半期 年次 size · xs / sm / md / lg size も ToggleGroup にだけ指定すれば全アイテムへ伝播する。md が既定。xs 24px / sm 28px / md 32px / lg 36px はすべて --control-height の同じ段なので、行の高さが先に決まっている場所ではその段を選ぶ。xs は 24px の密な行にセグメントを載せるための段。 size="xs" · 24px の密な行 日次 月次 年次 size="sm" 日次 月次 年次 size="md" 日次 月次 年次 size="lg" 日次 月次 年次 継承と上書き(group → item) group の variant / size は全アイテムへ伝播し、item 側で明示した prop が常に優先される。group だけ・item だけ・両方に指定した 3 つの書き方はすべて同じ描画になる。 group size="sm" · 中央のみ item size="lg" で上書き 日次 月次 年次 group にのみ指定(伝播) 日次 月次 item にのみ指定(従来の書き方・描画は不変) 日次 月次 ); }