feat(pop-string-list): 오버플로우 시스템 개편 - 더보기 점진 확장 + 페이지네이션 모드
- "전체보기" 토글을 "더보기" 점진 확장으로 변경 (loadMoreCount씩 추가, maxExpandRows 상한) - 페이지네이션 모드 추가: bottom(하단 페이지 표시) / side(좌우 화살표) 스타일 선택 - StepOverflow 설정 UI에 오버플로우 방식 Select + 모드별 분기 설정 추가 - PopRenderer viewer 모드에서 gridTemplateRows minmax(auto) 적용으로 동적 높이 확장 지원 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
fc0e913b8a
commit
f6461ae563
|
|
@ -122,18 +122,27 @@ export default function PopRenderer({
|
||||||
return Math.max(10, maxRowEnd + 3);
|
return Math.max(10, maxRowEnd + 3);
|
||||||
}, [components, overrides, mode, hiddenIds]);
|
}, [components, overrides, mode, hiddenIds]);
|
||||||
|
|
||||||
// CSS Grid 스타일 (행 높이 강제 고정: 셀 크기 = 컴포넌트 크기의 기준)
|
// CSS Grid 스타일
|
||||||
|
// 디자인 모드: 행 높이 고정 (정밀한 레이아웃 편집)
|
||||||
|
// 뷰어 모드: minmax(rowHeight, auto) (컴포넌트가 컨텐츠에 맞게 확장 가능)
|
||||||
|
const rowTemplate = isDesignMode
|
||||||
|
? `repeat(${dynamicRowCount}, ${breakpoint.rowHeight}px)`
|
||||||
|
: `repeat(${dynamicRowCount}, minmax(${breakpoint.rowHeight}px, auto))`;
|
||||||
|
const autoRowHeight = isDesignMode
|
||||||
|
? `${breakpoint.rowHeight}px`
|
||||||
|
: `minmax(${breakpoint.rowHeight}px, auto)`;
|
||||||
|
|
||||||
const gridStyle = useMemo((): React.CSSProperties => ({
|
const gridStyle = useMemo((): React.CSSProperties => ({
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: `repeat(${breakpoint.columns}, 1fr)`,
|
gridTemplateColumns: `repeat(${breakpoint.columns}, 1fr)`,
|
||||||
gridTemplateRows: `repeat(${dynamicRowCount}, ${breakpoint.rowHeight}px)`,
|
gridTemplateRows: rowTemplate,
|
||||||
gridAutoRows: `${breakpoint.rowHeight}px`,
|
gridAutoRows: autoRowHeight,
|
||||||
gap: `${finalGap}px`,
|
gap: `${finalGap}px`,
|
||||||
padding: `${finalPadding}px`,
|
padding: `${finalPadding}px`,
|
||||||
minHeight: "100%",
|
minHeight: "100%",
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}), [breakpoint, finalGap, finalPadding, dynamicRowCount]);
|
}), [breakpoint, finalGap, finalPadding, dynamicRowCount, rowTemplate, autoRowHeight]);
|
||||||
|
|
||||||
// 그리드 가이드 셀 생성 (동적 행 수)
|
// 그리드 가이드 셀 생성 (동적 행 수)
|
||||||
const gridCells = useMemo(() => {
|
const gridCells = useMemo(() => {
|
||||||
|
|
@ -265,11 +274,11 @@ export default function PopRenderer({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 뷰어 모드: 드래그 없는 일반 렌더링
|
// 뷰어 모드: 드래그 없는 일반 렌더링 (overflow visible로 컨텐츠 확장 허용)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={comp.id}
|
key={comp.id}
|
||||||
className="relative rounded-lg border-2 border-gray-200 bg-white transition-all overflow-hidden z-10"
|
className="relative rounded-lg border-2 border-gray-200 bg-white transition-all z-10"
|
||||||
style={positionStyle}
|
style={positionStyle}
|
||||||
>
|
>
|
||||||
<ComponentContent
|
<ComponentContent
|
||||||
|
|
@ -580,7 +589,7 @@ function renderActualComponent(component: PopComponentDefinitionV5, screenId?: s
|
||||||
|
|
||||||
if (ActualComp) {
|
if (ActualComp) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full overflow-hidden">
|
<div className="w-full min-h-full">
|
||||||
<ActualComp config={component.config} label={component.label} screenId={screenId} />
|
<ActualComp config={component.config} label={component.label} screenId={screenId} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
*
|
*
|
||||||
* 리스트 모드: 엑셀형 행/열 (CSS Grid)
|
* 리스트 모드: 엑셀형 행/열 (CSS Grid)
|
||||||
* 카드 모드: 셀 병합 가능한 카드 (CSS Grid + colSpan/rowSpan)
|
* 카드 모드: 셀 병합 가능한 카드 (CSS Grid + colSpan/rowSpan)
|
||||||
* 오버플로우: visibleRows 제한 + "전체보기" 확장
|
* 오버플로우: visibleRows 제한 + "더보기" 점진 확장
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { ChevronDown, ChevronUp, Loader2, AlertCircle, ChevronsUpDown } from "lucide-react";
|
import { ChevronDown, ChevronUp, ChevronLeft, ChevronRight, Loader2, AlertCircle, ChevronsUpDown } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
|
|
@ -70,7 +70,10 @@ export function PopStringListComponent({
|
||||||
const [rows, setRows] = useState<RowData[]>([]);
|
const [rows, setRows] = useState<RowData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [expanded, setExpanded] = useState(false);
|
// 더보기 모드: 현재 표시 중인 행 수
|
||||||
|
const [displayCount, setDisplayCount] = useState<number>(0);
|
||||||
|
// 페이지네이션 모드: 현재 페이지 (1부터 시작)
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
// 카드 버튼 행 단위 로딩 인덱스 (-1 = 로딩 없음)
|
// 카드 버튼 행 단위 로딩 인덱스 (-1 = 로딩 없음)
|
||||||
const [loadingRowIdx, setLoadingRowIdx] = useState<number>(-1);
|
const [loadingRowIdx, setLoadingRowIdx] = useState<number>(-1);
|
||||||
|
|
@ -112,21 +115,54 @@ export function PopStringListComponent({
|
||||||
[rows, publish, screenId]
|
[rows, publish, screenId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 오버플로우 계산 (JSON 복원 시 string 유입 방어)
|
// 오버플로우 설정 (JSON 복원 시 string 유입 방어)
|
||||||
|
const overflowMode = overflow?.mode || "loadMore";
|
||||||
const visibleRows = Number(overflow?.visibleRows) || 5;
|
const visibleRows = Number(overflow?.visibleRows) || 5;
|
||||||
const maxExpandRows = Number(overflow?.maxExpandRows) || 20;
|
const loadMoreCount = Number(overflow?.loadMoreCount) || 5;
|
||||||
|
const maxExpandRows = Number(overflow?.maxExpandRows) || 50;
|
||||||
const showExpandButton = overflow?.showExpandButton ?? true;
|
const showExpandButton = overflow?.showExpandButton ?? true;
|
||||||
|
const pageSize = Number(overflow?.pageSize) || visibleRows;
|
||||||
|
const paginationStyle = overflow?.paginationStyle || "bottom";
|
||||||
|
|
||||||
// 표시할 데이터 슬라이스
|
// --- 더보기 모드 ---
|
||||||
const visibleData = expanded
|
useEffect(() => {
|
||||||
? rows.slice(0, maxExpandRows)
|
setDisplayCount(visibleRows);
|
||||||
: rows.slice(0, visibleRows);
|
}, [visibleRows]);
|
||||||
const hasMore = rows.length > visibleRows;
|
|
||||||
|
|
||||||
// 확장/축소 토글
|
const effectiveLimit = Math.min(displayCount || visibleRows, maxExpandRows, rows.length);
|
||||||
const toggleExpanded = useCallback(() => {
|
const hasMore = showExpandButton && rows.length > effectiveLimit && effectiveLimit < maxExpandRows;
|
||||||
setExpanded((prev) => !prev);
|
const isExpanded = effectiveLimit > visibleRows;
|
||||||
}, []);
|
|
||||||
|
const handleLoadMore = useCallback(() => {
|
||||||
|
setDisplayCount((prev) => {
|
||||||
|
const current = prev || visibleRows;
|
||||||
|
return Math.min(current + loadMoreCount, maxExpandRows, rows.length);
|
||||||
|
});
|
||||||
|
}, [visibleRows, loadMoreCount, maxExpandRows, rows.length]);
|
||||||
|
|
||||||
|
const handleCollapse = useCallback(() => {
|
||||||
|
setDisplayCount(visibleRows);
|
||||||
|
}, [visibleRows]);
|
||||||
|
|
||||||
|
// --- 페이지네이션 모드 ---
|
||||||
|
const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [pageSize, rows.length]);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback((page: number) => {
|
||||||
|
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
|
||||||
|
}, [totalPages]);
|
||||||
|
|
||||||
|
// --- 모드별 visibleData 결정 ---
|
||||||
|
const visibleData = useMemo(() => {
|
||||||
|
if (overflowMode === "pagination") {
|
||||||
|
const start = (currentPage - 1) * pageSize;
|
||||||
|
return rows.slice(start, start + pageSize);
|
||||||
|
}
|
||||||
|
return rows.slice(0, effectiveLimit);
|
||||||
|
}, [overflowMode, rows, currentPage, pageSize, effectiveLimit]);
|
||||||
|
|
||||||
// dataSource 원시값 추출 (객체 참조 대신 안정적인 의존성 사용)
|
// dataSource 원시값 추출 (객체 참조 대신 안정적인 의존성 사용)
|
||||||
const dsTableName = dataSource?.tableName;
|
const dsTableName = dataSource?.tableName;
|
||||||
|
|
@ -236,8 +272,10 @@ export function PopStringListComponent({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPaginationSide = overflowMode === "pagination" && paginationStyle === "side" && totalPages > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex h-full w-full flex-col overflow-hidden ${className || ""}`}>
|
<div className={`flex w-full flex-col ${className || ""}`}>
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
{header?.enabled && header.label && (
|
{header?.enabled && header.label && (
|
||||||
<div className="shrink-0 border-b px-3 py-2">
|
<div className="shrink-0 border-b px-3 py-2">
|
||||||
|
|
@ -246,12 +284,9 @@ export function PopStringListComponent({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 컨텐츠 */}
|
{/* 컨텐츠 */}
|
||||||
<div className="flex-1 overflow-auto">
|
<div className={`flex-1 ${isPaginationSide ? "relative" : ""}`}>
|
||||||
{displayMode === "list" ? (
|
{displayMode === "list" ? (
|
||||||
<ListModeView
|
<ListModeView columns={listColumns} data={visibleData} />
|
||||||
columns={listColumns}
|
|
||||||
data={visibleData}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<CardModeView
|
<CardModeView
|
||||||
cardGrid={cardGrid}
|
cardGrid={cardGrid}
|
||||||
|
|
@ -260,29 +295,96 @@ export function PopStringListComponent({
|
||||||
loadingRowId={loadingRowIdx}
|
loadingRowId={loadingRowIdx}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isPaginationSide && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
className="absolute left-1 top-1/2 z-10 h-7 w-7 -translate-y-1/2 rounded-full bg-background/60 opacity-70 shadow-sm backdrop-blur-sm transition-opacity hover:opacity-100 disabled:opacity-20"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage >= totalPages}
|
||||||
|
className="absolute right-1 top-1/2 z-10 h-7 w-7 -translate-y-1/2 rounded-full bg-background/60 opacity-70 shadow-sm backdrop-blur-sm transition-opacity hover:opacity-100 disabled:opacity-20"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 전체보기 버튼 */}
|
{/* side 모드 페이지 인디케이터 (컨텐츠 아래 별도 영역) */}
|
||||||
{showExpandButton && hasMore && (
|
{isPaginationSide && (
|
||||||
<div className="shrink-0 border-t px-3 py-1.5">
|
<div className="shrink-0 flex justify-center py-1">
|
||||||
<Button
|
<span className="rounded-full bg-muted/50 px-2.5 py-0.5 text-[10px] tabular-nums text-muted-foreground">
|
||||||
variant="ghost"
|
{currentPage} / {totalPages}
|
||||||
size="sm"
|
</span>
|
||||||
onClick={toggleExpanded}
|
</div>
|
||||||
className="h-7 w-full text-xs text-muted-foreground"
|
)}
|
||||||
>
|
|
||||||
{expanded ? (
|
{/* 더보기 모드 컨트롤 */}
|
||||||
<>
|
{overflowMode === "loadMore" && showExpandButton && (hasMore || isExpanded) && (
|
||||||
<ChevronUp className="mr-1 h-3 w-3" />
|
<div className="shrink-0 border-t px-3 py-1.5 flex gap-2">
|
||||||
접기
|
{hasMore && (
|
||||||
</>
|
<Button
|
||||||
) : (
|
variant="ghost"
|
||||||
<>
|
size="sm"
|
||||||
<ChevronDown className="mr-1 h-3 w-3" />
|
onClick={handleLoadMore}
|
||||||
전체보기 ({rows.length}건)
|
className="h-7 flex-1 text-xs text-muted-foreground"
|
||||||
</>
|
>
|
||||||
)}
|
<ChevronDown className="mr-1 h-3 w-3" />
|
||||||
</Button>
|
더보기 ({rows.length - effectiveLimit}건 남음)
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isExpanded && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCollapse}
|
||||||
|
className="h-7 text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
<ChevronUp className="mr-1 h-3 w-3" />
|
||||||
|
접기
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 페이지네이션 bottom 모드 컨트롤 */}
|
||||||
|
{overflowMode === "pagination" && paginationStyle === "bottom" && totalPages > 1 && (
|
||||||
|
<div className="shrink-0 border-t px-3 py-1.5 flex items-center justify-between">
|
||||||
|
<span className="text-[10px] text-muted-foreground">
|
||||||
|
{rows.length}건 중 {(currentPage - 1) * pageSize + 1}-{Math.min(currentPage * pageSize, rows.length)}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage <= 1}
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
<span className="text-xs tabular-nums px-1">
|
||||||
|
{currentPage} / {totalPages}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage >= totalPages}
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ interface ConfigPanelProps {
|
||||||
const DEFAULT_CONFIG: PopStringListConfig = {
|
const DEFAULT_CONFIG: PopStringListConfig = {
|
||||||
displayMode: "list",
|
displayMode: "list",
|
||||||
header: { enabled: true, label: "" },
|
header: { enabled: true, label: "" },
|
||||||
overflow: { visibleRows: 5, showExpandButton: true, maxExpandRows: 20 },
|
overflow: { visibleRows: 5, mode: "loadMore", showExpandButton: true, loadMoreCount: 5, maxExpandRows: 50, pageSize: 5, paginationStyle: "bottom" },
|
||||||
dataSource: { tableName: "" },
|
dataSource: { tableName: "" },
|
||||||
listColumns: [],
|
listColumns: [],
|
||||||
cardGrid: undefined,
|
cardGrid: undefined,
|
||||||
|
|
@ -414,6 +414,8 @@ function StepOverflow({
|
||||||
overflow: PopStringListConfig["overflow"];
|
overflow: PopStringListConfig["overflow"];
|
||||||
onChange: (overflow: PopStringListConfig["overflow"]) => void;
|
onChange: (overflow: PopStringListConfig["overflow"]) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const mode = overflow.mode || "loadMore";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -429,32 +431,130 @@ function StepOverflow({
|
||||||
className="mt-1 h-8 text-xs"
|
className="mt-1 h-8 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label className="text-xs">전체보기 버튼</Label>
|
<div>
|
||||||
<Switch
|
<Label className="text-xs">오버플로우 방식</Label>
|
||||||
checked={overflow.showExpandButton}
|
<Select
|
||||||
onCheckedChange={(showExpandButton) =>
|
value={mode}
|
||||||
onChange({ ...overflow, showExpandButton })
|
onValueChange={(v) =>
|
||||||
|
onChange({ ...overflow, mode: v as "loadMore" | "pagination" })
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="loadMore" className="text-xs">
|
||||||
|
더보기 (점진 확장)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="pagination" className="text-xs">
|
||||||
|
페이지네이션
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{overflow.showExpandButton && (
|
|
||||||
<div>
|
{mode === "loadMore" && (
|
||||||
<Label className="text-xs">최대 확장 행 수</Label>
|
<>
|
||||||
<Input
|
<div className="flex items-center justify-between">
|
||||||
type="number"
|
<Label className="text-xs">더보기 버튼</Label>
|
||||||
min={overflow.visibleRows}
|
<Switch
|
||||||
max={200}
|
checked={overflow.showExpandButton}
|
||||||
value={overflow.maxExpandRows}
|
onCheckedChange={(showExpandButton) =>
|
||||||
onChange={(e) =>
|
onChange({ ...overflow, showExpandButton })
|
||||||
onChange({
|
}
|
||||||
...overflow,
|
/>
|
||||||
maxExpandRows: Number(e.target.value) || 20,
|
</div>
|
||||||
})
|
{overflow.showExpandButton && (
|
||||||
}
|
<>
|
||||||
className="mt-1 h-8 text-xs"
|
<div>
|
||||||
/>
|
<Label className="text-xs">더보기 추가 행 수</Label>
|
||||||
</div>
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={50}
|
||||||
|
value={overflow.loadMoreCount ?? 5}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange({
|
||||||
|
...overflow,
|
||||||
|
loadMoreCount: Number(e.target.value) || 5,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1 h-8 text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-1 text-[10px]">
|
||||||
|
클릭할 때마다 추가로 표시할 행 수
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs">최대 확장 행 수</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={overflow.visibleRows}
|
||||||
|
max={200}
|
||||||
|
value={overflow.maxExpandRows}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange({
|
||||||
|
...overflow,
|
||||||
|
maxExpandRows: Number(e.target.value) || 50,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1 h-8 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === "pagination" && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs">페이지당 행 수</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
value={overflow.pageSize ?? overflow.visibleRows}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange({
|
||||||
|
...overflow,
|
||||||
|
pageSize: Number(e.target.value) || 5,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1 h-8 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs">네비게이션 스타일</Label>
|
||||||
|
<Select
|
||||||
|
value={overflow.paginationStyle || "bottom"}
|
||||||
|
onValueChange={(v) =>
|
||||||
|
onChange({
|
||||||
|
...overflow,
|
||||||
|
paginationStyle: v as "bottom" | "side",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="bottom" className="text-xs">
|
||||||
|
하단 페이지 표시
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="side" className="text-xs">
|
||||||
|
좌우 화살표
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-muted-foreground mt-1 text-[10px]">
|
||||||
|
{(overflow.paginationStyle || "bottom") === "bottom"
|
||||||
|
? "컴포넌트 하단에 이전/다음 버튼과 페이지 번호 표시"
|
||||||
|
: "컴포넌트 좌우에 화살표 버튼 표시"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import type { PopStringListConfig } from "./types";
|
||||||
const defaultConfig: PopStringListConfig = {
|
const defaultConfig: PopStringListConfig = {
|
||||||
displayMode: "list",
|
displayMode: "list",
|
||||||
header: { enabled: true, label: "" },
|
header: { enabled: true, label: "" },
|
||||||
overflow: { visibleRows: 5, showExpandButton: true, maxExpandRows: 20 },
|
overflow: { visibleRows: 5, mode: "loadMore", showExpandButton: true, loadMoreCount: 5, maxExpandRows: 50, pageSize: 5, paginationStyle: "bottom" },
|
||||||
dataSource: { tableName: "" },
|
dataSource: { tableName: "" },
|
||||||
listColumns: [],
|
listColumns: [],
|
||||||
cardGrid: undefined,
|
cardGrid: undefined,
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,23 @@ export interface ListColumnConfig {
|
||||||
alternateColumns?: string[]; // 런타임에서 전환 가능한 대체 컬럼 목록
|
alternateColumns?: string[]; // 런타임에서 전환 가능한 대체 컬럼 목록
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 오버플로우 방식 */
|
||||||
|
export type OverflowMode = "loadMore" | "pagination";
|
||||||
|
|
||||||
|
/** 페이지네이션 네비게이션 스타일 */
|
||||||
|
export type PaginationStyle = "bottom" | "side";
|
||||||
|
|
||||||
/** 오버플로우 설정 */
|
/** 오버플로우 설정 */
|
||||||
export interface OverflowConfig {
|
export interface OverflowConfig {
|
||||||
visibleRows: number; // 기본 표시 행 수
|
visibleRows: number; // 기본 표시 행 수
|
||||||
showExpandButton: boolean; // "전체보기" 버튼 표시
|
mode: OverflowMode; // 오버플로우 방식
|
||||||
maxExpandRows: number; // 확장 시 최대 행 수
|
// 더보기 모드 전용
|
||||||
|
showExpandButton: boolean; // "더보기" 버튼 표시
|
||||||
|
loadMoreCount: number; // 더보기 클릭 시 추가 로딩 행 수
|
||||||
|
maxExpandRows: number; // 최대 표시 행 수 (무한 확장 방지)
|
||||||
|
// 페이지네이션 모드 전용
|
||||||
|
pageSize: number; // 페이지당 표시 행 수
|
||||||
|
paginationStyle: PaginationStyle; // bottom: 하단 페이지 표시, side: 좌우 화살표
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 헤더 설정 */
|
/** 헤더 설정 */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue