UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

329 lines (322 loc) • 16.3 kB
import { SortAscIcon, SortDescIcon } from '@primer/octicons-react'; import { clsx } from 'clsx'; import React from 'react'; import styled from 'styled-components'; import Text from '../Text/Text.js'; import { get } from '../constants.js'; import sx from '../sx.js'; import VisuallyHidden from '../_VisuallyHidden.js'; import { SortDirection } from './sorting.js'; import { useTableLayout } from './useTable.js'; import { SkeletonText } from '../experimental/Skeleton/SkeletonText.js'; import { ScrollableRegion } from '../ScrollableRegion/ScrollableRegion.js'; import '../FeatureFlags/FeatureFlags.js'; import { useFeatureFlag } from '../FeatureFlags/useFeatureFlag.js'; import '../FeatureFlags/DefaultFeatureFlags.js'; import { toggleStyledComponent } from '../internal/utils/toggleStyledComponent.js'; import { Button } from '../internal/components/ButtonReset.js'; import classes from './Table.module.css.js'; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const cssModulesFlag = 'primer_react_css_modules_staff'; // ---------------------------------------------------------------------------- // Table // ---------------------------------------------------------------------------- const StyledTable = toggleStyledComponent(cssModulesFlag, 'table', styled.table.withConfig({ displayName: "Table__StyledTable", componentId: "sc-jofqvq-0" })(["--table-border-radius:0.375rem;--table-cell-padding:var(--cell-padding-block,0.5rem) var(--cell-padding-inline,0.75rem);--table-font-size:0.75rem;background-color:", ";border-spacing:0;border-collapse:separate;display:grid;font-size:var(--table-font-size);grid-template-columns:var(--grid-template-columns);line-height:calc(20 / 12);width:100%;&[data-cell-padding='condensed']{--cell-padding-block:0.25rem;--cell-padding-inline:0.5rem;}&[data-cell-padding='normal']{--cell-padding-block:0.5rem;--cell-padding-inline:0.75rem;}&[data-cell-padding='spacious']{--cell-padding-block:0.75rem;--cell-padding-inline:1rem;}.TableCell:first-child,.TableHeader:first-child{border-left:1px solid ", ";}.TableCell:last-child,.TableHeader:last-child{border-right:1px solid ", ";}.TableHeader,.TableCell{text-align:start;display:flex;align-items:center;border-bottom:1px solid ", ";padding:var(--table-cell-padding);}.TableHeader[data-cell-align='end'],.TableCell[data-cell-align='end']{text-align:end;display:flex;justify-content:flex-end;}.TableHeader[data-cell-align='end'] .TableSortButton{display:flex;flex-direction:row-reverse;}.TableHead .TableRow:first-of-type .TableHeader{border-top:1px solid ", ";}.TableHead .TableRow:first-of-type .TableHeader:first-child{border-top-left-radius:var(--table-border-radius);}.TableHead .TableRow:first-of-type .TableHeader:last-child{border-top-right-radius:var(--table-border-radius);}.TableOverflowWrapper:last-child & .TableBody .TableRow:last-of-type .TableCell:first-child{border-bottom-left-radius:var(--table-border-radius);}.TableOverflowWrapper:last-child & .TableBody .TableRow:last-of-type .TableCell:last-child{border-bottom-right-radius:var(--table-border-radius);}.TableRow > *:first-child:not(.TableCellSkeleton),.TableRow > *:first-child .TableCellSkeletonItem{padding-inline-start:1rem;}.TableRow > *:last-child:not(.TableCellSkeleton),.TableRow > *:last-child .TableCellSkeletonItem{padding-inline-end:1rem;}.TableHeader{background-color:", ";color:", ";font-weight:600;border-top:1px solid ", ";}.TableHeader[aria-sort='descending'],.TableHeader[aria-sort='ascending']{color:", ";}.TableSortIcon{visibility:hidden;}.TableHeader:hover .TableSortIcon--ascending,.TableHeader .TableSortButton:focus .TableSortIcon--ascending{visibility:visible;}.TableHeader[aria-sort='ascending'] .TableSortIcon--ascending,.TableHeader[aria-sort='descending'] .TableSortIcon--descending{visibility:visible;}.TableRow:hover .TableCell:not(.TableCellSkeleton){background-color:", ";}.TableCell[scope='row']{align-items:center;display:flex;color:", ";font-weight:600;}.TableCellSkeleton{padding:0;}.TableCellSkeletonItems{display:flex;flex-direction:column;width:100%;}.TableCellSkeletonItem{padding:var(--table-cell-padding);&:nth-of-type(5n + 1){--skeleton-item-width:85%;}&:nth-of-type(5n + 2){--skeleton-item-width:67.5%;}&:nth-of-type(5n + 3){--skeleton-item-width:80%;}&:nth-of-type(5n + 4){--skeleton-item-width:60%;}&:nth-of-type(5n + 5){--skeleton-item-width:75%;}}.TableCellSkeletonItem [data-component='SkeletonText']{width:var(--skeleton-item-width);}.TableCellSkeletonItem:not(:last-of-type){border-bottom:1px solid ", ";}.TableHead,.TableBody,.TableRow{display:contents;}@supports (grid-template-columns:subgrid){.TableHead,.TableBody,.TableRow{display:grid;grid-template-columns:subgrid;grid-column:-1 /1;}}.TableSortButton{column-gap:0.5rem;}"], get('colors.canvas.default'), get('colors.border.default'), get('colors.border.default'), get('colors.border.default'), get('colors.border.default'), get('colors.canvas.subtle'), get('colors.fg.muted'), get('colors.border.default'), get('colors.fg.default'), get('colors.actionListItem.default.hoverBg'), get('colors.fg.default'), get('colors.border.default'))); const Table = /*#__PURE__*/React.forwardRef(function Table({ 'aria-labelledby': labelledby, cellPadding = 'normal', className, gridTemplateColumns, ...rest }, ref) { const enabled = useFeatureFlag(cssModulesFlag); return ( /*#__PURE__*/ // TODO update type to be non-optional in next major release // @ts-expect-error this type should be required in the next major version React.createElement(ScrollableRegion, { "aria-labelledby": labelledby, className: clsx('TableOverflowWrapper', { [classes.TableOverflowWrapper]: enabled }) }, /*#__PURE__*/React.createElement(StyledTable, _extends({}, rest, { "aria-labelledby": labelledby, "data-cell-padding": cellPadding, className: clsx(className, { Table: true, [classes.Table]: enabled }), role: "table", ref: ref, style: { '--grid-template-columns': gridTemplateColumns } }))) ); }); // ---------------------------------------------------------------------------- // TableHead // ---------------------------------------------------------------------------- function TableHead({ children }) { const enabled = useFeatureFlag(cssModulesFlag); return ( /*#__PURE__*/ // We need to explicitly pass this role because some ATs and browsers drop table semantics // when we use `display: contents` or `display: grid` in the table React.createElement("thead", { className: clsx('TableHead', { [classes.TableHead]: enabled }), role: "rowgroup" }, children) ); } TableHead.displayName = "TableHead"; // ---------------------------------------------------------------------------- // TableBody // ---------------------------------------------------------------------------- function TableBody({ children }) { const enabled = useFeatureFlag(cssModulesFlag); return ( /*#__PURE__*/ // We need to explicitly pass this role because some ATs and browsers drop table semantics // when we use `display: contents` or `display: grid` in the table React.createElement("tbody", { className: clsx('TableBody', { [classes.TableBody]: enabled }), role: "rowgroup" }, children) ); } TableBody.displayName = "TableBody"; // ---------------------------------------------------------------------------- // TableHeader // ---------------------------------------------------------------------------- function TableHeader({ align, children, ...rest }) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement("th", _extends({}, rest, { className: clsx('TableHeader', { [classes.TableHeader]: enabled }), role: "columnheader", scope: "col", "data-cell-align": align }), children); } TableHeader.displayName = "TableHeader"; function TableSortHeader({ align, children, direction, onToggleSort, ...rest }) { const enabled = useFeatureFlag(cssModulesFlag); const ariaSort = direction === 'DESC' ? 'descending' : direction === 'ASC' ? 'ascending' : undefined; return /*#__PURE__*/React.createElement(TableHeader, _extends({}, rest, { "aria-sort": ariaSort, align: align }), /*#__PURE__*/React.createElement(Button, { type: "button", className: clsx('TableSortButton', { [classes.TableSortButton]: enabled }), onClick: () => { onToggleSort(); } }, children, direction === SortDirection.NONE || direction === SortDirection.ASC ? /*#__PURE__*/React.createElement(SortAscIcon, { className: clsx('TableSortIcon', 'TableSortIcon--ascending', { [classes.TableSortIcon]: enabled, [classes['TableSortIcon--ascending']]: enabled }) }) : null, direction === SortDirection.DESC ? /*#__PURE__*/React.createElement(SortDescIcon, { className: clsx('TableSortIcon', 'TableSortIcon--descending', { [classes.TableSortIcon]: enabled, [classes['TableSortIcon--descending']]: enabled }) }) : null)); } TableSortHeader.displayName = "TableSortHeader"; // ---------------------------------------------------------------------------- // TableRow // ---------------------------------------------------------------------------- function TableRow({ children, ...rest }) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement("tr", _extends({}, rest, { className: clsx('TableRow', { [classes.TableRow]: enabled }), role: "row" }), children); } TableRow.displayName = "TableRow"; // ---------------------------------------------------------------------------- // TableCell // ---------------------------------------------------------------------------- function TableCell({ align, className, children, scope, ...rest }) { const enabled = useFeatureFlag(cssModulesFlag); const BaseComponent = scope ? 'th' : 'td'; const role = scope ? 'rowheader' : 'cell'; return /*#__PURE__*/React.createElement(BaseComponent, _extends({}, rest, { className: clsx('TableCell', className, { [classes.TableCell]: enabled }), scope: scope, role: role, "data-cell-align": align }), children); } TableCell.displayName = "TableCell"; function TableCellPlaceholder({ children }) { return /*#__PURE__*/React.createElement(Text, { color: "fg.subtle" }, children); } TableCellPlaceholder.displayName = "TableCellPlaceholder"; // ---------------------------------------------------------------------------- // TableContainer // ---------------------------------------------------------------------------- const StyledTableContainer = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({ displayName: "Table__StyledTableContainer", componentId: "sc-jofqvq-1" })(["display:grid;grid-template-columns:1fr 1fr;grid-template-areas:'title actions' 'divider divider' 'subtitle subtitle' 'filter filter' 'table table' 'footer footer';column-gap:", ";", " .TableTitle{grid-area:title;align-self:center;}.TableSubtitle{grid-area:subtitle;}.TableActions{display:flex;column-gap:", ";align-items:center;grid-area:actions;justify-self:end;}.TableDivider{grid-area:divider;margin-top:", ";margin-bottom:", ";}.Table{grid-area:table;}.TableTitle + .TableOverflowWrapper,.TableSubtitle + .TableOverflowWrapper,.TableActions + .TableOverflowWrapper{margin-top:", ";}.TableOverflowWrapper{grid-area:table;}"], get('space.2'), sx, get('space.2'), get('space.3'), get('space.2'), get('space.2'))); function TableContainer({ children, sx }) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement(StyledTableContainer, { className: clsx({ [classes.TableContainer]: enabled }), sx: sx }, children); } TableContainer.displayName = "TableContainer"; const TableTitle = /*#__PURE__*/React.forwardRef(function TableTitle({ as = 'h2', children, id }, ref) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement(StyledTableTitle, { as: as, className: clsx('TableTitle', { [classes.TableTitle]: enabled }), id: id, ref: ref }, children); }); const StyledTableTitle = toggleStyledComponent(cssModulesFlag, 'h2', styled.h2.withConfig({ displayName: "Table__StyledTableTitle", componentId: "sc-jofqvq-2" })(["color:var(--fgColor-default);font-size:var(--text-body-size-medium);font-weight:var(--base-text-weight-semibold);line-height:calc(20 / 14);margin:0;"])); function TableSubtitle({ as, children, id }) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement(StyledTableSubtitle, { as: as, className: clsx('TableSubtitle', { [classes.TableSubtitle]: enabled }), id: id }, children); } TableSubtitle.displayName = "TableSubtitle"; const StyledTableSubtitle = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({ displayName: "Table__StyledTableSubtitle", componentId: "sc-jofqvq-3" })(["color:var(--fgColor-default);font-weight:var(--base-text-weight-normal);font-size:var(--text-body-size-small);line-height:var(--text-title-lineHeight-small);margin:0;"])); function TableDivider() { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement(StyledTableDivider, { className: clsx('TableDivider', { [classes.TableDivider]: enabled }), role: "presentation" }); } TableDivider.displayName = "TableDivider"; const StyledTableDivider = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({ displayName: "Table__StyledTableDivider", componentId: "sc-jofqvq-4" })(["background-color:var(--borderColor-default);width:100%;height:1px;"])); function TableActions({ children }) { const enabled = useFeatureFlag(cssModulesFlag); return /*#__PURE__*/React.createElement("div", { className: clsx('TableActions', { [classes.TableActions]: enabled }) }, children); } TableActions.displayName = "TableActions"; // ---------------------------------------------------------------------------- // TableSkeleton // ---------------------------------------------------------------------------- function TableSkeleton({ cellPadding, columns, rows = 10, ...rest }) { const enabled = useFeatureFlag(cssModulesFlag); const { gridTemplateColumns } = useTableLayout(columns); return /*#__PURE__*/React.createElement(Table, _extends({}, rest, { cellPadding: cellPadding, gridTemplateColumns: gridTemplateColumns }), /*#__PURE__*/React.createElement(TableHead, null, /*#__PURE__*/React.createElement(TableRow, null, Array.isArray(columns) ? columns.map((column, i) => { return /*#__PURE__*/React.createElement(TableHeader, { key: i }, typeof column.header === 'string' ? column.header : column.header()); }) : null)), /*#__PURE__*/React.createElement(TableBody, null, /*#__PURE__*/React.createElement(TableRow, null, Array.from({ length: columns.length }).map((_, i) => { return /*#__PURE__*/React.createElement(TableCell, { key: i, className: clsx('TableCellSkeleton', { [classes.TableCellSkeleton]: enabled }) }, /*#__PURE__*/React.createElement(VisuallyHidden, null, "Loading"), /*#__PURE__*/React.createElement("div", { className: clsx('TableCellSkeletonItems', { [classes.TableCellSkeletonItems]: enabled }) }, Array.from({ length: rows }).map((_, i) => { return /*#__PURE__*/React.createElement("div", { key: i, className: clsx('TableCellSkeletonItem', { [classes.TableCellSkeletonItem]: enabled }) }, /*#__PURE__*/React.createElement(SkeletonText, null)); }))); })))); } TableSkeleton.displayName = "TableSkeleton"; export { Table, TableActions, TableBody, TableCell, TableCellPlaceholder, TableContainer, TableDivider, TableHead, TableHeader, TableRow, TableSkeleton, TableSortHeader, TableSubtitle, TableTitle };