diff --git a/packages/braid-design-system/src/lib/components/Table/Table.css.ts b/packages/braid-design-system/src/lib/components/Table/Table.css.ts index ef35a851eb8..b977e398728 100644 --- a/packages/braid-design-system/src/lib/components/Table/Table.css.ts +++ b/packages/braid-design-system/src/lib/components/Table/Table.css.ts @@ -5,15 +5,15 @@ import { responsiveStyle } from '../../css/responsiveStyle'; // TABLE const borderColor = createVar(); -const borderWidth = createVar(); +const sectionBorderWidth = createVar(); export const table = style([ { vars: { // [borderWidth]: '1px', - [borderWidth]: vars.borderWidth.standard, + [sectionBorderWidth]: vars.borderWidth.standard, }, borderCollapse: 'separate', - border: `${borderWidth} solid ${borderColor}`, + border: `${sectionBorderWidth} solid ${borderColor}`, fontVariantNumeric: 'tabular-nums', wordBreak: 'break-word', }, @@ -51,20 +51,6 @@ globalStyle( }, ); -// export const noSideBorders = style({ -// borderLeftColor: 'transparent', -// borderRightColor: 'transparent', -// }); - -// TABLE HEADER -export const tableHeaderRounding = style({}); -// globalStyle(`${tableHeaderRounding} > tr > th:first-of-type`, { -// borderTopLeftRadius: vars.borderRadius.large, -// }); -// globalStyle(`${tableHeaderRounding} > tr > th:last-of-type`, { -// borderTopRightRadius: vars.borderRadius.large, -// }); - // TABLE CELLS export const alignYCenter = style({ verticalAlign: 'middle', @@ -89,18 +75,22 @@ export const maxWidth = style({ maxWidth: maxWidthVar, }); -export const borderBottom = style({ - borderBottom: `${borderWidth} solid ${borderColor}`, +export const headerCellBorder = style({}); +globalStyle(`${table} > thead > tr:last-child > ${headerCellBorder}`, { + borderBottom: `${sectionBorderWidth} solid ${borderColor}`, }); -// No border on bottom of last row (table border is used instead) -globalStyle(`${table} > tbody > tr:last-of-type > *`, { - borderBottom: 0, + +export const bodyCellBorder = style({}); +// Apply border on bottom of all cells except last row of a table section +globalStyle(`${table} > * > tr:not(:last-child) > *`, { + borderBottom: `1px solid ${borderColor}`, }); -// export const borderTopFooter = style({}); -// globalStyle(`${table} > tfoot > *:first-of-type > *${borderTopFooter}`, { -// borderTop: `${vars.borderWidth.standard} solid ${borderColor}`, -// }); +export const footerCellBorder = style({}); +// Apply heavier section border between footer and body sections +globalStyle(`${table} > tfoot > tr:first-child > ${footerCellBorder}`, { + borderTop: `${sectionBorderWidth} solid ${borderColor}`, +}); export const showOnTablet = style( responsiveStyle({ tablet: { display: 'table-cell' } }), diff --git a/packages/braid-design-system/src/lib/components/Table/Table.docs.tsx b/packages/braid-design-system/src/lib/components/Table/Table.docs.tsx index f2bf368a9da..6538e4d298d 100644 --- a/packages/braid-design-system/src/lib/components/Table/Table.docs.tsx +++ b/packages/braid-design-system/src/lib/components/Table/Table.docs.tsx @@ -2,6 +2,7 @@ import React from 'react'; import type { ComponentDocs } from 'site/types'; import { Badge, + Box, ButtonIcon, Card, HiddenVisually, @@ -12,6 +13,7 @@ import { Table, TableBody, TableCell, + TableHeadCell, TableHeader, TableRow, Text, @@ -23,19 +25,29 @@ import { Placeholder } from '../private/Placeholder/Placeholder'; const docs: ComponentDocs = { category: 'Layout', + subComponents: [ + 'TableHeader', + 'TableFooter', + 'TableBody', + 'TableRow', + 'TableHeadCell', + 'TableCell', + ], Example: () => source( - - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + @@ -95,70 +107,152 @@ const docs: ComponentDocs = { { label: 'Table structure', description: ( - - A Table must include a TableBody{' '} - with one or more TableRow components, with each - containing TableCell components that represent each - column of the tabular data. - + <> + + A Table is made up of three sections:{' '} + TableHeader, TableBody (required) + and TableFooter, each section containing one or + more TableRow components. Each row is made of up of{' '} + TableCell or TableHeaderCell{' '} + components representing each column of the data. + + ), - Example: ({ setDefaultState, getState }) => { - const { code, value } = source( - <> - {setDefaultState('rows', [ - { - column1: 'Sit', - column2: 'Amet', - column3: 'Consectetur', - }, - { - column1: 'Adipiscing', - column2: 'Elit', - column3: 'Praesent', - }, - { - column1: 'Semper', - column2: 'Interdum', - column3: 'Viverra', - }, - ])} -
- - {getState('rows').map((row: any) => ( - - - {row.column1} - - - {row.column2} - - - {row.column3} - - - ))} - -
- , - ); + code: false, + playroom: false, + Example: () => + source( + + + + Table + + + + + TableHeader + - return { - code: code.replaceAll(': any', '').replaceAll(' key={row}', ''), - value, - }; - }, + + + + TableRow + + + + TableHeaderCell + + + + + + + + + + TableBody (required) + + + + + + TableRow + + + + + TableCell + + + + + + + + + + + TableFooter + + + + + + TableRow + + + + + TableCell + + + + + + + + + , + ), }, { label: 'Column headings', description: ( - A TableHeader component can be placed before the{' '} - TableBody, providing a header row that defaults all{' '} - TableCell components inside to be{' '} - - table header elements - {' '} - with relevant styling. + A TableHeader can be provided, containing a{' '} + TableRow of column headings. Each heading cell should + use the TableHeadCell component, providing the + relevant semantics, column labelling and styling. ), Example: ({ setDefaultState, getState }) => { @@ -183,15 +277,17 @@ const docs: ComponentDocs = { ])} - - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( @@ -222,9 +318,9 @@ const docs: ComponentDocs = { label: 'Row headings', description: ( - Row-level headings are supported by specifying the{' '} - header prop on the TableCell{' '} - component. + Row-level headings are supported by providing a{' '} + TableHeadCell within a TableRow of + the TableBody section. ), Example: ({ setDefaultState, getState }) => { @@ -251,9 +347,9 @@ const docs: ComponentDocs = { {getState('rows').map((row: any) => ( - + {row.column1} - + {row.column2} @@ -318,25 +414,27 @@ const docs: ComponentDocs = { ])}
- - Time - - - Lorem - - - Ipsum - - - Dolor - + + + Time + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( - + {row.column1} - + {row.column2} @@ -364,9 +462,10 @@ const docs: ComponentDocs = { description: ( <> - By default, a TableCell has a{' '} - width of auto, accomodating the - longest content within the column. + By default, a TableCell (and{' '} + TableHeadCell) has a width of{' '} + auto, accomodating the longest content within the + column. If the table content is larger than the available space, each column @@ -416,21 +515,23 @@ const docs: ComponentDocs = { ])}
- - Status - - - Lorem - - - Ipsum - - - Dolor - - - Actions - + + + Status + + + Lorem + + + Ipsum + + + Dolor + + + Actions + + {getState('rows').map((row: any) => ( @@ -516,21 +617,23 @@ const docs: ComponentDocs = { ])}
- - Status - - - Lorem - - - Ipsum - - - Dolor - - - Actions - + + + Status + + + Lorem + + + Ipsum + + + Dolor + + + Actions + + {getState('rows').map((row: any) => ( @@ -592,15 +695,17 @@ const docs: ComponentDocs = {
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + @@ -640,15 +745,17 @@ const docs: ComponentDocs = {
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + @@ -692,7 +799,7 @@ const docs: ComponentDocs = { The horizontal alignment of content within cells can be configured using the align prop on TableCell{' '} - components. + and TableHeadCell components. Supported alignments are left (default),{' '} @@ -724,15 +831,17 @@ const docs: ComponentDocs = { ])}
- - “left” - - - “center” - - - “right” - + + + “left” + + + “center” + + + “right” + + {getState('rows').map((row: any) => ( @@ -764,9 +873,10 @@ const docs: ComponentDocs = { description: ( <> - By default, all TableCell components are prevented - from wrapping their content. This keep rows a consistent height and - means the content can influence the column width. + By default, all TableCell and{' '} + TableHeadCell components are prevented from + wrapping their content. This keep rows a consistent height and means + the content can influence the column width. If desired, wrapping can be enabled by setting the{' '} @@ -795,15 +905,17 @@ const docs: ComponentDocs = { ])}
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( @@ -879,15 +991,17 @@ const docs: ComponentDocs = {
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( @@ -912,12 +1026,14 @@ const docs: ComponentDocs = {
- - Lorem - - - Dolor - + + + Lorem + + + Dolor + + {getState('rows').map((row: any) => ( @@ -953,15 +1069,17 @@ const docs: ComponentDocs = { ])}
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( @@ -1017,15 +1135,17 @@ const docs: ComponentDocs = {
- - Lorem - - - Ipsum - - - Dolor - + + + Lorem + + + Ipsum + + + Dolor + + {getState('rows').map((row: any) => ( diff --git a/packages/braid-design-system/src/lib/components/Table/TableCell.tsx b/packages/braid-design-system/src/lib/components/Table/TableCell.tsx index e4409e6d19b..1fec8709004 100644 --- a/packages/braid-design-system/src/lib/components/Table/TableCell.tsx +++ b/packages/braid-design-system/src/lib/components/Table/TableCell.tsx @@ -21,8 +21,7 @@ import buildDataAttributes, { import * as styles from './Table.css'; type Percentage = `${number}%`; -interface TableCellProps { - header?: boolean; +interface CellProps { children: ReactNode; hideBelow?: ResponsiveRangeProps['below']; hideAbove?: ResponsiveRangeProps['above']; @@ -31,10 +30,15 @@ interface TableCellProps { width?: 'content' | 'auto' | Percentage; minWidth?: number; maxWidth?: number; + colspan?: number; data?: DataAttributeMap; } -export const TableCell = ({ - header, +interface BaseCellProps { + header?: boolean; + scope?: 'col' | 'row'; +} +const Cell = ({ + header: isHeaderCell, children, hideAbove, hideBelow, @@ -43,29 +47,22 @@ export const TableCell = ({ width = 'auto', minWidth, maxWidth, + colspan, + scope, data, ...restProps -}: TableCellProps) => { +}: CellProps & BaseCellProps) => { const [hideOnMobile, hideOnTablet, hideOnDesktop, hideOnWide] = resolveResponsiveRangeProps({ below: hideBelow, above: hideAbove, }); - const tableHeaderContext = useContext(TableHeaderContext); const tableFooterContext = useContext(TableFooterContext); - const tableRowContext = useContext(TableRowContext); const tableContext = useContext(TableContext); - assert( - tableRowContext, - 'TableCell cannot be used outside a TableRow component', - ); - assert(tableContext, 'TableCell cannot be used outside a Table component'); - const isHeaderCell = - typeof header !== 'undefined' ? header : tableHeaderContext; const isFooterCell = tableFooterContext; const softWidth = width === 'content' ? '1%' : width; @@ -74,7 +71,8 @@ export const TableCell = ({ return ( . - * However, we don't do this for the to avoid long titles forcing - * columns to be wider than they need to be to display the data. - * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table#displaying_large_tables_in_small_spaces - */ [styles.nowrap]: !wrap, [styles.softWidth]: width && width !== 'auto', [styles.minWidth]: typeof minWidth !== 'undefined', [styles.maxWidth]: hasMaxWidth, [styles.alignYCenter]: tableContext.alignY === 'center', - [styles.borderBottom]: true, - // [styles.borderTopFooter]: isFooterCell, + [styles.bodyCellBorder]: !isHeaderCell, + [styles.footerCellBorder]: isFooterCell, + [styles.headerCellBorder]: isHeaderCell, [styles.showOnTablet]: !hideOnTablet && hideOnMobile, [styles.showOnDesktop]: !hideOnDesktop && (hideOnTablet || hideOnMobile), @@ -132,3 +122,34 @@ export const TableCell = ({ ); }; + +export const TableCell = (props: CellProps) => { + const tableHeaderContext = useContext(TableHeaderContext); + const tableRowContext = useContext(TableRowContext); + + assert( + !tableHeaderContext, + 'TableCell cannot be used inside a TableHeader component. Please use TableHeadCell instead.', + ); + + assert( + tableRowContext, + 'TableCell cannot be used outside a TableRow component', + ); + + return ; +}; + +export const TableHeadCell = (props: CellProps) => { + const tableHeaderContext = useContext(TableHeaderContext); + const tableRowContext = useContext(TableRowContext); + + assert( + tableRowContext, + 'TableHeadCell cannot be used outside a TableRow component', + ); + + return ( + + ); +}; diff --git a/packages/braid-design-system/src/lib/components/Table/TableHeader.tsx b/packages/braid-design-system/src/lib/components/Table/TableHeader.tsx index beb4056d535..92772318354 100644 --- a/packages/braid-design-system/src/lib/components/Table/TableHeader.tsx +++ b/packages/braid-design-system/src/lib/components/Table/TableHeader.tsx @@ -1,13 +1,12 @@ import assert from 'assert'; import { useContext, type ReactNode } from 'react'; import { Box } from '../Box/Box'; -import { TableRow } from './TableRow'; import { TableContext, TableHeaderContext } from './TableContext'; import buildDataAttributes, { type DataAttributeMap, } from '../private/buildDataAttributes'; -import * as styles from './Table.css'; +// import * as styles from './Table.css'; interface TableHeaderProps { children: ReactNode; @@ -29,13 +28,13 @@ TableHeaderProps) => { - {children} + {children} ); diff --git a/packages/braid-design-system/src/lib/components/Table/TableRow.tsx b/packages/braid-design-system/src/lib/components/Table/TableRow.tsx index 0c46ec890f5..1eca471828b 100644 --- a/packages/braid-design-system/src/lib/components/Table/TableRow.tsx +++ b/packages/braid-design-system/src/lib/components/Table/TableRow.tsx @@ -27,14 +27,12 @@ export const TableRow = ({ children, data, ...restProps }: TableRowProps) => { assert( !tableRowContext, - tableHeaderContext - ? 'TableRow is already provided by the TableHeader component' - : 'TableRow cannot be nested instead another TableRow', + 'TableRow cannot be nested instead another TableRow', ); assert( tableBodyContext || tableHeaderContext || tableFooterContext, - 'TableRow must be used within a TableBody component', + 'TableRow must be used within a table section, e.g. TableHeader, TableBody or TableFooter component', ); return ( diff --git a/packages/braid-design-system/src/lib/components/index.ts b/packages/braid-design-system/src/lib/components/index.ts index 550b99d9915..20f8754e3b8 100644 --- a/packages/braid-design-system/src/lib/components/index.ts +++ b/packages/braid-design-system/src/lib/components/index.ts @@ -73,6 +73,7 @@ export { Table } from './Table/Table'; export { TableBody } from './Table/TableBody'; export { TableCell } from './Table/TableCell'; export { TableFooter } from './Table/TableFooter'; +export { TableHeadCell } from './Table/TableCell'; export { TableHeader } from './Table/TableHeader'; export { TableRow } from './Table/TableRow'; export { Tab } from './Tabs/Tab'; diff --git a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap index 5e64556fe86..8a71bd88442 100644 --- a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap +++ b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap @@ -7873,7 +7873,6 @@ exports[`Table 1`] = ` | "center" | "top" children: ReactNode - columnWidths?: (string | number)[] data?: DataAttributeMap fullBleed?: boolean label: string @@ -7900,8 +7899,8 @@ exports[`TableCell 1`] = ` | "left" | "right" children: ReactNode + colspan?: number data?: DataAttributeMap - header?: boolean hideAbove?: | "desktop" | "mobile" @@ -7910,11 +7909,52 @@ exports[`TableCell 1`] = ` | "desktop" | "tablet" | "wide" - nowrap?: boolean + maxWidth?: number + minWidth?: number width?: | "auto" | "content" - | number + | \`\${number}%\` + wrap?: boolean +}, +} +`; + +exports[`TableFooter 1`] = ` +{ + exportType: component, + props: { + children: ReactNode + data?: DataAttributeMap +}, +} +`; + +exports[`TableHeadCell 1`] = ` +{ + exportType: component, + props: { + align?: + | "center" + | "left" + | "right" + children: ReactNode + colspan?: number + data?: DataAttributeMap + hideAbove?: + | "desktop" + | "mobile" + | "tablet" + hideBelow?: + | "desktop" + | "tablet" + | "wide" + maxWidth?: number + minWidth?: number + width?: + | "auto" + | "content" + | \`\${number}%\` wrap?: boolean }, } diff --git a/site/src/undocumentedExports.json b/site/src/undocumentedExports.json index 93b2e2c5d36..05f293b5dc9 100644 --- a/site/src/undocumentedExports.json +++ b/site/src/undocumentedExports.json @@ -15,6 +15,7 @@ "TableBody", "TableCell", "TableFooter", + "TableHeadCell", "TableHeader", "TableRow", "Tab",