From 9a8985ed12bdabb46a8aafbe4d14eae4a7777386 Mon Sep 17 00:00:00 2001 From: Andrew Holloway Date: Tue, 17 Sep 2024 12:00:32 -0500 Subject: [PATCH] feat(DataTable): handle conditional shadow on pinned header --- src/components/DataTable/DataTable.module.css | 8 ++-- src/components/DataTable/DataTable.test.ts | 25 ++++++++++++ src/components/DataTable/DataTable.tsx | 40 ++++++++++++++++++- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/components/DataTable/DataTable.module.css b/src/components/DataTable/DataTable.module.css index 072486210..439d629f8 100644 --- a/src/components/DataTable/DataTable.module.css +++ b/src/components/DataTable/DataTable.module.css @@ -4,8 +4,6 @@ /* Visible table caption */ /* TODO-AH: make it so that we have the search bar and actions wrap together instead of separately */ -/* TODO-AH: implement showing of shadow below sticky headers and column */ -/* - https://stackoverflow.com/questions/38122751/positionsticky-adding-style-when-the-element-detaches-from-normal-flow */ .data-table__caption-container { display: flex; align-items: flex-end; @@ -163,7 +161,7 @@ .data-table__header-row { border-bottom: calc(var(--eds-border-width-sm) * 1px) solid; position: sticky; - top: 0; + top: -1px; } .data-table__group-row { @@ -182,6 +180,10 @@ padding: calc(var(--eds-size-2) / 16 * 1rem) calc(var(--eds-size-3) / 16 * 1rem); } +} + +.data-table--is-pinned { + box-shadow: var(--eds-box-shadow-sm); } diff --git a/src/components/DataTable/DataTable.test.ts b/src/components/DataTable/DataTable.test.ts index 32aca6861..3b7b7fc34 100644 --- a/src/components/DataTable/DataTable.test.ts +++ b/src/components/DataTable/DataTable.test.ts @@ -2,6 +2,31 @@ import { generateSnapshots } from '@chanzuckerberg/story-utils'; import * as stories from './DataTable.stories'; import type { StoryFile } from '../../util/utility-types'; +// Stubbing out this class b/c it doesn't exist in non-browser contexts +export class IntersectionObserver { + root = null; + rootMargin = ''; + thresholds = []; + + disconnect() { + return null; + } + + observe() { + return null; + } + + takeRecords() { + return []; + } + + unobserve() { + return null; + } +} +window.IntersectionObserver = IntersectionObserver; +global.IntersectionObserver = IntersectionObserver; + describe('', () => { generateSnapshots(stories as StoryFile); }); diff --git a/src/components/DataTable/DataTable.tsx b/src/components/DataTable/DataTable.tsx index a0244887e..fdda223cb 100644 --- a/src/components/DataTable/DataTable.tsx +++ b/src/components/DataTable/DataTable.tsx @@ -1,6 +1,6 @@ import { flexRender, type Table } from '@tanstack/react-table'; import clsx from 'clsx'; -import React from 'react'; +import React, { useEffect } from 'react'; import type { EDSBase, Size, Status, Align } from '../../util/variant-types'; @@ -411,6 +411,44 @@ export const DataTableTable = ({ size && styles[`data-table--size-${size}`], ); + // eslint-disable-next-line @chanzuckerberg/edu-react/use-effect-deps-presence + useEffect(() => { + /** + * Here, we set up a selector on the header of a table, and look for it to start + * clipping off the top edge of its visible area. Once it does the observer will trigger + * causing the state class to be added / removed (depending on the intersection value). + * + * We use the generated class names from the module for both the trigger and whats toggled + */ + let observer: IntersectionObserver; + let el: Element | null; + function triggerEffect() { + el = document.querySelector(`.${styles['data-table__table']} thead`); + if (el && typeof window !== 'undefined') { + observer = new IntersectionObserver( + ([event]) => { + return event.target.classList.toggle( + styles['data-table--is-pinned'], + event.intersectionRatio < 1, + ); + }, + + { threshold: [1] }, + ); + + observer.observe(el); + } + } + triggerEffect(); + + // TODO: anything to de-allocate when using `observer`? + return () => { + if (el) { + observer.unobserve(el); + } + }; + }); + return {children}
; };