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 と上がります。
);
}