diff --git a/src/components/table/FillerRows.tsx b/src/components/table/FillerRows.tsx
index 613c2278..5d33127b 100644
--- a/src/components/table/FillerRows.tsx
+++ b/src/components/table/FillerRows.tsx
@@ -1,7 +1,9 @@
import type { Row } from '@tanstack/react-table'
import type { VirtualItem } from '@tanstack/react-virtual'
-import { type TableFillLevel, Td, Tr } from './Table'
+import { type TableFillLevel } from './Table'
+import { Td } from './Td'
+import { Tr } from './Tr'
function FillerRow({
columns,
diff --git a/src/components/table/SortIndicator.tsx b/src/components/table/SortIndicator.tsx
new file mode 100644
index 00000000..62abc78e
--- /dev/null
+++ b/src/components/table/SortIndicator.tsx
@@ -0,0 +1,28 @@
+import type { SortDirection } from '@tanstack/react-table'
+
+import ArrowRightIcon from '../icons/ArrowRightIcon'
+
+export function SortIndicator({
+ direction = false,
+}: {
+ direction: false | SortDirection
+}) {
+ switch (direction) {
+ case 'asc':
+ return (
+
+ )
+ case 'desc':
+ return (
+
+ )
+ case false:
+ return null
+ }
+}
diff --git a/src/components/table/T.tsx b/src/components/table/T.tsx
new file mode 100644
index 00000000..0219630e
--- /dev/null
+++ b/src/components/table/T.tsx
@@ -0,0 +1,13 @@
+import styled from 'styled-components'
+
+export const T = styled.table<{ $gridTemplateColumns: string }>(
+ ({ theme, $gridTemplateColumns }) => ({
+ gridTemplateColumns: $gridTemplateColumns,
+ borderSpacing: 0,
+ display: 'grid',
+ borderCollapse: 'collapse',
+ minWidth: '100%',
+ width: '100%',
+ ...theme.partials.text.body2LooseLineHeight,
+ })
+)
diff --git a/src/components/table/Table.tsx b/src/components/table/Table.tsx
index 041ebcf1..c6c4c67b 100644
--- a/src/components/table/Table.tsx
+++ b/src/components/table/Table.tsx
@@ -1,7 +1,5 @@
import { Div, type DivProps } from 'honorable'
import {
- type CSSProperties,
- type ComponentProps,
Fragment,
type MouseEvent,
type Ref,
@@ -16,7 +14,6 @@ import type {
ColumnDef,
FilterFn,
Row,
- SortDirection,
TableOptions,
} from '@tanstack/react-table'
import {
@@ -30,28 +27,25 @@ import {
import { rankItem } from '@tanstack/match-sorter-utils'
import type { VirtualItem } from '@tanstack/react-virtual'
import { useVirtualizer } from '@tanstack/react-virtual'
-import styled, { useTheme } from 'styled-components'
+import { useTheme } from 'styled-components'
import { isEmpty, isNil } from 'lodash-es'
import { type FillLevel, InfoOutlineIcon, Tooltip } from '../../index'
import Button from '../Button'
import CaretUpIcon from '../icons/CaretUpIcon'
-import ArrowRightIcon from '../icons/ArrowRightIcon'
-import { FillLevelProvider } from '../contexts/FillLevelContext'
import EmptyState, { type EmptyStateProps } from '../EmptyState'
import { Spinner } from '../Spinner'
-import {
- tableCellColor,
- tableCellHoverColor,
- tableFillLevelToBg,
- tableFillLevelToBorder,
- tableFillLevelToBorderColor,
- tableFillLevelToHighlightedCellBg,
- tableHeaderColor,
-} from './colors'
+import { tableFillLevelToBg, tableFillLevelToBorderColor } from './colors'
import { FillerRows } from './FillerRows'
import { useIsScrolling, useOnVirtualSliceChange } from './hooks'
+import { SortIndicator } from './SortIndicator'
+import { T } from './T'
+import { Tbody } from './Tbody'
+import { Td, TdExpand, TdLoading } from './Td'
+import { Th } from './Th'
+import { Thead } from './Thead'
+import { Tr } from './Tr'
export type TableProps = DivProps & {
data: any[]
@@ -104,240 +98,6 @@ function getGridTemplateCols(columnDefs: ColumnDef[] = []): string {
.join(' ')
}
-const T = styled.table<{ $gridTemplateColumns: string }>(
- ({ theme, $gridTemplateColumns }) => ({
- gridTemplateColumns: $gridTemplateColumns,
- borderSpacing: 0,
- display: 'grid',
- borderCollapse: 'collapse',
- minWidth: '100%',
- width: '100%',
- ...theme.partials.text.body2LooseLineHeight,
- })
-)
-
-const TheadUnstyled = forwardRef<
- HTMLTableSectionElement,
- ComponentProps<'thead'>
->((props, ref) => (
-
-
-
-))
-
-const Thead = styled(TheadUnstyled)(() => ({
- display: 'contents',
- position: 'sticky',
- top: 0,
- zIndex: 3,
-}))
-
-const TbodyUnstyled = forwardRef<
- HTMLTableSectionElement,
- ComponentProps<'tbody'>
->((props, ref) => (
-
-
-
-))
-
-const Tbody = styled(TbodyUnstyled)(() => ({
- display: 'contents',
-}))
-
-export const Tr = styled.tr<{
- $fillLevel: TableFillLevel
- $highlighted?: boolean
- $selected?: boolean
- $selectable?: boolean
- $clickable?: boolean
- $raised?: boolean
-}>(
- ({
- theme,
- $clickable: clickable = false,
- $raised: raised = false,
- $selectable: selectable = false,
- $selected: selected = false,
- $highlighted: highlighted = false,
- $fillLevel: fillLevel,
- }) => ({
- display: 'contents',
- backgroundColor:
- theme.colors[
- tableCellColor(fillLevel, highlighted, raised, selectable, selected)
- ],
-
- ...(clickable && {
- cursor: 'pointer',
-
- // highlight when hovered, but don't highlight if a child button is hovered
- '&:not(:has(button:hover)):hover': {
- backgroundColor:
- theme.colors[tableCellHoverColor(fillLevel, selectable, selected)],
- },
- }),
- })
-)
-
-const Th = styled.th<{
- $fillLevel: TableFillLevel
- $stickyColumn: boolean
- $highlight?: boolean
- $cursor?: CSSProperties['cursor']
- $hideHeader?: boolean
-}>(
- ({
- theme,
- $fillLevel: fillLevel,
- $stickyColumn: stickyColumn,
- $highlight: highlight,
- $cursor: cursor,
- $hideHeader: hideHeader,
- }) => ({
- padding: 0,
- position: 'sticky',
- top: 0,
- zIndex: 4,
- '.thOuterWrap': {
- alignItems: 'center',
- display: hideHeader ? 'none' : 'flex',
- position: 'relative',
- backgroundColor: theme.colors[tableHeaderColor(fillLevel, highlight)],
- zIndex: 4,
- borderBottom: theme.borders[tableFillLevelToBorder[fillLevel]],
- color: theme.colors.text,
- height: 48,
- minHeight: 48,
- whiteSpace: 'nowrap',
- padding: '0 12px',
- textAlign: 'left',
- ...(cursor ? { cursor } : {}),
- '.thSortIndicatorWrap': {
- display: 'flex',
- gap: theme.spacing.xsmall,
- },
- },
- '&:last-child': {
- /* Hackery to hide unpredictable visible gap between columns */
- zIndex: 3,
- '&::before': {
- content: '""',
- position: 'absolute',
- top: 0,
- right: 0,
- bottom: 0,
- width: 10000,
- backgroundColor: theme.colors[tableHeaderColor(fillLevel, false)],
- borderBottom: hideHeader
- ? 'none'
- : theme.borders[tableFillLevelToBorder[fillLevel]],
- },
- },
- '&:first-child': {
- ...(stickyColumn
- ? {
- backgroundColor: 'inherit',
- position: 'sticky',
- left: 0,
- zIndex: 5,
- '.thOuterWrap': {
- boxShadow: theme.boxShadows.slight,
- zIndex: 5,
- },
- }
- : {}),
- },
- })
-)
-
-// TODO: Set vertical align to top for tall cells (~3 lines of text or more). See ENG-683.
-export const Td = styled.td<{
- $fillLevel: TableFillLevel
- $firstRow?: boolean
- $loose?: boolean
- $padCells?: boolean
- $stickyColumn: boolean
- $highlight?: boolean
- $truncateColumn: boolean
- $center?: boolean
-}>(
- ({
- theme,
- $fillLevel: fillLevel,
- $firstRow: firstRow,
- $loose: loose,
- $padCells: padCells,
- $stickyColumn: stickyColumn,
- $highlight: highlight,
- $truncateColumn: truncateColumn = false,
- $center: center,
- }) => ({
- ...theme.partials.text.body2LooseLineHeight,
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: center ? 'center' : 'flex-start',
- height: 'auto',
- minHeight: 52,
-
- backgroundColor: highlight
- ? theme.colors[tableFillLevelToHighlightedCellBg[fillLevel]]
- : 'inherit',
- borderTop: firstRow ? '' : theme.borders[tableFillLevelToBorder[fillLevel]],
- color: theme.colors['text-light'],
-
- padding: padCells ? (loose ? '16px 12px' : '8px 12px') : 0,
- '&:first-child': stickyColumn
- ? {
- boxShadow: theme.boxShadows.slight,
- position: 'sticky',
- left: 0,
- zIndex: 1,
- }
- : {},
- ...(truncateColumn
- ? {
- '*': {
- width: '100%',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- },
- }
- : {}),
- })
-)
-
-const TdExpand = styled.td(({ theme }) => ({
- '&:last-child': {
- gridColumn: '2 / -1',
- },
- backgroundColor: 'inherit',
- color: theme.colors['text-light'],
- height: 'auto',
- minHeight: 52,
- padding: '16px 12px',
-}))
-
-const TdLoading = styled(Td)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- gridColumn: '1 / -1',
- textAlign: 'center',
- gap: theme.spacing.xsmall,
- color: theme.colors['text-xlight'],
- minHeight: theme.spacing.large * 2 + theme.spacing.xlarge,
-}))
-
function isRow(row: Row | VirtualItem): row is Row {
return typeof (row as Row).getVisibleCells === 'function'
}
@@ -362,31 +122,6 @@ const defaultGlobalFilterFn: FilterFn = (
return itemRank.passed
}
-const sortDirToIcon = {
- asc: (
-
- ),
- desc: (
-
- ),
-}
-
-function SortIndicator({
- direction = false,
-}: {
- direction: false | SortDirection
-}) {
- if (!direction) return null
-
- return sortDirToIcon[direction]
-}
-
function TableRef(
{
data,
diff --git a/src/components/table/Tbody.tsx b/src/components/table/Tbody.tsx
new file mode 100644
index 00000000..68080808
--- /dev/null
+++ b/src/components/table/Tbody.tsx
@@ -0,0 +1,20 @@
+import { type ComponentProps, forwardRef } from 'react'
+import styled from 'styled-components'
+
+import { FillLevelProvider } from '../contexts/FillLevelContext'
+
+const TbodyUnstyled = forwardRef<
+ HTMLTableSectionElement,
+ ComponentProps<'tbody'>
+>((props, ref) => (
+
+
+
+))
+
+export const Tbody = styled(TbodyUnstyled)(() => ({
+ display: 'contents',
+}))
diff --git a/src/components/table/Td.tsx b/src/components/table/Td.tsx
new file mode 100644
index 00000000..d791cfb5
--- /dev/null
+++ b/src/components/table/Td.tsx
@@ -0,0 +1,87 @@
+import styled from 'styled-components'
+
+import {
+ tableFillLevelToBorder,
+ tableFillLevelToHighlightedCellBg,
+} from './colors'
+import { type TableFillLevel } from './Table'
+
+export const Td = styled.td<{
+ $fillLevel: TableFillLevel
+ $firstRow?: boolean
+ $loose?: boolean
+ $padCells?: boolean
+ $stickyColumn: boolean
+ $highlight?: boolean
+ $truncateColumn: boolean
+ $center?: boolean
+}>(
+ ({
+ theme,
+ $fillLevel: fillLevel,
+ $firstRow: firstRow,
+ $loose: loose,
+ $padCells: padCells,
+ $stickyColumn: stickyColumn,
+ $highlight: highlight,
+ $truncateColumn: truncateColumn = false,
+ $center: center,
+ }) => ({
+ ...theme.partials.text.body2LooseLineHeight,
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: center ? 'center' : 'flex-start',
+ height: 'auto',
+ minHeight: 52,
+
+ backgroundColor: highlight
+ ? theme.colors[tableFillLevelToHighlightedCellBg[fillLevel]]
+ : 'inherit',
+ borderTop: firstRow ? '' : theme.borders[tableFillLevelToBorder[fillLevel]],
+ color: theme.colors['text-light'],
+
+ padding: padCells ? (loose ? '16px 12px' : '8px 12px') : 0,
+ '&:first-child': stickyColumn
+ ? {
+ boxShadow: theme.boxShadows.slight,
+ position: 'sticky',
+ left: 0,
+ zIndex: 1,
+ }
+ : {},
+ ...(truncateColumn
+ ? {
+ '*': {
+ width: '100%',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
+ }
+ : {}),
+ })
+)
+
+export const TdExpand = styled.td(({ theme }) => ({
+ '&:last-child': {
+ gridColumn: '2 / -1',
+ },
+ backgroundColor: 'inherit',
+ color: theme.colors['text-light'],
+ height: 'auto',
+ minHeight: 52,
+ padding: '16px 12px',
+}))
+
+export const TdLoading = styled(Td)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gridColumn: '1 / -1',
+ textAlign: 'center',
+ gap: theme.spacing.xsmall,
+ color: theme.colors['text-xlight'],
+ minHeight: theme.spacing.large * 2 + theme.spacing.xlarge,
+}))
diff --git a/src/components/table/Th.tsx b/src/components/table/Th.tsx
new file mode 100644
index 00000000..a8b12af7
--- /dev/null
+++ b/src/components/table/Th.tsx
@@ -0,0 +1,76 @@
+import type { CSSProperties } from 'react'
+import styled from 'styled-components'
+
+import { tableFillLevelToBorder, tableHeaderColor } from './colors'
+import { type TableFillLevel } from './Table'
+
+export const Th = styled.th<{
+ $fillLevel: TableFillLevel
+ $stickyColumn: boolean
+ $highlight?: boolean
+ $cursor?: CSSProperties['cursor']
+ $hideHeader?: boolean
+}>(
+ ({
+ theme,
+ $fillLevel: fillLevel,
+ $stickyColumn: stickyColumn,
+ $highlight: highlight,
+ $cursor: cursor,
+ $hideHeader: hideHeader,
+ }) => ({
+ padding: 0,
+ position: 'sticky',
+ top: 0,
+ zIndex: 4,
+ '.thOuterWrap': {
+ alignItems: 'center',
+ display: hideHeader ? 'none' : 'flex',
+ position: 'relative',
+ backgroundColor: theme.colors[tableHeaderColor(fillLevel, highlight)],
+ zIndex: 4,
+ borderBottom: theme.borders[tableFillLevelToBorder[fillLevel]],
+ color: theme.colors.text,
+ height: 48,
+ minHeight: 48,
+ whiteSpace: 'nowrap',
+ padding: '0 12px',
+ textAlign: 'left',
+ ...(cursor ? { cursor } : {}),
+ '.thSortIndicatorWrap': {
+ display: 'flex',
+ gap: theme.spacing.xsmall,
+ },
+ },
+ '&:last-child': {
+ /* Hackery to hide unpredictable visible gap between columns */
+ zIndex: 3,
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ width: 10000,
+ backgroundColor: theme.colors[tableHeaderColor(fillLevel, false)],
+ borderBottom: hideHeader
+ ? 'none'
+ : theme.borders[tableFillLevelToBorder[fillLevel]],
+ },
+ },
+ '&:first-child': {
+ ...(stickyColumn
+ ? {
+ backgroundColor: 'inherit',
+ position: 'sticky',
+ left: 0,
+ zIndex: 5,
+ '.thOuterWrap': {
+ boxShadow: theme.boxShadows.slight,
+ zIndex: 5,
+ },
+ }
+ : {}),
+ },
+ })
+)
diff --git a/src/components/table/Thead.tsx b/src/components/table/Thead.tsx
new file mode 100644
index 00000000..94b41bf5
--- /dev/null
+++ b/src/components/table/Thead.tsx
@@ -0,0 +1,23 @@
+import { type ComponentProps, forwardRef } from 'react'
+import styled from 'styled-components'
+
+import { FillLevelProvider } from '../contexts/FillLevelContext'
+
+const TheadUnstyled = forwardRef<
+ HTMLTableSectionElement,
+ ComponentProps<'thead'>
+>((props, ref) => (
+
+
+
+))
+
+export const Thead = styled(TheadUnstyled)(() => ({
+ display: 'contents',
+ position: 'sticky',
+ top: 0,
+ zIndex: 3,
+}))
diff --git a/src/components/table/Tr.tsx b/src/components/table/Tr.tsx
new file mode 100644
index 00000000..b93d9718
--- /dev/null
+++ b/src/components/table/Tr.tsx
@@ -0,0 +1,39 @@
+import styled from 'styled-components'
+
+import { tableCellColor, tableCellHoverColor } from './colors'
+import { type TableFillLevel } from './Table'
+
+export const Tr = styled.tr<{
+ $fillLevel: TableFillLevel
+ $highlighted?: boolean
+ $selected?: boolean
+ $selectable?: boolean
+ $clickable?: boolean
+ $raised?: boolean
+}>(
+ ({
+ theme,
+ $clickable: clickable = false,
+ $raised: raised = false,
+ $selectable: selectable = false,
+ $selected: selected = false,
+ $highlighted: highlighted = false,
+ $fillLevel: fillLevel,
+ }) => ({
+ display: 'contents',
+ backgroundColor:
+ theme.colors[
+ tableCellColor(fillLevel, highlighted, raised, selectable, selected)
+ ],
+
+ ...(clickable && {
+ cursor: 'pointer',
+
+ // highlight when hovered, but don't highlight if a child button is hovered
+ '&:not(:has(button:hover)):hover': {
+ backgroundColor:
+ theme.colors[tableCellHoverColor(fillLevel, selectable, selected)],
+ },
+ }),
+ })
+)