@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
157 lines (152 loc) • 6.28 kB
JavaScript
/**
* 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 };