Files
dydx-v4-web/src/components/Table.tsx
T

1004 lines
27 KiB
TypeScript

import React, { Fragment, Key, useCallback, useEffect, useMemo, useState } from 'react';
import styled, { css, type AnyStyledComponent } from 'styled-components';
import {
useTable,
useTableCell,
useTableColumnHeader,
useTableRow,
useTableHeaderRow,
useTableRowGroup,
useTableSelectAllCheckbox,
useTableSelectionCheckbox,
mergeProps,
useFocusRing,
useCollator,
} from 'react-aria';
import { type ColumnSize, type TableCollection } from '@react-types/table';
import { type GridNode } from '@react-types/grid';
import type { Node, SortDescriptor, SortDirection, CollectionChildren } from '@react-types/shared';
import {
Cell,
// CollectionBuilderContext,
Column,
Row,
TableBody,
TableHeader,
type TableState,
useTableState,
} from '@react-stately/table';
import { useAsyncList } from 'react-stately';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { CaretIcon } from '@/icons';
import { STRING_KEYS } from '@/constants/localization';
import { MustBigNumber } from '@/lib/numbers';
import { Icon, IconName } from './Icon';
import { Tag } from './Tag';
import { Button } from './Button';
export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader';
export { MarketTableCell } from './Table/MarketTableCell';
export type CustomRowConfig = {
key: string;
slotCustomRow: (..._: Parameters<typeof TableRow>) => React.ReactNode;
};
export type TableItem<TableRowData> = {
value: TableRowData;
slotBefore?: () => React.ReactNode;
label: string;
tag?: React.ReactNode;
slotAfter?: () => React.ReactNode;
onSelect?: (key: TableRowData) => void;
};
export type ColumnDef<TableRowData extends object> = {
columnKey: string;
label: React.ReactNode;
tag?: React.ReactNode;
colspan?: number;
childColumns?: ColumnDef<TableRowData>[];
getCellValue: (row: TableRowData) => string | number;
allowsSorting?: boolean; // Default true
allowsResizing?: boolean;
renderCell: (row: TableRowData) => React.ReactNode;
isActionable?: boolean;
hideOnBreakpoint?: MediaQueryKeys;
width?: ColumnSize;
};
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
label?: string;
columns: ColumnDef<TableRowData>[];
data: TableRowData[];
getRowKey: (rowData: TableRowData, rowIndex?: number) => TableRowKey;
getRowAttributes?: (rowData: TableRowData, rowIndex?: number) => Record<string, any>;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
defaultSortDescriptor?: SortDescriptor;
selectionMode?: 'multiple' | 'single';
selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode;
viewMoreConfig?: {
initialNumRowsToShow: number;
numRowsPerPage?: number;
};
// collection: TableCollection<string>;
// children: React.ReactNode;
};
type StyleProps = {
hideHeader?: boolean;
withGradientCardRows?: boolean;
withFocusStickyRows?: boolean;
withOuterBorder?: boolean;
withInnerBorders?: boolean;
withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean;
className?: string;
style?: React.CSSProperties;
};
export type TableConfig<TableRowData> = TableItem<TableRowData>[];
export const Table = <TableRowData extends object, TableRowKey extends Key>({
label = '',
columns,
data = [],
getRowKey,
getRowAttributes,
onRowAction,
defaultSortDescriptor,
selectionMode = 'single',
selectionBehavior = 'toggle',
slotEmpty,
viewMoreConfig,
// shouldRowRender,
// collection,
// children,
hideHeader = false,
withGradientCardRows = false,
withFocusStickyRows = false,
withOuterBorder = false,
withInnerBorders = false,
withScrollSnapColumns = false,
withScrollSnapRows = false,
className,
style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(viewMoreConfig?.initialNumRowsToShow);
const enableViewMore = viewMoreConfig !== undefined;
const onViewMoreClick = () => {
if (!viewMoreConfig) return;
const { numRowsPerPage } = viewMoreConfig;
if (numRowsPerPage) {
setNumRowsToShow((prev) => (prev ?? 0) + numRowsPerPage);
} else {
setNumRowsToShow(data.length);
}
};
const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter(
({ hideOnBreakpoint }) => !hideOnBreakpoint || !currentBreakpoints[hideOnBreakpoint as string]
);
const collator = useCollator();
const sortFn = (
a: TableRowData,
b: TableRowData,
sortColumn?: Key,
sortDirection?: SortDirection
) => {
if (!sortColumn) return 0;
const column = columns.find((column) => column.columnKey === sortColumn);
const first = column?.getCellValue(a);
const second = column?.getCellValue(b);
return (
// Compare the items by the sorted column
(isNaN(first as number)
? // String
collator.compare(first as string, second as string)
: // Number
MustBigNumber(first).comparedTo(MustBigNumber(second))) *
// Flip the direction if descending order is specified.
(sortDirection === 'descending' ? -1 : 1)
);
};
const list = useAsyncList<TableRowData>({
getKey: getRowKey,
load: async ({ sortDescriptor }) => ({
items: sortDescriptor?.column
? data.sort((a, b) => sortFn(a, b, sortDescriptor?.column, sortDescriptor?.direction))
: data,
}),
initialSortDescriptor: defaultSortDescriptor,
sort: async ({ items, sortDescriptor }) => ({
items: items.sort((a, b) => sortFn(a, b, sortDescriptor?.column, sortDescriptor?.direction)),
}),
});
useEffect(() => list.reload(), [data]);
const isEmpty = data.length === 0;
return (
<Styled.TableWrapper
className={className}
style={style}
isEmpty={isEmpty}
withGradientCardRows={withGradientCardRows}
withOuterBorder={withOuterBorder}
>
{!isEmpty ? (
<TableRoot
aria-label={label}
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
selectedKeys={selectedKeys}
setSelectedKeys={setSelectedKeys}
selectionMode={selectionMode}
selectionBehavior={selectionBehavior}
getRowAttributes={getRowAttributes}
onRowAction={
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
numColumns={shownColumns.length}
onViewMoreClick={
enableViewMore && numRowsToShow! < data.length ? onViewMoreClick : undefined
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
withFocusStickyRows={withFocusStickyRows}
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
withScrollSnapColumns={withScrollSnapColumns}
withScrollSnapRows={withScrollSnapRows}
>
<TableHeader columns={shownColumns}>
{(column) => (
<Column
key={column.columnKey}
childColumns={column.childColumns}
allowsSorting={column.allowsSorting ?? true}
allowsResizing={column.allowsResizing}
width={column.width}
>
{column.label}
{column.tag && <Tag>{column.tag}</Tag>}
</Column>
)}
</TableHeader>
<TableBody items={enableViewMore ? list.items.slice(0, numRowsToShow) : list.items}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
<Cell key={`${getRowKey(item)}-${columnKey}`}>
{columns.find((column) => column.columnKey === columnKey)?.renderCell?.(item)}
</Cell>
)}
</Row>
)}
</TableBody>
</TableRoot>
) : (
<Styled.Empty withOuterBorder={withOuterBorder}>{slotEmpty}</Styled.Empty>
)}
</Styled.TableWrapper>
);
};
const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey extends Key>(props: {
'aria-label'?: string;
sortDescriptor?: SortDescriptor;
onSortChange?: (descriptor: SortDescriptor) => void;
selectionMode: 'multiple' | 'single';
selectionBehavior: 'replace' | 'toggle';
selectedKeys: Set<TableRowKey>;
setSelectedKeys: (selectedKeys: Set<TableRowKey>) => void;
getRowAttributes?: (
rowData: TableRowData,
rowIndex?: number
) => Record<string, string | number | Record<string, string | number>>;
onRowAction?: (key: TableRowKey) => void;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
children: CollectionChildren<TableRowData>;
numColumns: number;
onViewMoreClick?: () => void;
hideHeader?: boolean;
withGradientCardRows?: boolean;
withFocusStickyRows?: boolean;
withOuterBorder?: boolean;
withInnerBorders?: boolean;
withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean;
}) => {
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
const state = useTableState<TableRowData>({
...props,
showSelectionCheckboxes: selectionMode === 'multiple' && selectionBehavior !== 'replace',
});
const ref = React.useRef<HTMLTableElement>(null);
const { collection } = state;
const { gridProps } = useTable(
{
'aria-label': props['aria-label'],
onRowAction: props.onRowAction as (key: Key) => void,
},
state,
ref
);
return (
<Styled.Table
ref={ref}
{...gridProps}
hideHeader={props.hideHeader}
withGradientCardRows={props.withGradientCardRows}
withOuterBorder={props.withOuterBorder}
withInnerBorders={props.withInnerBorders}
>
<TableHeadRowGroup
hidden={props.hideHeader}
withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders}
>
{collection.headerRows.map((headerRow) => (
<TableHeaderRow
key={headerRow.key}
item={headerRow}
state={state}
withScrollSnapRows={props.withScrollSnapRows}
>
{/* {Array.from(collection.getChildren!(headerRow.key), (column) => */}
{[...headerRow.childNodes].map(
(column) => (
// column.props.isSelectionCell ? (
// <TableSelectAllCell key={column.key} column={column} state={state} />
// ) : (
<TableColumnHeader
key={column.key}
column={column}
state={state}
withScrollSnapColumns={props.withScrollSnapColumns}
/>
)
// )
)}
</TableHeaderRow>
))}
</TableHeadRowGroup>
<TableBodyRowGroup
withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders}
withOuterBorder={props.withOuterBorder}
>
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
{[...collection.body.childNodes].map((row) =>
(row.value as CustomRowConfig)?.slotCustomRow ? (
(row.value as CustomRowConfig).slotCustomRow({
item: row,
state,
...props.getRowAttributes?.(row.value!),
withGradientCardRows: props.withGradientCardRows,
withFocusStickyRows: props.withFocusStickyRows,
withScrollSnapRows: props.withScrollSnapRows,
children: null,
})
) : (
<TableRow
key={row.key}
item={row}
state={state}
// shouldRowRender={props.shouldRowRender}
{...props.getRowAttributes?.(row.value)}
withGradientCardRows={props.withGradientCardRows}
withFocusStickyRows={props.withFocusStickyRows}
withScrollSnapRows={props.withScrollSnapRows}
>
{/* {Array.from(collection.getChildren!(row.key), (cell) => */}
{[...row.childNodes].map(
(cell) => (
// cell.props.isSelectionCell ? (
// <TableCheckboxCell key={cell.key} cell={cell} state={state} />
// ) : (
<TableCell
key={cell.key}
cell={cell}
state={state}
isActionable={
((cell as GridNode<TableRowData>).column?.value as ColumnDef<TableRowData>)
?.isActionable
}
/>
)
// )
)}
</TableRow>
)
)}
{onViewMoreClick ? (
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
) : undefined}
</TableBodyRowGroup>
</Styled.Table>
);
};
const TableHeadRowGroup = ({
children,
hidden,
withGradientCardRows,
withInnerBorders,
}: { children: React.ReactNode } & {
hidden?: boolean;
withGradientCardRows?: boolean;
withInnerBorders?: boolean;
}) => {
const { rowGroupProps } = useTableRowGroup();
return (
<Styled.Thead
{...rowGroupProps}
hidden={hidden}
withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders}
>
{children}
</Styled.Thead>
);
};
const TableBodyRowGroup = ({
children,
withGradientCardRows,
withInnerBorders,
withOuterBorder,
}: { children: React.ReactNode } & StyleProps) => {
const { rowGroupProps } = useTableRowGroup();
return (
<Styled.Tbody
{...rowGroupProps}
withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders}
withOuterBorder={withOuterBorder}
>
{children}
</Styled.Tbody>
);
};
const TableHeaderRow = <TableRowData extends object>({
item,
state,
children,
withScrollSnapRows,
}: {
item: TableCollection<TableRowData>['headerRows'][number];
state: TableState<TableRowData>;
children: React.ReactNode;
withScrollSnapRows?: boolean;
}) => {
const ref = React.useRef<HTMLTableRowElement>(null);
const { rowProps } = useTableHeaderRow({ node: item }, state, ref);
return (
<Styled.Tr ref={ref} {...rowProps} withScrollSnapRows={withScrollSnapRows}>
{children}
</Styled.Tr>
);
};
const TableColumnHeader = <TableRowData extends object>({
column,
state,
withScrollSnapColumns,
}: {
column: Node<TableRowData>;
state: TableState<TableRowData>;
withScrollSnapColumns?: boolean;
}) => {
const ref = React.useRef<HTMLTableCellElement>(null);
const { columnHeaderProps } = useTableColumnHeader({ node: column }, state, ref);
const { isFocusVisible, focusProps } = useFocusRing();
return (
<Styled.Th
{...mergeProps(columnHeaderProps, focusProps)}
colSpan={column.props.colspan}
// data-focused={isFocusVisible || undefined}
style={{ width: column.props?.width }}
ref={ref}
withScrollSnapColumns={withScrollSnapColumns}
>
<Styled.Row>
{column.rendered}
{column.props.allowsSorting && (
<Styled.SortArrow
aria-hidden="true"
sortDirection={
state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction
}
>
<Icon iconName={IconName.Triangle} aria-hidden="true" />
</Styled.SortArrow>
)}
</Styled.Row>
</Styled.Th>
);
};
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.ViewMoreTr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
onPointerDown={(e: MouseEvent) => e.preventDefault()}
>
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.ViewMoreTr>
);
};
export const TableRow = <TableRowData extends object>({
item,
children,
state,
// shouldRowRender,
withGradientCardRows,
withFocusStickyRows,
withScrollSnapRows,
...attrs
}: {
item: TableCollection<TableRowData>['rows'][number];
children: React.ReactNode;
state: TableState<TableRowData>;
// shouldRowRender?: (prevRowData: TableRowData, currentRowData: TableRowData) => boolean;
withGradientCardRows?: boolean;
withFocusStickyRows?: boolean;
withScrollSnapRows?: boolean;
}) => {
const ref = React.useRef<HTMLTableRowElement>(null);
const selectionManager = state.selectionManager;
const isSelected = selectionManager.isSelected(item.key);
const isClickable = selectionManager.selectionBehavior === 'toggle';
const { rowProps, isPressed } = useTableRow(
{
node: item,
},
state,
ref
);
const { isFocusVisible, focusProps } = useFocusRing();
return (
<Styled.Tr
ref={ref}
data-selected={isSelected}
// data-focused={isFocusVisible || undefined}
$data-isPressed={isPressed}
{...mergeProps(rowProps, focusProps)}
{...attrs}
withGradientCardRows={withGradientCardRows}
withFocusStickyRows={withFocusStickyRows}
withScrollSnapRows={withScrollSnapRows}
isClickable={isClickable}
>
{children}
</Styled.Tr>
);
};
// const TableRowMemo = React.memo(
// TableRow,
// (a, b) => !!b.shouldRowRender?.(a.item.value, b.item.value)
// );
const TableCell = <TableRowData extends object>({
cell,
state,
isActionable,
}: {
cell: Node<TableRowData>;
state: TableState<TableRowData>;
isActionable?: boolean;
}) => {
const ref = React.useRef<HTMLTableCellElement>(null);
const { gridCellProps } = useTableCell({ node: cell }, state, ref);
const { isFocusVisible, focusProps } = useFocusRing();
return (
<Styled.Td
{...mergeProps(
isActionable
? {
onMouseDown: (e: MouseEvent) => e.stopPropagation(),
onPointerDown: (e: MouseEvent) => e.stopPropagation(),
...gridCellProps,
}
: gridCellProps,
focusProps
)}
// data-focused={isFocusVisible || undefined}
ref={ref}
>
{/* <Styled.Row> */}
{cell.rendered}
{/* </Styled.Row> */}
</Styled.Td>
);
};
// const TableSelectAllCell = ({ column, state }) => {
// const ref = React.useRef<HTMLTableHeaderCellElement>(null);
// const isSingleSelectionMode = state.selectionManager.selectionMode === 'single';
// const { columnHeaderProps } = useTableColumnHeader({ node: column }, state, ref);
// const { checkboxProps } = useTableSelectAllCheckbox(state);
// return (
// <Styled.Th
// {...columnHeaderProps}
// ref={ref}
// >
// {state.selectionManager.selectionMode === 'single' ? (
// <VisuallyHidden>{inputProps['aria-label']}</VisuallyHidden>
// ) : (
// <Checkbox {...checkboxProps} />
// )}
// </Styled.Th>
// );
// };
// const TableCheckboxCell = ({ cell, state }: { cell; state }) => {
// const ref = React.useRef<HTMLTableCellElement>(null);
// const { gridCellProps } = useTableCell({ node: cell }, state, ref);
// const { checkboxProps } = useTableSelectionCheckbox({ key: cell.parentKey }, state);
// return (
// <Styled.Td
// {...gridCellProps}
// ref={ref}
// >
// <Checkbox {...checkboxProps} />
// </Styled.Td>
// );
// };
const Styled: Record<string, AnyStyledComponent> = {};
Styled.TableWrapper = styled.div<{
isEmpty: boolean;
withGradientCardRows?: boolean;
withOuterBorder: boolean;
}>`
// Params
--tableHeader-textColor: var(--color-text-0, inherit);
--tableHeader-backgroundColor: inherit;
--table-header-height: 2rem;
--tableRow-hover-backgroundColor: var(--color-layer-3);
--tableRow-backgroundColor: ;
--table-cell-align: start; // start | center | end
--table-firstColumn-cell-align: start; // start | center | end | var(--table-cell-align)
--table-lastColumn-cell-align: end; // start | center | end | var(--table-cell-align)
--tableCell-padding: 0 1rem;
--tableViewMore-borderColor: inherit;
// Rules
flex: 1;
${layoutMixins.contentSectionAttached}
scroll-snap-align: none;
${layoutMixins.stack}
overflow: clip;
${({ isEmpty, withGradientCardRows, withOuterBorder }) =>
withOuterBorder &&
(!withGradientCardRows || isEmpty) &&
css`
${layoutMixins.withOuterBorderClipped}
`}
`;
Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
${layoutMixins.column}
height: 100%;
justify-items: center;
align-content: center;
padding: 4rem;
gap: 0.75em;
color: var(--color-text-0);
font: var(--font-base-book);
`;
Styled.Table = styled.table<{
hideHeader?: boolean;
withGradientCardRows: boolean;
withOuterBorder: boolean;
withInnerBorders: boolean;
withSolidHeader: boolean;
}>`
align-self: start;
${layoutMixins.stickyArea1}
--stickyArea1-background: var(--color-layer-2);
--stickyArea1-topHeight: var(--table-header-height);
${({ hideHeader }) =>
hideHeader &&
css`
--stickyArea1-topHeight: 0px;
`}
border-collapse: separate;
border-spacing: 0;
/* [data-selected] {} */
${({ withInnerBorders, hideHeader }) =>
withInnerBorders &&
css`
border-spacing: 0 var(--border-width);
--stickyArea1-topGap: var(--border-width);
${hideHeader &&
css`
--stickyArea1-topGap: 0px;
`}
// Compensate for outer <table> border (hidden and omitted from scroll with overflow: clip; on <TableWrapper>)
margin: calc(-1 * var(--border-width)) 0;
`}
${({ withGradientCardRows }) =>
withGradientCardRows &&
css`
border-spacing: 0 0.75rem;
// Use negative margin and 0 padding on 'th' element, so that border-spacing
// doesn't affect the header row's height
margin: -0.75rem clamp(0rem, 1rem - var(--contentContainerPage-paddingLeft, 0rem), 1rem) 0;
th {
padding: 0;
}
`}
@media ${breakpoints.tablet} {
min-height: 6.25rem;
}
`;
Styled.Tr = styled.tr<{
isClickable?: boolean;
withFocusStickyRows?: boolean;
withScrollSnapRows: boolean;
}>`
/* Computed */
--tableRow-currentBackgroundColor: var(--tableRow-backgroundColor);
/* Rules */
background-color: var(--tableRow-currentBackgroundColor);
${({ isClickable }) =>
isClickable &&
css`
cursor: pointer;
&:hover,
&:focus-visible,
&:focus-within {
--tableRow-currentBackgroundColor: var(--tableRow-hover-backgroundColor);
filter: brightness(var(--hover-filter-base));
}
`};
${({ withFocusStickyRows }) =>
withFocusStickyRows &&
css`
&:focus-visible,
&:focus-within {
${layoutMixins.sticky}
z-index: 1;
}
`}
${({ withScrollSnapRows }) =>
withScrollSnapRows &&
css`
${layoutMixins.scrollSnapItem}
`}
`;
Styled.Th = styled.th<{ withScrollSnapColumns: boolean }>`
// Computed
--table-cell-currentAlign: var(--table-cell-align);
&:first-of-type {
--table-cell-currentAlign: var(--table-firstColumn-cell-align, var(--table-cell-align));
}
&:last-of-type {
--table-cell-currentAlign: var(--table-lastColumn-cell-align, var(--table-cell-align));
}
// Rules
${({ withScrollSnapColumns }) =>
withScrollSnapColumns &&
css`
${layoutMixins.scrollSnapItem}
`}
white-space: nowrap;
text-align: var(--table-cell-currentAlign);
`;
Styled.Td = styled.td`
// Computed
--table-cell-currentAlign: var(--table-cell-align);
&:first-of-type {
--table-cell-currentAlign: var(--table-firstColumn-cell-align, var(--table-cell-align));
}
&:last-of-type {
--table-cell-currentAlign: var(--table-lastColumn-cell-align, var(--table-cell-align));
}
// Rules
padding: var(--tableCell-padding);
text-align: var(--table-cell-currentAlign);
> * {
vertical-align: middle;
}
`;
Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
float: right;
margin-left: auto;
display: inline-flex;
transition: transform 0.3s var(--ease-out-expo), font-size 0.3s var(--ease-out-expo);
font-size: 0.375em;
${Styled.Th}[aria-sort="none"] & {
visibility: hidden;
}
${Styled.Th}[aria-sort="ascending"] & {
transform: scaleY(-1);
}
`;
Styled.Thead = styled.thead<StyleProps>`
${layoutMixins.stickyHeader}
scroll-snap-align: none;
font: var(--font-mini-book);
> * {
height: var(--stickyArea-topHeight);
}
color: var(--tableHeader-textColor);
background-color: var(--tableHeader-backgroundColor);
${({ withInnerBorders, withGradientCardRows }) =>
withInnerBorders &&
!withGradientCardRows &&
css`
${layoutMixins.withInnerHorizontalBorders}
`}
`;
Styled.Tbody = styled.tbody<StyleProps>`
${layoutMixins.stickyArea2}
font: var(--font-small-book);
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
&:before,
&:after {
content: '';
display: table-row;
}
${({ withInnerBorders, withGradientCardRows }) =>
withInnerBorders &&
!withGradientCardRows &&
css`
${layoutMixins.withInnerHorizontalBorders}
--stickyArea2-paddingTop: var(--border-width);
--stickyArea2-paddingBottom: var(--border-width);
--stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
}
`}
${({ withOuterBorder }) =>
withOuterBorder &&
css`
tr:last-of-type:not(:only-of-type) {
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
}
tr:first-of-type {
box-shadow: none;
}
`}
${({ withGradientCardRows }) =>
withGradientCardRows &&
css`
--table-row-default-gradient: linear-gradient(
342.62deg,
var(--color-gradient-base-0) -9.23%,
var(--color-gradient-base-1) 110.36%
);
--table-row-gradient-to-color: transparent;
--tableCell-borderRadius: ;
&:before,
&:after {
content: none;
}
tr {
background: linear-gradient(
270deg,
var(--table-row-gradient-to-color) -32.39%,
transparent 100%
),
var(--table-row-default-gradient);
@supports (background: -webkit-named-image(i)) {
background: var(--table-row-gradient-to-color);
}
td:first-child {
--tableCell-borderRadius: 1rem 0 0 1rem;
}
td:last-child {
--tableCell-borderRadius: 0 1rem 1rem 0;
}
td {
height: 4.25rem;
border-radius: var(--tableCell-borderRadius);
}
}
`}
`;
Styled.Row = styled.div`
${layoutMixins.inlineRow}
padding: var(--tableCell-padding);
`;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
--button-textColor: var(--color-text-1);
width: 100%;
svg {
width: 0.675rem;
margin-left: 0.5ch;
}
`;
Styled.ViewMoreTr = styled(Styled.Tr)`
--border-color: var(--tableViewMore-borderColor);
`;