/** * ServiceRolePanel — ロール一覧 ⇄ 詳細 (canonical master-detail roles surface). * * MasterDetail (rail=master) underneath owns all geometry: two tracks at 1440/1024, stacked * list-then-detail at 390. The panel adds selection (aria-current rows), locked system roles, * the built-in destructive AlertDialog (onDeleteRole fires only after confirm) and #216 * lifecycle states. The detail body is consumer-composed — here a PermissionMatrix. */ import * as React from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, Descriptions, PermissionMatrix, } from "@godxjp/ui/data-display"; import { Flex, PageContainer, ServiceRolePanel } from "@godxjp/ui/layout"; import { toast } from "@godxjp/ui/feedback"; import { grantKey } from "@godxjp/ui/lib/permission-grid"; const ROLES = [ { id: "owner", name: "オーナー", description: "契約・請求を含む全操作", memberCount: 1, locked: true, }, { id: "operator", name: "オペレーター", description: "日常運用の操作", memberCount: 12 }, { id: "analyst", name: "アナリスト", description: "閲覧とレポート出力", memberCount: 34 }, ]; const PERMISSIONS = [ { id: "instance.manage", name: "インスタンス管理", group: "運用" }, { id: "member.invite", name: "メンバー招待", group: "管理" }, { id: "report.export", name: "レポート出力", group: "レポート" }, ]; const GRANTS = new Set([ ...PERMISSIONS.map((p) => grantKey("owner", p.id)), grantKey("operator", "instance.manage"), grantKey("operator", "member.invite"), grantKey("analyst", "report.export"), ]); export default function Demo() { const [selected, setSelected] = React.useState("operator"); return ( 選択 + 削除確認 locked の「オーナー」に削除ボタンは出ない。削除は AlertDialog で確認後に onDeleteRole。 {/* Two panels share this page, so each names its regions DISTINCTLY — duplicate landmark names fail axe landmark-unique (same pattern as the master-detail docs page). Single-instance apps keep the localized defaults. */} toast.success(`ロール ${roleId} を削除しました(デモ)`)} masterViewport="compact" masterLabel="ロール一覧(編集可)" detailLabel="選択中ロールの詳細" > {(role) => role && ( {role.name} {role.description ?? "—"} {String(role.memberCount ?? 0)} ) } 読み取り専用 readOnly では削除などの変更系アフォーダンスが消える。 {}} readOnly masterLabel="ロール一覧(読み取り専用)" detailLabel="ロールの詳細(読み取り専用)" > {(role) => role && ( {role.name} ) } ライフサイクル状態(#216 の語彙) loading → denied → error → empty の優先順位。 {}} /> ); }