feat: 접는 사이드바 구현 (v5 파이프라인 후속)
- sidebarCollapsed 상태 + 조건부 렌더링 - PanelLeftOpen/PanelLeftClose 아이콘 토글 - collapsed 시 아이콘 컬럼 표시 Made-with: Cursor
This commit is contained in:
parent
beb95bf2aa
commit
c0be2f3528
|
|
@ -41,6 +41,7 @@ export default function ScreenManagementPage() {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||||
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
|
|
||||||
const tableCount = useMemo(() => new Set(screens.map((s) => s.tableName).filter(Boolean)).size, [screens]);
|
const tableCount = useMemo(() => new Set(screens.map((s) => s.tableName).filter(Boolean)).size, [screens]);
|
||||||
|
|
||||||
|
|
@ -217,49 +218,75 @@ export default function ScreenManagementPage() {
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
{viewMode === "flow" ? (
|
{viewMode === "flow" ? (
|
||||||
<div className="flex-1 overflow-hidden flex">
|
<div className="flex-1 overflow-hidden flex">
|
||||||
{/* 왼쪽: 트리 구조 */}
|
{/* 왼쪽: 트리 구조 (접기/펼기 지원) */}
|
||||||
<div className="w-[350px] min-w-[280px] max-w-[450px] flex flex-col border-r border-border/50 bg-background/80 backdrop-blur-sm">
|
<div className={`flex flex-col border-r border-border/50 bg-background/80 backdrop-blur-sm transition-all duration-300 ease-in-out ${
|
||||||
{/* 검색 */}
|
sidebarCollapsed ? "w-[48px] min-w-[48px]" : "w-[320px] min-w-[280px] max-w-[400px]"
|
||||||
<div className="flex-shrink-0 p-3 border-b border-border/50">
|
}`}>
|
||||||
<div className="relative">
|
{/* 사이드바 헤더 */}
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<div className="flex-shrink-0 flex items-center justify-between p-2 border-b border-border/50">
|
||||||
<Input
|
{!sidebarCollapsed && <span className="text-xs font-medium text-muted-foreground px-1">탐색</span>}
|
||||||
placeholder="화면 검색..."
|
<Button
|
||||||
value={searchTerm}
|
variant="ghost"
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
size="icon"
|
||||||
className="pl-9 h-9 rounded-xl bg-muted/30 border-border/50 focus:bg-background focus:ring-2 focus:ring-primary/30 transition-colors"
|
className={`h-7 w-7 ${sidebarCollapsed ? "mx-auto" : "ml-auto"}`}
|
||||||
/>
|
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||||
|
>
|
||||||
|
{sidebarCollapsed ? <PanelLeftOpen className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/* 사이드바 접힘 시 아이콘 컬럼 */}
|
||||||
|
{sidebarCollapsed && (
|
||||||
|
<div className="flex-1 flex flex-col items-center gap-2 py-3">
|
||||||
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setSidebarCollapsed(false)}>
|
||||||
|
<Search className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
<div className="mt-auto pb-2">
|
||||||
|
<Badge variant="secondary" className="text-[10px] px-1.5">{screens.length}</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
{/* 트리 뷰 */}
|
{/* 사이드바 펼침 시 전체 UI */}
|
||||||
<div className="flex-1 overflow-hidden">
|
{!sidebarCollapsed && (
|
||||||
<ScreenGroupTreeView
|
<>
|
||||||
screens={filteredScreens}
|
{/* 검색 */}
|
||||||
selectedScreen={selectedScreen}
|
<div className="flex-shrink-0 p-3 border-b border-border/50">
|
||||||
onScreenSelect={handleScreenSelect}
|
<div className="relative">
|
||||||
onScreenDesign={handleDesignScreen}
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
searchTerm={searchTerm}
|
<Input
|
||||||
onGroupSelect={(group) => {
|
placeholder="화면 검색..."
|
||||||
setSelectedGroup(group);
|
value={searchTerm}
|
||||||
setSelectedScreen(null); // 화면 선택 해제
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
setFocusedScreenIdInGroup(null); // 포커스 초기화
|
className="pl-9 h-9 rounded-xl bg-muted/30 border-border/50 focus:bg-background focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||||
}}
|
/>
|
||||||
onScreenSelectInGroup={(group, screenId) => {
|
</div>
|
||||||
// 그룹 내 화면 클릭 시
|
</div>
|
||||||
const isNewGroup = selectedGroup?.id !== group.id;
|
{/* 트리 뷰 */}
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
if (isNewGroup) {
|
<ScreenGroupTreeView
|
||||||
// 새 그룹 진입: 포커싱 없이 시작 (첫 진입 시 망가지는 문제 방지)
|
screens={filteredScreens}
|
||||||
setSelectedGroup(group);
|
selectedScreen={selectedScreen}
|
||||||
setFocusedScreenIdInGroup(null);
|
onScreenSelect={handleScreenSelect}
|
||||||
} else {
|
onScreenDesign={handleDesignScreen}
|
||||||
// 같은 그룹 내에서 다른 화면 클릭: 포커싱 유지
|
searchTerm={searchTerm}
|
||||||
setFocusedScreenIdInGroup(screenId);
|
onGroupSelect={(group) => {
|
||||||
}
|
setSelectedGroup(group);
|
||||||
setSelectedScreen(null);
|
setSelectedScreen(null);
|
||||||
}}
|
setFocusedScreenIdInGroup(null);
|
||||||
/>
|
}}
|
||||||
</div>
|
onScreenSelectInGroup={(group, screenId) => {
|
||||||
|
const isNewGroup = selectedGroup?.id !== group.id;
|
||||||
|
if (isNewGroup) {
|
||||||
|
setSelectedGroup(group);
|
||||||
|
setFocusedScreenIdInGroup(null);
|
||||||
|
} else {
|
||||||
|
setFocusedScreenIdInGroup(screenId);
|
||||||
|
}
|
||||||
|
setSelectedScreen(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 오른쪽: 관계 시각화 (React Flow) */}
|
{/* 오른쪽: 관계 시각화 (React Flow) */}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue