import { useState } from "react"; import { Filter, X } from "lucide-react"; import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, EmptyState, } from "@godxjp/ui/data-display"; import { Button, Text } from "@godxjp/ui/general"; import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@godxjp/ui/data-entry"; import { Flex, PageContainer } from "@godxjp/ui/layout"; import { Toolbar, ToolbarGroup } from "@godxjp/ui/navigation"; const STATUS_LABEL: Record = { active: "有効", invited: "招待中", suspended: "停止", }; const ROLE_LABEL: Record = { admin: "管理者", member: "メンバー", guest: "ゲスト", }; /** * Toolbar + ToolbarGroup — the list-page filter strip (Toolbar is the renamed FilterBar, #197). * Place ABOVE the table Card — NEVER inside CardContent flush. * Each labelled filter control goes in a ToolbarGroup; SearchInput is placed directly. * Composed only from real @godxjp/ui components. */ export default function Demo() { const [search, setSearch] = useState(""); const [status, setStatus] = useState("all"); const [fiscalPeriod, setFiscalPeriod] = useState("all"); const [department, setDepartment] = useState("all"); // #197 — sticky filter strip with active-filter chips (clear-one / clear-all). const [memberQuery, setMemberQuery] = useState("田中"); const [memberStatus, setMemberStatus] = useState("active"); const [memberRole, setMemberRole] = useState("all"); const activeChips = [ memberQuery.trim() && { key: "q", label: `検索: ${memberQuery.trim()}`, tone: undefined as "success" | undefined, clear: () => setMemberQuery(""), }, memberStatus !== "all" && { key: "status", label: `状態: ${STATUS_LABEL[memberStatus]}`, tone: "success" as const, clear: () => setMemberStatus("all"), }, memberRole !== "all" && { key: "role", label: `権限: ${ROLE_LABEL[memberRole]}`, tone: undefined, clear: () => setMemberRole("all"), }, ].filter(Boolean) as { key: string; label: string; tone?: "success"; clear: () => void }[]; const hasMemberFilters = activeChips.length > 0; function clearMemberFilters() { setMemberQuery(""); setMemberStatus("all"); setMemberRole("all"); } const hasActiveFilters = search !== "" || status !== "all" || fiscalPeriod !== "all" || department !== "all"; function handleClear() { setSearch(""); setStatus("all"); setFiscalPeriod("all"); setDepartment("all"); } return ( {/* #197 — sticky filter strip with active-filter chips over a long list */} スティッキーフィルター · 適用中のチップ Toolbar `sticky` でリスト上部に固定 · SearchInput + Select スロット、適用中の条件を削除可能なチップで表示 (個別解除 × / 一括解除)。 1024px 以下では縦積みに折り返す。チップの × は Badge の内側ではなく兄弟要素の Button として描画する。 {/* A scroll container so the sticky strip stays pinned while the list scrolls. */}
{/* Active-filter chip row — each chip is a Badge label + a SIBLING × Button. */} {!hasMemberFilters && ( 条件なし )} {activeChips.map((chip) => ( {chip.label} ))} {hasMemberFilters && ( )} {/* Stand-in list body — long enough to scroll under the pinned strip. */} {Array.from({ length: 14 }).map((_, i) => ( {memberQuery.trim() || "田中"} {i + 1} 番 {STATUS_LABEL[memberStatus === "all" ? "active" : memberStatus]} ))}
{/* Full filter bar above a representative table placeholder */} 仕訳一覧フィルター Toolbar + ToolbarGroup · SearchInput is placed directly (no ToolbarGroup); every other control gets a ToolbarGroup with a label. hasActiveFilters drives the clear-all button. {/* Toolbar sits ABOVE the table — shown here inside CardContent for demo only */} {/* Simulated table body */} {/* Minimal toolbar — search only */} SearchInput のみ (ToolbarGroup なし) SearchInput は自己ラベル付きのためToolbarGroup 不要。 単一フィルターの場合は Toolbar + SearchInput だけで完結する。 {/* Department filter example */} 複数 ToolbarGroup · 部門・科目フィルター 勤怠・経費レポート向け。各フィルターを ToolbarGroup でラベルを付けて並べる。 setDepartment("all")}>
); }