Skip to content

Commit

Permalink
sticky-header-intersection-observer
Browse files Browse the repository at this point in the history
  • Loading branch information
KenyShah24 committed Dec 11, 2024
1 parent bf0d4dc commit e4366eb
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 4 deletions.
57 changes: 56 additions & 1 deletion src/assets/scss/_recordset-table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
overflow-y: hidden;
}

.chaise-table.table {
.chaise-table.table, .sticky-header > table {
border-top: 1px solid map.get(variables.$color-map, 'table-border');
border-bottom: 1px solid map.get(variables.$color-map, 'table-border');
margin: 0;
Expand Down Expand Up @@ -262,3 +262,58 @@
display: none !important;
position: absolute !important;
}
.sticky-header {
position: fixed;
top: 0;
// z-index: 1000;
visibility:none;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
overflow-x: hidden;
}
.sticky-header > table > thead > tr > th{
font-weight: 400;
border-top: 1px solid map.get(variables.$color-map, 'table-border');
position: relative;
padding: 7px;


&.actions-header {
white-space: nowrap;
text-align: center;
font-size: variables.$h4-font-size;
padding: 8px;
}

.table-column-displayname {
font-size: variables.$h4-font-size;
}

// preserve space for the icon on the right
// 20 for the "icon space"
// 10 for the "right indentation"
padding-right: 30px;
.table-heading-icons {
position: absolute;
bottom: 7px;
right: 10px;
font-size: variables.$h4-font-size;

.table-column-spinner {
position: absolute;
bottom: 2px;
right: -5px;

.spinner-border {
// NOTE: we should not customize the width/height since it causes wobbling effect
color: map.get(variables.$color-map, 'table-header-spinner');
border-width: .16em;
}
}
}
}
.sticky-header{
.sticky-header-table{
table-layout: fixed;
width: 100%;
}
}
183 changes: 180 additions & 3 deletions src/components/recordset/recordset-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ const RecordsetTable = ({
const tableContainer = useRef<HTMLDivElement>(null);
const stickyScrollbarRef = useRef<HTMLDivElement>(null);
const tableEndRef = useRef<HTMLDivElement>(null);
const tableRef = useRef<HTMLTableElement>(null);
const outerTableRef = useRef<HTMLDivElement>(null);
const headRef = useRef<HTMLTableSectionElement>(null);
const stickyHeaderRef = useRef<HTMLDivElement>(null);


const [currSortColumn, setCurrSortColumn] = useState<SortColumn | null>(
Expand All @@ -70,6 +74,7 @@ const RecordsetTable = ({
// tracks whether a paging action has successfully occurred for this table
// used for related tables to fire an event when the content has loaded to scroll back to the top of the related table
const [pagingSuccess, setPagingSuccess] = useState<boolean>(false);
const [headerTop, setHeaderTop] = useState<number>(0);

type RowConfig = {
isSelected: boolean;
Expand Down Expand Up @@ -161,7 +166,167 @@ const RecordsetTable = ({
}
}, []);

useEffect(()=>{
console.log('calllled');
setHeaderTop(headRef.current!.getBoundingClientRect().top);
},[isInitialized]);

console.log(headerTop, headRef.current?.getBoundingClientRect());

useEffect(()=>{
if(!outerTableRef.current || !headRef.current || !stickyHeaderRef.current){
return;
}

const observer = new IntersectionObserver(
([entry]) => {
if(stickyHeaderRef.current){
if (!entry.isIntersecting) {
stickyHeaderRef.current.style.visibility = 'visible';
const hasScrollbar = outerTableRef.current!.scrollWidth > outerTableRef.current!.clientWidth;
console.log('hasScrollbar ',hasScrollbar);
stickyHeaderRef.current.style.top = `${headerTop}px`;
} else {
stickyHeaderRef.current.style.visibility = 'hidden';
}
}
},
{ root: null, threshold: 0 }
);
observer.observe(headRef.current);

// Sync widths of the columns
const syncWidths = () => {
if(stickyHeaderRef.current && tableRef.current){

const originalThs = tableRef.current.querySelectorAll('tbody > tr > td');
const stickyThs = stickyHeaderRef.current?.querySelectorAll('th');

// Loop through columns and set widths
stickyThs!.forEach((headerCol, index) => {
const dataCol = originalThs[index];
if (dataCol instanceof HTMLElement) { // Ensure it's an HTML element
const colWidth = dataCol.offsetWidth; // Get the actual width of the column
headerCol.style.width = `${colWidth}px`; // Set width on sticky header
}

});
stickyHeaderRef.current.style.width = `${outerTableRef.current?.offsetWidth}px`;
}
};
const handleScroll = () => {
if (stickyHeaderRef.current && stickyScrollbarRef.current) {
stickyHeaderRef.current.scrollLeft = stickyScrollbarRef.current.scrollLeft;
}
};

stickyScrollbarRef.current?.addEventListener('scroll', handleScroll);

// Sync column widths on resize
window.addEventListener('resize', syncWidths);

// Perform initial sync
syncWidths();

// Cleanup function
return () => {
observer.disconnect();
stickyScrollbarRef.current?.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', syncWidths);
};

},[isLoading, headerTop]);

// useEffect(() => {
// if (!outerTableRef.current || !headRef.current || !stickyHeaderRef.current) return;

// // Intersection Observer for bottom scrollbar visibility
// const bottomObserver = new IntersectionObserver(
// ([entry]) => {
// if (stickyScrollbarRef.current) {
// if (entry.isIntersecting) {
// stickyScrollbarRef.current.classList.add('no-scroll-bar');
// if(stickyHeaderRef.current){
// stickyHeaderRef.current.style.visibility = 'hidden';
// }
// } else {
// stickyScrollbarRef.current.classList.remove('no-scroll-bar');
// }
// }
// },
// { root: null, threshold: 0.1 }
// );

// // Intersection Observer for sticky header visibility
// const headerObserver = new IntersectionObserver(
// ([entry]) => {
// if (stickyHeaderRef.current) {
// if (!entry.isIntersecting) {
// stickyHeaderRef.current.style.visibility = 'visible';
// stickyHeaderRef.current.style.top = `calc(${stickyScrollbarRef.current!.getBoundingClientRect().top}px + 15px)`;
// } else {
// stickyHeaderRef.current.style.visibility = 'hidden';
// }
// }
// },
// { root: null, threshold: 0 }
// );

// // Observe elements
// if (tableEndRef.current) bottomObserver.observe(tableEndRef.current);
// headerObserver.observe(headRef.current);

// // Sync widths of the columns
// const syncWidths = () => {
// if (!stickyHeaderRef.current || !tableRef.current) return;

// const originalThs = tableRef.current.querySelectorAll('tbody > tr > td');
// const stickyThs = stickyHeaderRef.current?.querySelectorAll('th');

// stickyThs!.forEach((headerCol, index) => {
// const dataCol = originalThs[index];
// if (dataCol instanceof HTMLElement) {
// const colWidth = dataCol.offsetWidth;
// headerCol.style.width = `${colWidth}px`;
// }
// });

// stickyHeaderRef.current.style.width = `${outerTableRef.current?.offsetWidth}px`;

// console.log('headRef.current:', headRef.current?.offsetWidth,
// 'outerTableRef.current:', outerTableRef.current?.offsetWidth,
// 'stickyHeaderRef.current:', stickyHeaderRef.current.offsetWidth);
// };

// // Scroll synchronization
// const handleScroll = () => {
// if (stickyHeaderRef.current && stickyScrollbarRef.current) {
// stickyHeaderRef.current.scrollLeft = stickyScrollbarRef.current.scrollLeft;
// }
// };

// stickyScrollbarRef.current?.addEventListener('scroll', handleScroll);

// // Sync column widths on resize
// window.addEventListener('resize', syncWidths);

// // Perform initial sync
// syncWidths();

// // Cleanup function
// return () => {
// bottomObserver.disconnect();
// headerObserver.disconnect();
// stickyScrollbarRef.current?.removeEventListener('scroll', handleScroll);
// window.removeEventListener('resize', syncWidths);
// };
// }, [isLoading, showSingleScrollbar]);


useEffect(()=>{
console.log(stickyScrollbarRef.current?.scrollLeft, outerTableRef.current?.scrollLeft, tableContainer.current?.scrollLeft);

},[stickyScrollbarRef.current?.scrollLeft])
/**
* add the top horizontal scroll if needed
*/
Expand Down Expand Up @@ -380,6 +545,7 @@ const RecordsetTable = ({
</span>
)
}
console.log(columnModels.length);

const renderColumnHeaders = () => {
return columnModels.map((col: any, index: number) => {
Expand Down Expand Up @@ -519,9 +685,9 @@ const RecordsetTable = ({
className='chaise-table-top-scroll-wrapper'>
<div className='chaise-table-top-scroll'></div>
</div>
<div className={outerTableClassname()}>
<table className='table chaise-table table-hover'>
<thead className='table-heading'>
<div className={outerTableClassname()} ref={outerTableRef}>
<table className='table chaise-table table-hover' ref={tableRef}>
<thead className='table-heading' ref={headRef}>
<tr>
{showActionButtons && renderActionsHeader()}
{renderColumnHeaders()}
Expand All @@ -536,6 +702,17 @@ const RecordsetTable = ({
top scrollbar when the bottom one is visible */}
<div className='dummy-table-end-div' ref={tableEndRef}/>

<div className='sticky-header' id='sticky-header' ref={stickyHeaderRef}>
<table className='sticky-header-table'>
<thead className='table-heading'>
<tr>
{showActionButtons && renderActionsHeader()}
{renderColumnHeaders()}
</tr>
</thead>
</table>
</div>

{!hasTimeoutError && numHiddenRecords > 0 &&
<div className='chaise-table-footer'>
<button onClick={() => setShowAllRows(!showAllRows)} className='show-all-rows-btn chaise-btn chaise-btn-primary'>
Expand Down

0 comments on commit e4366eb

Please sign in to comment.