@@ -23,8 +23,6 @@ export interface ResourceColumn {
2323 id : string
2424 header : string
2525 widthMultiplier ?: number
26- /** Fixed pixel width. When set, the column is excluded from proportional sizing. */
27- widthPx ?: number
2826}
2927
3028export interface ResourceCell {
@@ -94,7 +92,7 @@ interface ResourceProps {
9492 overlay ?: ReactNode
9593}
9694
97- const EMPTY_CELL_PLACEHOLDER = '- - - '
95+ export const EMPTY_CELL_PLACEHOLDER = '— '
9896const SKELETON_ROW_COUNT = 5
9997
10098/**
@@ -214,20 +212,13 @@ export const ResourceTable = memo(function ResourceTable({
214212 emptyMessage,
215213 overlay,
216214} : ResourceTableProps ) {
217- const headerRef = useRef < HTMLDivElement > ( null )
218215 const loadMoreRef = useRef < HTMLDivElement > ( null )
219216 const sortEnabled = defaultSort != null
220217 const [ internalSort , setInternalSort ] = useState < { column : string ; direction : 'asc' | 'desc' } > ( {
221218 column : defaultSort ?? '' ,
222219 direction : 'desc' ,
223220 } )
224221
225- const handleBodyScroll = useCallback ( ( e : React . UIEvent < HTMLDivElement > ) => {
226- if ( headerRef . current ) {
227- headerRef . current . scrollLeft = e . currentTarget . scrollLeft
228- }
229- } , [ ] )
230-
231222 const handleSort = useCallback ( ( column : string , direction : 'asc' | 'desc' ) => {
232223 setInternalSort ( { column, direction } )
233224 } , [ ] )
@@ -290,10 +281,10 @@ export const ResourceTable = memo(function ResourceTable({
290281
291282 return (
292283 < div className = 'relative flex min-h-0 flex-1 flex-col overflow-hidden' >
293- < div ref = { headerRef } className = 'overflow-hidden ' >
284+ < div className = 'min-h-0 flex-1 overflow-auto ' >
294285 < table className = 'w-full table-fixed text-small' >
295286 < ResourceColGroup columns = { columns } hasCheckbox = { hasCheckbox } />
296- < thead className = 'shadow-[inset_0_-1px_0_var(--border)]' >
287+ < thead className = 'sticky top-0 z-10 bg-[var(--bg)] shadow-[inset_0_-1px_0_var(--border)]' >
297288 < tr >
298289 { hasCheckbox && (
299290 < th className = 'h-10 w-[52px] py-1.5 pr-0 pl-5 text-left align-middle' >
@@ -341,14 +332,6 @@ export const ResourceTable = memo(function ResourceTable({
341332 } ) }
342333 </ tr >
343334 </ thead >
344- </ table >
345- </ div >
346- < div
347- className = 'min-h-0 flex-1 overflow-auto [scrollbar-gutter:stable]'
348- onScroll = { handleBodyScroll }
349- >
350- < table className = 'w-full table-fixed text-small' >
351- < ResourceColGroup columns = { columns } hasCheckbox = { hasCheckbox } />
352335 < tbody >
353336 { displayRows . map ( ( row ) => (
354337 < DataRow
@@ -644,43 +627,22 @@ interface ResourceColGroupProps {
644627 hasCheckbox ?: boolean
645628}
646629
647- const CHECKBOX_COLUMN_WIDTH_PX = 52
648- const CHECKBOX_COLUMN_WIDTH = `${ CHECKBOX_COLUMN_WIDTH_PX } px`
630+ const CHECKBOX_COLUMN_WIDTH = '52px'
649631
650632const ResourceColGroup = memo ( function ResourceColGroup ( {
651633 columns,
652634 hasCheckbox,
653635} : ResourceColGroupProps ) {
654- const fixedPxTotal = columns . reduce ( ( sum , col ) => sum + ( col . widthPx ?? 0 ) , 0 )
655- const flexibleWeights = columns . map ( ( col , colIdx ) =>
656- col . widthPx ? 0 : ( colIdx === 0 ? 2.5 : 1.0 ) * ( col . widthMultiplier ?? 1 )
636+ const weights = columns . map (
637+ ( col , colIdx ) => ( colIdx === 0 ? 2.5 : 1.0 ) * ( col . widthMultiplier ?? 1 )
657638 )
658- const flexibleTotal = flexibleWeights . reduce ( ( s , w ) => s + w , 0 )
659- const reservedPx = fixedPxTotal + ( hasCheckbox ? CHECKBOX_COLUMN_WIDTH_PX : 0 )
660-
639+ const total = weights . reduce ( ( s , w ) => s + w , 0 )
661640 return (
662641 < colgroup >
663642 { hasCheckbox && < col style = { { width : CHECKBOX_COLUMN_WIDTH } } /> }
664- { columns . map ( ( col , colIdx ) => {
665- if ( col . widthPx ) {
666- return < col key = { col . id } style = { { width : `${ col . widthPx } px` } } />
667- }
668- const columnRatio = flexibleTotal > 0 ? flexibleWeights [ colIdx ] / flexibleTotal : 0
669- const columnPercent = columnRatio * 100
670- const reservedOffset = reservedPx * columnRatio
671-
672- return (
673- < col
674- key = { col . id }
675- style = { {
676- width :
677- reservedOffset > 0
678- ? `calc(${ columnPercent } % - ${ reservedOffset } px)`
679- : `${ columnPercent } %` ,
680- } }
681- />
682- )
683- } ) }
643+ { columns . map ( ( col , colIdx ) => (
644+ < col key = { col . id } style = { { width : `${ ( ( weights [ colIdx ] / total ) * 100 ) . toFixed ( 3 ) } %` } } />
645+ ) ) }
684646 </ colgroup >
685647 )
686648} )
@@ -697,55 +659,48 @@ const DataTableSkeleton = memo(function DataTableSkeleton({
697659 hasCheckbox,
698660} : DataTableSkeletonProps ) {
699661 return (
700- < >
701- < div className = 'overflow-hidden' >
702- < table className = 'w-full table-fixed text-small' >
703- < ResourceColGroup columns = { columns } hasCheckbox = { hasCheckbox } />
704- < thead className = 'shadow-[inset_0_-1px_0_var(--border)]' >
705- < tr >
662+ < div className = 'min-h-0 flex-1 overflow-auto' >
663+ < table className = 'w-full table-fixed text-small' >
664+ < ResourceColGroup columns = { columns } hasCheckbox = { hasCheckbox } />
665+ < thead className = 'sticky top-0 z-10 bg-[var(--bg)] shadow-[inset_0_-1px_0_var(--border)]' >
666+ < tr >
667+ { hasCheckbox && (
668+ < th className = 'h-10 w-[52px] py-2.5 pr-0 pl-5 text-left align-middle' >
669+ < Skeleton className = 'size-[14px] rounded-xs' />
670+ </ th >
671+ ) }
672+ { columns . map ( ( col ) => (
673+ < th
674+ key = { col . id }
675+ className = 'h-10 px-6 py-2.5 text-left align-middle font-base text-[var(--text-muted)]'
676+ >
677+ < div className = 'flex min-h-[20px] items-center' >
678+ < Skeleton className = 'h-[12px] w-[56px]' />
679+ </ div >
680+ </ th >
681+ ) ) }
682+ </ tr >
683+ </ thead >
684+ < tbody >
685+ { Array . from ( { length : rowCount } , ( _ , i ) => (
686+ < tr key = { i } >
706687 { hasCheckbox && (
707- < th className = 'h-10 w-[52px] py-2.5 pr-0 pl-5 text-left align-middle' >
688+ < td className = 'w-[52px] py-2.5 pr-0 pl-5 align-middle' >
708689 < Skeleton className = 'size-[14px] rounded-xs' />
709- </ th >
690+ </ td >
710691 ) }
711- { columns . map ( ( col ) => (
712- < th
713- key = { col . id }
714- className = 'h-10 px-6 py-2.5 text-left align-middle font-base text-[var(--text-muted)]'
715- >
716- < div className = 'flex min-h-[20px] items-center' >
717- < Skeleton className = 'h-[12px] w-[56px]' />
718- </ div >
719- </ th >
692+ { columns . map ( ( col , colIdx ) => (
693+ < td key = { col . id } className = 'px-6 py-2.5 align-middle' >
694+ < span className = 'flex min-h-[21px] items-center gap-3' >
695+ { colIdx === 0 && < Skeleton className = 'size-[14px] rounded-xs' /> }
696+ < Skeleton className = 'h-[14px] w-[128px]' />
697+ </ span >
698+ </ td >
720699 ) ) }
721700 </ tr >
722- </ thead >
723- </ table >
724- </ div >
725- < div className = 'min-h-0 flex-1 overflow-auto' >
726- < table className = 'w-full table-fixed text-small' >
727- < ResourceColGroup columns = { columns } hasCheckbox = { hasCheckbox } />
728- < tbody >
729- { Array . from ( { length : rowCount } , ( _ , i ) => (
730- < tr key = { i } >
731- { hasCheckbox && (
732- < td className = 'w-[52px] py-2.5 pr-0 pl-5 align-middle' >
733- < Skeleton className = 'size-[14px] rounded-xs' />
734- </ td >
735- ) }
736- { columns . map ( ( col , colIdx ) => (
737- < td key = { col . id } className = 'px-6 py-2.5 align-middle' >
738- < span className = 'flex min-h-[21px] items-center gap-3' >
739- { colIdx === 0 && < Skeleton className = 'size-[14px] rounded-xs' /> }
740- < Skeleton className = 'h-[14px] w-[128px]' />
741- </ span >
742- </ td >
743- ) ) }
744- </ tr >
745- ) ) }
746- </ tbody >
747- </ table >
748- </ div >
749- </ >
701+ ) ) }
702+ </ tbody >
703+ </ table >
704+ </ div >
750705 )
751706} )
0 commit comments