Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/react-table/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ interface TableContextProps {
registerSelectableRow?: () => void;
hasAnimations?: boolean;
variant?: TableVariant | 'compact';
isStickyHeader?: boolean;
}

export const TableContext = createContext<TableContextProps>({
registerSelectableRow: () => {},
hasAnimations: false,
variant: undefined
variant: undefined,
isStickyHeader: false
});

const TableBase: React.FunctionComponent<TableProps> = ({
Expand Down Expand Up @@ -211,7 +213,7 @@ const TableBase: React.FunctionComponent<TableProps> = ({
};

return (
<TableContext.Provider value={{ registerSelectableRow, hasAnimations, variant }}>
<TableContext.Provider value={{ registerSelectableRow, hasAnimations, variant, isStickyHeader }}>
<table
aria-label={ariaLabel}
role={role}
Expand Down
99 changes: 84 additions & 15 deletions packages/react-table/src/components/Table/Thead.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import { forwardRef } from 'react';
import { forwardRef, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Table/table';
import { TableContext } from './Table';

/** Ratio must be below this to count as “pinned” (avoids doc-layout subpixel + strict threshold: [1] never hitting exactly 1). */
const PINNED_INTERSECTION_RATIO = 0.999;

const getOverflowScrollParent = (node: HTMLElement): Element | null => {
let parent = node.parentElement;
while (parent) {
const style = getComputedStyle(parent);
if (/(auto|scroll|overlay)/.test(style.overflowY) || /(auto|scroll|overlay)/.test(style.overflowX)) {
return parent;
}
parent = parent.parentElement;
}
return null;
};

const assignRef = <T,>(ref: React.Ref<T> | undefined, value: T | null) => {
if (!ref) {
return;
}
if (typeof ref === 'function') {
ref(value);
} else {
(ref as React.MutableRefObject<T | null>).current = value;
}
};

export interface TheadProps extends React.HTMLProps<HTMLTableSectionElement> {
/** Content rendered inside the <thead> row group */
Expand All @@ -22,20 +49,62 @@ const TheadBase: React.FunctionComponent<TheadProps> = ({
innerRef,
hasNestedHeader,
...props
}: TheadProps) => (
<thead
className={css(
styles.tableThead,
className,
noWrap && styles.modifiers.nowrap,
hasNestedHeader && styles.modifiers.nestedColumnHeader
)}
ref={innerRef}
{...props}
>
{children}
</thead>
);
}: TheadProps) => {
const { isStickyHeader } = useContext(TableContext);
const observeStickyPin = !!isStickyHeader;
const [isPinned, setIsPinned] = useState(false);
const theadElRef = useRef<HTMLTableSectionElement | null>(null);

const setTheadRef = useCallback(
(node: HTMLTableSectionElement | null) => {
theadElRef.current = node;
assignRef(innerRef, node);
},
[innerRef]
);

useEffect(() => {
if (!observeStickyPin || typeof IntersectionObserver === 'undefined') {
setIsPinned(false);
return;
}

const el = theadElRef.current;
if (!el) {
return;
}

const scrollRoot = getOverflowScrollParent(el);

// Requires sticky thead `inset-block-start: -1px` in CSS (see table.css).
const observer = new IntersectionObserver(
([entry]) => {
// console.log(scrollRoot, entry, entry.intersectionRatio);
setIsPinned(entry.intersectionRatio < PINNED_INTERSECTION_RATIO);
},
{ threshold: [0, 1], root: scrollRoot }
);

observer.observe(el);
return () => observer.disconnect();
}, [observeStickyPin]);

return (
<thead
className={css(
styles.tableThead,
className,
noWrap && styles.modifiers.nowrap,
hasNestedHeader && styles.modifiers.nestedColumnHeader,
observeStickyPin && isPinned && 'PINNED'
)}
ref={setTheadRef}
{...props}
>
{children}
</thead>
);
};

export const Thead = forwardRef((props: TheadProps, ref: React.Ref<HTMLTableSectionElement>) => (
<TheadBase {...props} innerRef={ref} />
Expand Down
Loading