UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

157 lines (152 loc) 6.28 kB
/** * MSKCC 2021, 2024 */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React__default, { useContext, useRef, useCallback, useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import debounce from 'lodash.debounce'; import { usePrefix } from '../../internal/usePrefix.js'; import { TableContext } from './TableContext.js'; import { useWindowEvent } from '../../internal/useEvent.js'; const isElementWrappingContent = (element, context) => { if (element.children.length > 0) { return false; } const computedStyles = window.getComputedStyle(element); context.font = computedStyles.font ? computedStyles.font : `${computedStyles.fontSize}" "${computedStyles.fontFamily}`; const measuredText = context?.measureText(element.textContent ?? ''); let textWidth = measuredText.width ?? 0; // account for letter spacing const letterSpacing = computedStyles.letterSpacing?.split('px'); if (letterSpacing && letterSpacing.length && !isNaN(Number(letterSpacing[0]))) { textWidth += Number(letterSpacing[0]) * (element.textContent?.length ?? 0); } // account for padding const paddingLeft = computedStyles.paddingLeft?.split('px'); if (paddingLeft && paddingLeft.length && !isNaN(Number(paddingLeft[0]))) { textWidth += Number(paddingLeft[0]); } const paddingRight = computedStyles.paddingLeft?.split('px'); if (paddingRight && paddingRight.length && !isNaN(Number(paddingRight[0]))) { textWidth += Number(paddingRight[0]); } // if measured textWidth is larger than the cell's width, then the content is being wrapped if (textWidth > element.getBoundingClientRect().width) { return true; } return false; }; const Table = _ref => { let { className, children, useZebraStyles, size = 'lg', isSortable = false, useStaticWidth, stickyHeader, overflowMenuOnHover = true, experimentalAutoAlign = false, ...other } = _ref; const { titleId, descriptionId } = useContext(TableContext); const prefix = usePrefix(); const tableRef = useRef(null); const componentClass = cx(`${prefix}--data-table`, className, { [`${prefix}--data-table--${size}`]: size, [`${prefix}--data-table--sort`]: isSortable, [`${prefix}--data-table--zebra`]: useZebraStyles, [`${prefix}--data-table--static`]: useStaticWidth, [`${prefix}--data-table--sticky-header`]: stickyHeader, [`${prefix}--data-table--visible-overflow-menu`]: !overflowMenuOnHover }); const toggleTableBodyAlignmentClass = useCallback(function () { let alignTop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; alignTop ? tableRef.current?.classList.add(`${prefix}--data-table--top-aligned-body`) : tableRef.current?.classList.remove(`${prefix}--data-table--top-aligned-body`); }, [prefix]); const toggleTableHeaderAlignmentClass = useCallback(function () { let alignTop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; alignTop ? tableRef.current?.classList.add(`${prefix}--data-table--top-aligned-header`) : tableRef.current?.classList.remove(`${prefix}--data-table--top-aligned-header`); }, [prefix]); const setTableAlignment = useCallback(() => { if (experimentalAutoAlign) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); if (tableRef.current && context) { const isBodyMultiline = Array.from(tableRef.current.querySelectorAll('td')).some(td => isElementWrappingContent(td, context)); const isHeaderMultiline = Array.from(tableRef.current.querySelectorAll('th')).some(th => { const label = th.querySelector(`.${prefix}--table-header-label`); return label && isElementWrappingContent(label, context); }); toggleTableBodyAlignmentClass(isBodyMultiline); toggleTableHeaderAlignmentClass(isHeaderMultiline); } } else { toggleTableBodyAlignmentClass(false); toggleTableHeaderAlignmentClass(false); } }, [experimentalAutoAlign, toggleTableBodyAlignmentClass, toggleTableHeaderAlignmentClass, prefix]); const debouncedSetTableAlignment = debounce(setTableAlignment, 100); useWindowEvent('resize', debouncedSetTableAlignment); // recalculate table alignment once fonts have loaded if (typeof document !== 'undefined' && document?.fonts?.status && document.fonts.status !== 'loaded') { document.fonts.ready.then(() => { setTableAlignment(); }); } useLayoutEffect(() => { setTableAlignment(); }, [setTableAlignment, size]); const table = /*#__PURE__*/React__default.createElement("div", { className: `${prefix}--data-table-content` }, /*#__PURE__*/React__default.createElement("table", _extends({ "aria-labelledby": titleId, "aria-describedby": descriptionId }, other, { className: componentClass, ref: tableRef }), children)); return stickyHeader ? /*#__PURE__*/React__default.createElement("section", { className: `${prefix}--data-table_inner-container` }, table) : table; }; Table.propTypes = { /** * Pass in the children that will be rendered within the Table */ children: PropTypes.node, className: PropTypes.string, /** * Experimental property. Allows table to align cell contents to the top if there is text wrapping in the content. Might have performance issues, intended for smaller tables */ experimentalAutoAlign: PropTypes.bool, /** * `false` If true, will apply sorting styles */ isSortable: PropTypes.bool, /** * Specify whether the overflow menu (if it exists) should be shown always, or only on hover */ overflowMenuOnHover: PropTypes.bool, /** * Change the row height of table. Currently supports `xs`, `sm`, `md`, `lg`, and `xl`. */ size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), /** * `false` If true, will keep the header sticky (only data rows will scroll) */ stickyHeader: PropTypes.bool, /** * `false` If true, will use a width of 'auto' instead of 100% */ useStaticWidth: PropTypes.bool, /** * `true` to add useZebraStyles striping. */ useZebraStyles: PropTypes.bool }; export { Table, Table as default };