import { useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display"; import { Text } from "@godxjp/ui/general"; import { Flex, PageContainer } from "@godxjp/ui/layout"; import { MegaMenu, type MegaMenuItemProp } from "@godxjp/ui/navigation"; import { BookOpen, Building2, Calculator, CreditCard, FileText, LifeBuoy, ShieldCheck, Users, } from "lucide-react"; /** * MegaMenu — the primary site navigation whose top-level items disclose a full-width panel. * * WAI-ARIA APG **Disclosure Navigation with Top-Level Links**, not Menubar: a bar of links to * PLACES is not a menu of COMMANDS, and `role="menu"` would promise assistive technology an * application menu. That is also why this is not `DropdownMenu` plus a token — `DropdownMenu` is * `role="menu"` by construction. * * The cards below are deliberately NOT tidy. Each one is an edge a real site hits on day one and a * demo page usually hides: a panel with forty links beside a panel with one, a 71-character * unbreakable label, CJK beside Latin, a group carrying an icon and a description beside a bare * column, and the narrow layout where the panel has nowhere to go. */ /** Forty links, which is the size at which a panel stops being a list and starts being a screen. */ const FORTY = Array.from({ length: 40 }, (_, index) => ({ key: `report-${index + 1}`, label: `レポート ${String(index + 1).padStart(2, "0")}`, href: `/reports/${index + 1}`, })); /** The bar every other card varies. CJK labels beside Latin ones, on purpose. */ const ITEMS: MegaMenuItemProp[] = [ { key: "products", label: "製品", icon: , panel: { groups: [ { key: "core", label: "コア業務", icon: , description: "毎日使う基幹アプリ。人事と給与はひとつの従業員台帳を共有します。", links: [ { key: "hr", label: "人事管理 / HR", href: "/hr", icon: , description: "従業員台帳、異動履歴、組織図", }, { key: "payroll", label: "給与計算 / Payroll", href: "/payroll", icon: , description: "月次締め、賞与、年末調整", }, { key: "expense", label: "経費精算 / Expenses", href: "/expense", icon: , description: "申請から振込まで", }, ], }, { // A BARE column beside the decorated one above: no icon, no description, no heading // beyond the label. Both shapes have to look deliberate side by side. key: "addons", label: "アドオン", links: [ { key: "attendance", label: "勤怠管理", href: "/attendance" }, { key: "workflow", label: "ワークフロー", href: "/workflow" }, { key: "mynumber", label: "マイナンバー管理", href: "/mynumber" }, { key: "eol", label: "提供終了予定のモジュール", href: "/eol", disabled: true }, ], }, { key: "platform", label: "Platform", icon: , links: [ { key: "sso", label: "SSO / SAML", href: "/sso" }, { key: "audit", label: "Audit log", href: "/audit" }, { // 71 characters, no space, no hyphen — a real webhook identifier pasted into a nav. // It must WRAP inside its column, never widen the panel past the viewport. key: "webhook", label: "webhookEndpointConfigurationForOutboundPayrollNotificationsV2", href: "/webhooks", description: "エンドポイント設定", }, ], }, ], footer: ( すべての製品と価格は製品一覧ページでご覧いただけます。 ), }, }, { key: "reports", label: "レポート", icon: , panel: { groups: [ { key: "all", label: "標準レポート (40)", links: FORTY.slice(0, 20) }, { key: "more", label: "続き", links: FORTY.slice(20) }, ], }, }, { key: "support", label: "Support", icon: , panel: { // ONE link. The narrowest panel a consumer will ever build, and it must not look broken // beside the forty-link one above. groups: [ { key: "contact", label: "お問い合わせ", links: [{ key: "contact-us", label: "サポートに連絡する", href: "/support" }], }, ], }, }, { key: "docs", label: "ドキュメント", href: "/docs", icon: }, { key: "pricing", label: "料金", href: "/pricing" }, ]; export default function MegaMenuDoc() { const [route, setRoute] = useState("payroll"); return ( {/* 1 · IN A REAL BAR — the component where it actually lives. */} サイトのヘッダー · ロゴの隣に置く バーのふるまい(roving tabindex・aria-expanded・Escape で戻る)はすべて `MegaMenu` が持ちます。現在のルートは `value` で渡し、`aria-current="page"` としてバーとパネルの両方に出ます。
`Topbar` のスロットには入れないでください。実測した理由が 2 つあります。`topbar-center` は約 1280px 未満で `display: none` になるのでノート PC の幅でナビゲーションごと消え、`topbar-start` は `overflow: clip` かつ `flex-wrap: nowrap` の 1 行なので、狭い画面で縦に開く アコーディオンが行からはみ出します(375px で 58 要素がビューポート外)。電話幅のサイトナビは バーの中ではなく `Sheet` の中に置く、というのが実サイトの答えです。
GodX
{/* 2 · THE EDGES — one link beside forty, an unbreakable token, CJK beside Latin. */} 端 · 1 件のパネルと 40 件のパネル 「レポート」は 40 件、「Support」は 1 件。40 件のパネルは列に折り返して高さを抑え、それでも入り切らなければブロック方向にスクロールします。「Platform」列の 71 文字の webhook 識別子は列の中で折り返し、バーやパネルをビューポートの外へ広げません。 {/* 3 · HOVER INTENT — the mode antd defaults to, and the diagonal it has to survive. */} triggerAction="hover" · 斜めに動くマウス Ant Design の `triggerSubMenuAction`。トリガーから離れただけでは閉じません — 閉じるのは nav 全体からポインタが出たときだけで、それも `closeDelay`(既定 100ms、antd の `subMenuCloseDelay` と同じ)の猶予があります。だから「製品」から斜め下のリンクへ一直線に動けます。クリックでも開くので、タッチ端末で行き止まりになりません。 {/* 4 · NARROW — the same disclosure, in flow. */} 狭い画面 · 同じ disclosure がそのままアコーディオンになる 全幅パネルは 375px では行き場がありません。768px 未満ではバーが縦に積み、パネルは自分のトリガーの真下にインフローで開きます。DOM もARIA も同じ(`aria-expanded` / `aria-controls`)で、Escape もそのまま効きます。Menubar ではなく Disclosure を選んだ配当がこれです。ブラウザを 768px より狭くして確かめてください。 {/* 5 · WHAT IT IS NOT. */} これは DropdownMenu ではない `DropdownMenu` は `role="menu"` / `role="menuitem"`(コマンドのメニュー)です。リンクの集まりにこれを使うと、スクリーンリーダーはアプリケーションメニューとして読み上げ、Tab はウィジェット全体から出てしまいます。これがメガメニューの典型的な a11y 欠陥で、このコンポーネントが存在する理由です。操作(削除・名前の変更)には `DropdownMenu`、場所への移動には `MegaMenu` を使ってください。 現在のルート: {route}
); }