import { useState } from "react"; import { BarChart3, LifeBuoy, MessagesSquare, Receipt, Users } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display"; import { Button, Text } from "@godxjp/ui/general"; import { AppLauncher, type AppLauncherLabels, Flex, PageContainer, ResponsiveGrid, Topbar, } from "@godxjp/ui/layout"; const apps = [ { id: "console", name: "Console", href: "/console", icon: , current: true }, { id: "billing", name: "Billing", href: "/billing", icon: }, { id: "people", name: "People", href: "/people", icon: }, { id: "chat", name: "Chat", href: "/chat", icon: }, ]; const groups = [ { label: "More from Acme", apps: [ { id: "support", name: "Support", href: "https://support.example.test", icon: , // An external destination renders a plain anchor and bypasses `linkComponent` — a // client-side router link to another origin is a router asked to route somewhere it // does not own. external: true, }, ], }, ]; const labels: AppLauncherLabels = { trigger: "Acme のアプリ", title: "アプリを切り替える", empty: "利用できるアプリがありません。", loading: "アプリを読み込み中", retry: "再試行", externalHint: "(新しいタブで開きます)", }; /** * AppLauncher — the nine-dot platform app grid. * * WHERE IT SITS AMONG THINGS THAT LOOK LIKE IT: * * - `ServiceLauncherCard` (data-display) is also a launcher tile, but a PAGE-SIZED one — status, * hostname, plan, an action button, a reason it is locked — for a service-catalogue page where * choosing is a considered act. This tile is bar-sized: mark plus name, the whole tile one link, * because changing app is a reflex. A grid of `ServiceLauncherCard`s inside a popover is the * wrong component, not a smaller version of this one. * - `AppShell navRail` says the SAME platform scope as a docked column. Pick ONE. The launcher for * a platform with MANY apps where switching is occasional (the Google Workspace shape); the rail * for a single product where switching workspace is constant enough to deserve permanent screen * width (the Slack shape). Shipping both puts one scope in two places. */ export default function Demo() { const [error, setError] = useState("アプリ一覧を取得できませんでした。"); return ( バーの中のセル trigger は `TopbarItem` なので、高さはバーそのもの(`align-self: stretch`)。 `Button variant="ghost"` を置くと、背の高い帯の中に浮いた丸薬になります。 panel は `responsive="auto"` で、共有トークン `--sheet-responsive-breakpoint-width`(48rem)を境に popover と bottom sheet が入れ替わります。 Acme Console} end={} /> current のアプリだけが `aria-current="page"` を持ちます。 列数と非同期状態 既定は3列(Google と同じ形)。`columns` は1インスタンスの上書きです。 loading と error は grid の代わりに同じ所有 surface へ表示されます。 4 列} end={ } /> loading} end={} /> empty} end={} /> error} end={ setError(undefined)} responsive="popover" /> } /> {!error ? ( ) : null} バー以外の chrome · icon とパネルの向き `appearance="icon"` は正方形の ghost Button。バーではない chrome(nav rail、 カードのヘッダ、ツールバー)のための箱で、glyph は同じです。 パネルは既定で chrome から外へ開きます(bar は下+end、それ以外は inline-end+start)。 位置を変えられる chrome だけが向きを知っているので、`side` / `align` で明示できます。 Launchpad · 画面いっぱい `responsive="fullscreen"` は plaform の start bar のための形。背後のページを ぼかし、その地の上にタイルが並びます(macOS Launchpad / Windows Start)。 幅に関係なく 1つの surface に固定。列数だけが surface に合わせて 3 · 4 · 5 · 6 と上がります。 ); }