[as] feat: 행정지원 패널 전체접기/펼침 기능

This commit is contained in:
2026-08-26 11:04:37 +09:00
parent 816b6b2ca8
commit cff7b7da30
8 changed files with 311 additions and 158 deletions
+4
View File
@@ -1,5 +1,9 @@
export { NoData } from "./no-data";
export { PageLayout } from "./page-layout";
export { SectionCollapsible } from "./section-collapsible";
export {
SectionCollapsibleGroup,
SectionCollapsibleToggle,
} from "./section-collapsible-group";
export type { TextShimmerProps } from "./text-shimmer";
export { TEXT_SHIMMER_CLASS, TextShimmer } from "./text-shimmer";
@@ -0,0 +1,89 @@
"use client";
import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
import { ChevronsDownUp, ChevronsUpDown } from "lucide-react";
import { Button } from "@common/components";
interface SectionCollapsibleGroupValue {
batchOpen: boolean;
batchVersion: number;
expandAll: () => void;
collapseAll: () => void;
}
const SectionCollapsibleGroupContext = createContext<SectionCollapsibleGroupValue | null>(null);
function useSectionCollapsibleGroup() {
return useContext(SectionCollapsibleGroupContext);
}
interface SectionCollapsibleGroupProps {
children: ReactNode;
}
function SectionCollapsibleGroup({ children }: SectionCollapsibleGroupProps) {
const [batch, setBatch] = useState({ open: true, version: 0 });
const expandAll = useCallback(() => {
setBatch((prev) => ({ open: true, version: prev.version + 1 }));
}, []);
const collapseAll = useCallback(() => {
setBatch((prev) => ({ open: false, version: prev.version + 1 }));
}, []);
const value = useMemo(
() => ({
batchOpen: batch.open,
batchVersion: batch.version,
expandAll,
collapseAll,
}),
[batch.open, batch.version, expandAll, collapseAll],
);
return (
<SectionCollapsibleGroupContext.Provider value={value}>
{children}
</SectionCollapsibleGroupContext.Provider>
);
}
function SectionCollapsibleToggle() {
const group = useSectionCollapsibleGroup();
if (!group) return null;
const { batchOpen, expandAll, collapseAll } = group;
return (
<div className="sticky top-[6rem] z-30 flex justify-start gap-2 bg-container px-4 py-2">
<Button
type="button"
size="sm"
color="white"
className="gap-1 px-4 tracking-tight"
aria-expanded={batchOpen}
onClick={collapseAll}
>
<ChevronsDownUp className="shrink-0" />
전체접기
</Button>
<Button
type="button"
size="sm"
color="white"
className="gap-1 px-4 tracking-tight"
aria-expanded={batchOpen}
onClick={expandAll}
>
<ChevronsUpDown className="shrink-0" />
전체펼침
</Button>
</div>
);
}
export { SectionCollapsibleGroup, SectionCollapsibleToggle, useSectionCollapsibleGroup };
+15 -2
View File
@@ -1,11 +1,13 @@
"use client";
import type { ReactNode } from "react";
import { type ReactNode, useState } from "react";
import { ChevronDown } from "lucide-react";
import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from "@common/components";
import { useSectionCollapsibleGroup } from "./section-collapsible-group";
interface SectionCollapsibleProps {
title: ReactNode;
defaultOpen?: boolean;
@@ -13,8 +15,19 @@ interface SectionCollapsibleProps {
}
function SectionCollapsible({ title, defaultOpen = true, children }: SectionCollapsibleProps) {
const group = useSectionCollapsibleGroup();
const [open, setOpen] = useState(group?.batchOpen ?? defaultOpen);
const [seenVersion, setSeenVersion] = useState(group?.batchVersion ?? 0);
if (group && group.batchVersion !== seenVersion) {
setSeenVersion(group.batchVersion);
setOpen(group.batchOpen);
}
const collapsibleProps = group ? { open, onOpenChange: setOpen } : { defaultOpen };
return (
<Collapsible defaultOpen={defaultOpen} className="mx-4 rounded-sm border bg-background">
<Collapsible {...collapsibleProps} className="mx-4 rounded-sm border bg-background">
<CollapsibleTrigger asChild>
<button
type="button"