@spaced-out/ui-design-system
Version:
Sense UI components library
153 lines (140 loc) • 4.38 kB
Flow
// @flow strict
import * as React from 'react';
import classify from '../../utils/classify';
import type {ClassNameComponent} from '../../utils/makeClassNameComponent';
import {makeClassNameComponent} from '../../utils/makeClassNameComponent';
import {Checkbox} from '../Checkbox';
import {CircularLoader} from '../CircularLoader';
import {BodyLarge, TEXT_COLORS} from '../Text';
import {PaddedContentCell, SingleCell} from './Cell';
import type {GenericHeaderItems} from './DefaultTableHeader';
import type {GenericObject} from './Table';
import css from './Table.module.css';
type ClassNames = $ReadOnly<{tableRow?: string, checkbox?: string}>;
// When using a custom Row prop, you need to create a component that looks like
// MyRow = (props: TableRowProps<Entries, Extras>): React.Node => {...}
// otherwise flow will complain.
// Note that b/c extras is often optional, you will need to explicitly include
// `invariant(extras, 'extras exists');` in order to pull values out of
// extras (flow will remind you that it is of type `U | void`)
export type TableRowProps<T, U> = {
data: T,
extras?: U,
sortedKeys?: string[],
headers?: GenericHeaderItems<T, U>,
selected?: boolean,
disabled?: boolean,
};
export type TableRow<T, U> = React.ComponentType<TableRowProps<T, U>>;
export const BasicRow: ClassNameComponent<'tr'> = makeClassNameComponent(
css.defaultRow,
'tr',
);
type EmptyRowProps = {
emptyText?: React.Node,
isLoading?: boolean,
headersLength: number,
customLoader?: React.Node,
};
export const EmptyRow = ({
isLoading,
emptyText,
headersLength = 0,
customLoader,
}: EmptyRowProps): React.Element<'tr'> => (
<tr>
<td colSpan={headersLength}>
<div className={css.emptyRow}>
{isLoading ? (
customLoader ? (
customLoader
) : (
<div className={css.defaultLoader}>
{' '}
<CircularLoader colorToken="colorFillPrimary" size="large" />
</div>
)
) : (
emptyText || (
<BodyLarge
color={TEXT_COLORS.secondary}
className={css.defaultEmptyText}
>
Nothing to display here.
</BodyLarge>
)
)}
</div>
</td>
</tr>
);
// This is a fallback row we use to render a table when
// initially stubbing out a design, the idea is you just avoid
// passing in a Row component and instead let this render out
// all the fields in the header in the short term
//
// Using the default row has the benefit that mismatches between
// header and entries _will_ error out even though there are the
// suppressions below
export function DefaultRow<T: GenericObject, U: GenericObject>({
data,
extras,
headers,
selected,
onSelect,
classNames,
disabled,
}: {
data: T,
extras?: U,
headers: GenericHeaderItems<T, U>,
selected?: boolean,
// value dependent on checkbox checked value
onSelect?: ({value: string, checked: boolean}) => mixed,
classNames?: ClassNames,
disabled?: boolean,
}): React.Node {
return (
<BasicRow
className={classify(
selected ? css.defaultSelectedBodyRow : css.defaultBodyRow,
classNames?.tableRow,
)}
>
{selected != null && (
<PaddedContentCell
className={classify(css.checkbox, classNames?.checkbox)}
>
<Checkbox
checked={selected ? true : false}
onChange={onSelect}
disabled={disabled}
ariaLabel="Select row"
/>
</PaddedContentCell>
)}
{headers.map((item, index) => {
const {key, render: Renderer, className: cellClassName, sticky} = item;
const value = data[key];
return Renderer ? (
<Renderer
// eslint-disable-next-line react/no-array-index-key
key={index}
data={data}
extras={extras}
selected={selected}
disabled={disabled}
className={classify({[css.stickyCell]: sticky})}
/>
) : (
<SingleCell
// eslint-disable-next-line react/no-array-index-key
key={index}
title={String(value)}
className={classify(cellClassName, {[css.stickyCell]: sticky})}
/>
);
})}
</BasicRow>
);
}