diff options
Diffstat (limited to 'components/data-table/expandable-data-table.tsx')
| -rw-r--r-- | components/data-table/expandable-data-table.tsx | 707 |
1 files changed, 486 insertions, 221 deletions
diff --git a/components/data-table/expandable-data-table.tsx b/components/data-table/expandable-data-table.tsx index 9005e2fb..112e9448 100644 --- a/components/data-table/expandable-data-table.tsx +++ b/components/data-table/expandable-data-table.tsx @@ -5,7 +5,7 @@ import { flexRender, type Table as TanstackTable } from "@tanstack/react-table" import { ChevronRight, ChevronUp, ChevronDown } from "lucide-react" import { cn } from "@/lib/utils" -import { getCommonPinningStyles } from "@/lib/data-table" +import { getCommonPinningStylesWithBorder, debugPinningInfo } from "@/lib/data-table" import { Table, TableBody, @@ -26,18 +26,13 @@ interface ExpandableDataTableProps<TData> extends React.HTMLAttributes<HTMLDivEl expandedRows?: Set<string> setExpandedRows?: React.Dispatch<React.SetStateAction<Set<string>>> renderExpandedContent?: (row: TData) => React.ReactNode - expandable?: boolean // 확장 기능 활성화 여부 + expandable?: boolean maxHeight?: string | number - expandedRowClassName?: string // 확장된 행의 커스텀 클래스 + expandedRowClassName?: string + debug?: boolean // 디버깅 옵션 추가 + simpleExpansion?: boolean // 🎯 간단한 확장 방식 선택 옵션 추가 } -/** - * 확장 가능한 데이터 테이블 - 행 확장 시 바로 아래에 컨텐츠 표시 - * 개선사항: - * - 가로스크롤과 확장된 내용 독립성 보장 - * - 동적 높이 계산으로 세로스크롤 문제 해결 - * - 향상된 접근성 및 키보드 네비게이션 - */ export function ExpandableDataTable<TData>({ table, floatingBar = null, @@ -49,6 +44,8 @@ export function ExpandableDataTable<TData>({ expandable = false, maxHeight, expandedRowClassName, + debug = false, + simpleExpansion = false, // 🎯 기본값 false (전체 확장 방식) children, className, ...props @@ -56,11 +53,228 @@ export function ExpandableDataTable<TData>({ useAutoSizeColumns(table, autoSizeColumns) - // 스크롤 컨테이너 참조 - const scrollContainerRef = React.useRef<HTMLDivElement>(null) + const containerRef = React.useRef<HTMLDivElement>(null) + const scrollRef = React.useRef<HTMLDivElement>(null) // 🎯 스크롤 컨테이너 ref 추가 const [expandedHeights, setExpandedHeights] = React.useState<Map<string, number>>(new Map()) + const [isScrolled, setIsScrolled] = React.useState(false) + const [isScrolledToEnd, setIsScrolledToEnd] = React.useState(true) // 🎯 초기값을 true로 설정 (아직 계산 안됨) + const [isInitialized, setIsInitialized] = React.useState(false) // 🎯 초기화 상태 추가 + const [containerWidth, setContainerWidth] = React.useState<number>(0) - // 행 확장/축소 핸들러 (개선된 버전) + // 🎯 컨테이너 너비 감지 (패딩 제외된 실제 사용 가능한 너비) + React.useEffect(() => { + if (!containerRef.current) return + + const updateContainerWidth = () => { + if (containerRef.current) { + // clientWidth는 패딩을 제외한 실제 사용 가능한 너비 + setContainerWidth(containerRef.current.clientWidth) + } + } + + const resizeObserver = new ResizeObserver(updateContainerWidth) + resizeObserver.observe(containerRef.current) + + // 초기 너비 설정 + updateContainerWidth() + + return () => resizeObserver.disconnect() + }, []) + + // 🎯 초기 스크롤 상태 체크 (더 확실한 초기화) + React.useEffect(() => { + if (!scrollRef.current) return + + const checkScrollState = () => { + if (scrollRef.current) { + const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current + const newIsScrolled = scrollLeft > 0 + const newIsScrolledToEnd = Math.abs(scrollWidth - clientWidth - scrollLeft) < 1 + + setIsScrolled(newIsScrolled) + setIsScrolledToEnd(newIsScrolledToEnd) + setIsInitialized(true) + + if (debug) { + console.log('Initial scroll check:', { + scrollLeft, + scrollWidth, + clientWidth, + newIsScrolled, + newIsScrolledToEnd, + canScroll: scrollWidth > clientWidth + }) + } + } + } + + // 즉시 체크 + checkScrollState() + + // 짧은 지연 후 재체크 (테이블 렌더링 완료 대기) + const timeouts = [50, 100, 200].map(delay => + setTimeout(checkScrollState, delay) + ) + + // ResizeObserver로 테이블 크기 변화 감지 + const resizeObserver = new ResizeObserver(() => { + setTimeout(checkScrollState, 10) + }) + resizeObserver.observe(scrollRef.current) + + return () => { + timeouts.forEach(clearTimeout) + resizeObserver.disconnect() + } + }, [table, debug]) + + // 🎯 데이터 변경 시 스크롤 상태 재설정 + React.useEffect(() => { + setIsInitialized(false) // 🎯 데이터 변경 시 초기화 상태 리셋 + setIsScrolledToEnd(true) // 🎯 안전한 기본값으로 리셋 + }, [table.getRowModel().rows.length]) + + const handleScroll = (e: React.UIEvent<HTMLDivElement>) => { + const scrollLeft = e.currentTarget.scrollLeft + const scrollWidth = e.currentTarget.scrollWidth + const clientWidth = e.currentTarget.clientWidth + + // 🎯 왼쪽으로부터 스크롤 상태 (왼쪽 핀용) + setIsScrolled(scrollLeft > 0) + + // 🎯 오른쪽 끝까지 스크롤 상태 (오른쪽 핀용) + const isAtEnd = Math.abs(scrollWidth - clientWidth - scrollLeft) < 1 // 1px 오차 허용 + setIsScrolledToEnd(isAtEnd) + setIsInitialized(true) // 🎯 스크롤 이벤트 발생 시 초기화 완료 + + if (debug) { + console.log('Scroll state:', { + scrollLeft, + scrollWidth, + clientWidth, + isScrolled: scrollLeft > 0, + isScrolledToEnd: isAtEnd, + remainingScroll: scrollWidth - clientWidth - scrollLeft + }) + } + } + + // 🔧 개선된 핀 스타일 함수 (좌/우 핀 구분 처리) + const getPinnedStyle = React.useCallback((column: any, isHeader: boolean = false) => { + if (debug) { + debugPinningInfo(column) + } + + try { + const baseStyle = getCommonPinningStylesWithBorder({ + column, + withBorder: true + }) + + const pinnedSide = column.getIsPinned() + + if (!pinnedSide) { + // width를 제외한 나머지 스타일만 반환 + const { width, ...restStyle } = baseStyle + // 헤더인 경우 핀되지 않았어도 배경 필요 (sticky 때문에) + return { + ...restStyle, + ...(isHeader && { + background: "hsl(var(--background))", + transition: "none", + }), + } + } + + // 확장 버튼이 있을 때 left pin된 컬럼들을 오른쪽으로 이동 + let leftPosition = baseStyle.left + if (expandable && pinnedSide === "left") { + const expandButtonWidth = 40 // w-10 = 40px + if (typeof baseStyle.left === 'string') { + const currentLeft = parseFloat(baseStyle.left.replace('px', '')) + leftPosition = `${currentLeft + expandButtonWidth}px` + } else if (typeof baseStyle.left === 'number') { + leftPosition = `${baseStyle.left + expandButtonWidth}px` + } else { + leftPosition = `${expandButtonWidth}px` + } + } + + // 🎯 핀 위치에 따른 배경 결정 + let shouldShowBackground = false + if (isHeader) { + // 헤더는 항상 배경 표시 + shouldShowBackground = true + } else { + // 바디 셀의 경우 핀 위치에 따라 다른 조건 적용 + if (pinnedSide === "left") { + // 왼쪽 핀: 오른쪽으로 스크롤했을 때 배경 표시 + shouldShowBackground = isScrolled + } else if (pinnedSide === "right") { + // 오른쪽 핀: 초기화 전이거나 오른쪽 끝까지 스크롤하지 않았을 때 배경 표시 + shouldShowBackground = !isInitialized || !isScrolledToEnd + } + } + + // width를 제외한 스타일 적용 + const { width, ...restBaseStyle } = baseStyle + + const finalStyle = { + ...restBaseStyle, + left: leftPosition, + background: shouldShowBackground + ? "hsl(var(--background))" + : "transparent", + transition: isHeader ? "none" : "background-color 0.15s ease-out", + } + + if (debug) { + console.log("Final pinned style:", { + columnId: column.id, + pinnedSide, + isHeader, + isScrolled, + isScrolledToEnd, + isInitialized, + shouldShowBackground, + finalStyle + }) + } + + return finalStyle + } catch (error) { + console.error("Error in getPinnedStyle:", error) + // fallback 스타일 + return { + position: 'relative' as const, + ...(isHeader && { + background: "hsl(var(--background))", + }), + } + } + }, [expandable, isScrolled, isScrolledToEnd, isInitialized, debug]) + + // 확장 버튼용 스타일 (안정성 개선) + const getExpandButtonStyle = React.useCallback(() => { + return { + position: 'sticky' as const, + left: 0, + zIndex: 1, + background: "hsl(var(--background))", + minWidth: '40px', + maxWidth: '40px', + width: '40px', + } + }, []) + + // 🎯 테이블 총 너비 계산 + const getTableWidth = React.useCallback(() => { + const expandButtonWidth = expandable ? 40 : 0 + const totalSize = table.getCenterTotalSize() + table.getLeftTotalSize() + table.getRightTotalSize() + return Math.max(totalSize + expandButtonWidth, 800) // 최소 800px 보장 + }, [table, expandable]) + + // 행 확장/축소 핸들러 const toggleRowExpansion = React.useCallback((rowId: string, event?: React.MouseEvent) => { if (!setExpandedRows) return @@ -71,7 +285,6 @@ export function ExpandableDataTable<TData>({ const newExpanded = new Set(expandedRows) if (newExpanded.has(rowId)) { newExpanded.delete(rowId) - // 높이 정보도 제거 setExpandedHeights(prev => { const newHeights = new Map(prev) newHeights.delete(rowId) @@ -91,7 +304,66 @@ export function ExpandableDataTable<TData>({ } }, [toggleRowExpansion]) - // 확장된 내용의 높이 측정 및 업데이트 + // 🎯 확장된 내용 스타일 계산 함수 (컨테이너 너비 기준) + const getExpandedContentStyle = React.useCallback(() => { + const expandButtonWidth = expandable ? 40 : 0 + const availableWidth = containerWidth || 800 // 🎯 컨테이너 너비 사용 (fallback 800px) + const contentWidth = availableWidth - expandButtonWidth - 32 // 버튼 + 여백(16px * 2) 제외 + + // 🎯 디버그 정보 + if (debug) { + console.log('Expanded content sizing:', { + containerWidth, + availableWidth, + expandButtonWidth, + contentWidth, + finalWidth: Math.max(contentWidth, 300) + }) + } + + return { + width: `${Math.max(contentWidth, 300)}px`, // 🎯 최소 300px 보장 + marginLeft: `${expandButtonWidth + 8}px`, // 🎯 확장 버튼 + 여백 + marginRight: '16px', + padding: '12px 16px', + backgroundColor: 'hsl(var(--background))', + borderRadius: '0 0 6px 6px', + boxShadow: '0 2px 4px -1px rgba(0, 0, 0, 0.1)', + border: '1px solid hsl(var(--border))', + borderTop: 'none', + marginBottom: '4px', + maxWidth: `${availableWidth - expandButtonWidth - 16}px`, // 🎯 컨테이너 너비 초과 방지 + overflow: 'auto', + } + }, [expandable, containerWidth, debug]) + + // 🎯 간단한 확장 스타일 (컨테이너 너비 기준) + const getSimpleExpandedStyle = React.useCallback(() => { + const expandButtonWidth = expandable ? 40 : 0 + const availableWidth = containerWidth || 800 + const contentWidth = availableWidth - expandButtonWidth - 48 // 버튼 + 여백 제외 + + return { + marginLeft: `${expandButtonWidth + 8}px`, + marginRight: '16px', + width: `${Math.max(contentWidth, 300)}px`, + maxWidth: `${availableWidth - expandButtonWidth - 24}px`, + padding: '12px 16px', + backgroundColor: 'hsl(var(--muted) / 0.5)', + borderRadius: '6px', + border: '1px solid hsl(var(--border))', + marginTop: '8px', + marginBottom: '8px', + overflow: 'auto', + } + }, [expandable, containerWidth]) + + // 🎯 사용할 확장 스타일 선택 (props로 제어) + const useSimpleExpansion = simpleExpansion + const getExpandedPlaceholderHeight = React.useCallback((contentHeight: number) => { + const padding = 24 + 4 // padding (12px * 2) + marginBottom (4px) + return Math.max(contentHeight + padding, 220) + }, []) const updateExpandedHeight = React.useCallback((rowId: string, height: number) => { setExpandedHeights(prev => { if (prev.get(rowId) !== height) { @@ -103,22 +375,26 @@ export function ExpandableDataTable<TData>({ }) }, []) - // 컴팩트 모드를 위한 클래스 정의 (개선된 버전) + // 컴팩트 모드 스타일 const compactStyles = compact ? { row: "h-7", cell: "py-1 px-2 text-sm", + header: "py-1 px-2 text-sm", + headerRow: "h-8", expandedCell: "py-2 px-4", groupRow: "py-1 bg-muted/20 text-sm", emptyRow: "h-16", } : { row: "", cell: "", - expandedCell: "py-0 px-0", // 패딩 제거하여 확장 컨텐츠가 전체 영역 사용 + header: "", + headerRow: "", + expandedCell: "py-0 px-0", groupRow: "bg-muted/20", emptyRow: "h-24", } - // 확장 버튼 렌더링 함수 (접근성 개선) + // 확장 버튼 렌더링 const renderExpandButton = (rowId: string) => { if (!expandable || !setExpandedRows) return null @@ -143,7 +419,7 @@ export function ExpandableDataTable<TData>({ ) } - // 확장된 내용 래퍼 컴포넌트 (높이 측정 기능 포함) + // 확장된 내용 래퍼 컴포넌트 const ExpandedContentWrapper = React.memo<{ rowId: string children: React.ReactNode @@ -176,244 +452,233 @@ export function ExpandableDataTable<TData>({ <div className={cn("w-full space-y-2.5", className)} {...props}> {children} - {/* 메인 테이블 컨테이너 - 가로스크롤 문제 해결 */} <div - ref={scrollContainerRef} + ref={containerRef} // 🎯 컨테이너 wrapper ref (패딩 제외 너비 계산용) className="relative rounded-md border" style={{ - // maxHeight: maxHeight || '35rem', - minHeight: '200px' // 최소 높이 보장 + minHeight: '200px' }} > - {/* 가로스크롤 영역 */} - <div className="overflow-x-auto overflow-y-hidden h-full"> - {/* 세로스크롤 영역 (확장된 내용 포함) */} - <div className="overflow-y-auto h-full"> - <Table className="[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10 table-fixed"> - {/* 테이블 헤더 */} - <TableHeader className="bg-background"> - {table.getHeaderGroups().map((headerGroup) => ( - <TableRow key={headerGroup.id} className={compact ? "h-8" : ""}> - {/* 확장 버튼 컬럼 헤더 */} - {expandable && ( - <TableHead - className={cn("w-10 bg-background", compact ? "py-1 px-2" : "")} - style={{ position: 'sticky', left: 0, zIndex: 11 }} - /> - )} - - {headerGroup.headers.map((header) => { - if (header.column.getIsGrouped()) { - return null - } - - return ( - <TableHead - key={header.id} - colSpan={header.colSpan} - data-column-id={header.column.id} - className={cn( - compact ? "py-1 px-2 text-sm" : "", - "bg-background" - )} - style={{ - ...getCommonPinningStyles({ column: header.column }), - width: header.getSize(), - }} - > - <div style={{ position: "relative" }}> - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + <div + ref={scrollRef} // 🎯 스크롤 컨테이너 ref 연결 + className="overflow-auto" + style={{ maxHeight: maxHeight || '34rem' }} + onScroll={handleScroll} // 🎯 스크롤 이벤트 핸들러 + > + <Table + className="[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10" + style={{ + width: getTableWidth(), // 🎯 동적 너비 계산 + minWidth: '100%' + }} + > + <TableHeader className="bg-background"> + {table.getHeaderGroups().map((headerGroup) => ( + <TableRow key={headerGroup.id} className={compactStyles.headerRow}> + {expandable && ( + <TableHead + className={cn("w-10", compactStyles.header)} + style={getExpandButtonStyle()} + /> + )} + + {headerGroup.headers.map((header) => { + if (header.column.getIsGrouped()) { + return null + } - {header.column.getCanResize() && ( - <DataTableResizer header={header} /> + return ( + <TableHead + key={header.id} + colSpan={header.colSpan} + data-column-id={header.column.id} + className={cn( + compactStyles.header, + "bg-background" + )} + style={{ + ...getPinnedStyle(header.column, true), // 🎯 동적 스타일 + width: header.getSize() // 🎯 width 별도 설정 + }} + > + <div style={{ position: "relative" }}> + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() )} - </div> - </TableHead> - ) - })} - </TableRow> - ))} - </TableHeader> - - {/* 테이블 바디 */} - <TableBody> - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => { - const isExpanded = expandedRows.has(row.id) - const expandedHeight = expandedHeights.get(row.id) || 0 - - // 그룹핑 헤더 Row - if (row.getIsGrouped()) { - const groupingColumnId = row.groupingColumnId ?? "" - const groupingColumn = table.getColumn(groupingColumnId) - - let columnLabel = groupingColumnId - if (groupingColumn) { - const headerDef = groupingColumn.columnDef.meta?.excelHeader - if (typeof headerDef === "string") { - columnLabel = headerDef - } - } - return ( - <TableRow - key={row.id} - className={compactStyles.groupRow} - data-state={row.getIsExpanded() && "expanded"} - > - <TableCell - colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)} - className={compact ? "py-1 px-2" : ""} - > - {row.getCanExpand() && ( - <button - onClick={row.getToggleExpandedHandler()} - className="inline-flex items-center justify-center mr-2 w-5 h-5 hover:bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" - style={{ - marginLeft: `${row.depth * 1.5}rem`, - }} - aria-label={row.getIsExpanded() ? "그룹 축소" : "그룹 확장"} - > - {row.getIsExpanded() ? ( - <ChevronUp size={compact ? 14 : 16} /> - ) : ( - <ChevronRight size={compact ? 14 : 16} /> - )} - </button> - )} + {header.column.getCanResize() && ( + <DataTableResizer header={header} /> + )} + </div> + </TableHead> + ) + })} + </TableRow> + ))} + </TableHeader> - <span className="font-semibold"> - {columnLabel}: {row.getValue(groupingColumnId)} - </span> - <span className="ml-2 text-xs text-muted-foreground"> - ({row.subRows.length} rows) - </span> - </TableCell> - </TableRow> - ) + <TableBody> + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => { + const isExpanded = expandedRows.has(row.id) + const expandedHeight = expandedHeights.get(row.id) || 0 + + if (row.getIsGrouped()) { + const groupingColumnId = row.groupingColumnId ?? "" + const groupingColumn = table.getColumn(groupingColumnId) + + let columnLabel = groupingColumnId + if (groupingColumn) { + const headerDef = groupingColumn.columnDef.meta?.excelHeader + if (typeof headerDef === "string") { + columnLabel = headerDef + } } - // 일반 Row와 확장된 컨텐츠를 함께 렌더링 return ( - <React.Fragment key={row.id}> - {/* 메인 데이터 행 */} - <TableRow - className={cn( - compactStyles.row, - isExpanded && "bg-muted/30 border-b-0" - )} - data-state={row.getIsSelected() && "selected"} + <TableRow + key={row.id} + className={compactStyles.groupRow} + data-state={row.getIsExpanded() && "expanded"} + > + <TableCell + colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)} + className={compact ? "py-1 px-2" : ""} > - {/* 확장 버튼 셀 */} - {expandable && ( - <TableCell - className={cn("w-10", compactStyles.cell)} - style={{ - position: 'sticky', - left: 0, - zIndex: 1, - backgroundColor: isExpanded ? 'rgb(248 250 252)' : 'white' + {row.getCanExpand() && ( + <button + onClick={row.getToggleExpandedHandler()} + className="inline-flex items-center justify-center mr-2 w-5 h-5 hover:bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" + style={{ + marginLeft: `${row.depth * 1.5}rem`, }} + aria-label={row.getIsExpanded() ? "그룹 축소" : "그룹 확장"} > - {renderExpandButton(row.id)} - </TableCell> + {row.getIsExpanded() ? ( + <ChevronUp size={compact ? 14 : 16} /> + ) : ( + <ChevronRight size={compact ? 14 : 16} /> + )} + </button> )} - {/* 데이터 셀들 */} - {row.getVisibleCells().map((cell) => { - if (cell.column.getIsGrouped()) { - return null - } - - return ( - <TableCell - key={cell.id} - data-column-id={cell.column.id} - className={compactStyles.cell} - style={{ - ...getCommonPinningStyles({ column: cell.column }), - width: cell.column.getSize(), - }} - > - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - </TableCell> - ) - })} - </TableRow> + <span className="font-semibold"> + {columnLabel}: {row.getValue(groupingColumnId)} + </span> + <span className="ml-2 text-xs text-muted-foreground"> + ({row.subRows.length} rows) + </span> + </TableCell> + </TableRow> + ) + } + + return ( + <React.Fragment key={row.id}> + <TableRow + className={cn( + compactStyles.row, + isExpanded && "bg-muted/30 border-b-0" + )} + data-state={row.getIsSelected() && "selected"} + > + {expandable && ( + <TableCell + className={cn("w-10", compactStyles.cell)} + style={getExpandButtonStyle()} + > + {renderExpandButton(row.id)} + </TableCell> + )} - {/* 확장된 컨텐츠 행 - 가로스크롤 독립성 보장 */} - {isExpanded && renderExpandedContent && ( - <TableRow className="hover:bg-transparent"> + {row.getVisibleCells().map((cell) => { + if (cell.column.getIsGrouped()) { + return null + } + + return ( <TableCell - colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)} - className={cn( - compactStyles.expandedCell, - "border-t-0 relative", - expandedRowClassName - )} + key={cell.id} + data-column-id={cell.column.id} + className={compactStyles.cell} style={{ - minHeight: expandedHeight || 'auto', + ...getPinnedStyle(cell.column, false), // 🎯 동적 스타일 + width: cell.column.getSize() // 🎯 width 별도 설정 }} > - {/* 확장된 내용을 위한 고정 폭 컨테이너 */} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + </TableCell> + ) + })} + </TableRow> + + {isExpanded && renderExpandedContent && ( + <TableRow className="hover:bg-transparent"> + <TableCell + colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)} + className={cn( + compactStyles.expandedCell, + "border-t-0 relative", + expandedRowClassName + )} + style={{ + minHeight: expandedHeight || 'auto', + }} + > + {useSimpleExpansion ? ( + // 🎯 간단한 확장 방식: 테이블 내부에서만 확장 + <div style={getSimpleExpandedStyle()}> + <ExpandedContentWrapper rowId={row.id}> + {renderExpandedContent(row.original)} + </ExpandedContentWrapper> + </div> + ) : ( + // 🎯 전체 화면 확장 방식: 테이블 너비 기준으로 개선 <div className="relative w-full"> - {/* 가로스크롤과 독립적인 확장 영역 */} <div - className="absolute left-0 right-0 top-0 border-t" - style={{ - width: '80vw', - marginLeft: 'calc(-48vw + 50%)', - }} + className="absolute top-0" + style={getExpandedContentStyle()} > - <div className="max-w-none mx-auto"> - <ExpandedContentWrapper rowId={row.id}> - {renderExpandedContent(row.original)} - </ExpandedContentWrapper> - </div> + <ExpandedContentWrapper rowId={row.id}> + {renderExpandedContent(row.original)} + </ExpandedContentWrapper> </div> - {/* 높이 유지를 위한 스페이서 */} <div className="opacity-0 pointer-events-none" - style={{ height: Math.max(expandedHeight, 200) }} + style={{ height: getExpandedPlaceholderHeight(expandedHeight) }} /> </div> - </TableCell> - </TableRow> - )} - </React.Fragment> - ) - }) - ) : ( - // 데이터가 없을 때 - <TableRow> - <TableCell - colSpan={table.getAllColumns().length + (expandable ? 1 : 0)} - className={compactStyles.emptyRow + " text-center"} - > - No results. - </TableCell> - </TableRow> - )} - </TableBody> - </Table> - </div> + )} + </TableCell> + </TableRow> + )} + </React.Fragment> + ) + }) + ) : ( + <TableRow> + <TableCell + colSpan={table.getAllColumns().length + (expandable ? 1 : 0)} + className={compactStyles.emptyRow + " text-center"} + > + No results. + </TableCell> + </TableRow> + )} + </TableBody> + </Table> </div> </div> <div className="flex flex-col gap-2.5"> - {/* Pagination */} <DataTablePagination table={table} /> - - {/* Floating Bar (선택된 행 있을 때) */} {table.getFilteredSelectedRowModel().rows.length > 0 && floatingBar} </div> </div> |
