Initial commit
This commit is contained in:
@@ -0,0 +1,922 @@
|
||||
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 } from '@/hooks';
|
||||
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
|
||||
|
||||
import { Checkbox } from '@/components/Checkbox';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Icon, IconName } from './Icon';
|
||||
import { Tag } from './Tag';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
// 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,
|
||||
// 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 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
|
||||
Math.sign(
|
||||
(parseInt(first as string) || (first as number)) -
|
||||
(parseInt(second as string) || (second as number))
|
||||
)) *
|
||||
// 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)!))
|
||||
}
|
||||
// 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={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>;
|
||||
|
||||
hideHeader?: boolean;
|
||||
withGradientCardRows?: boolean;
|
||||
withFocusStickyRows?: boolean;
|
||||
withOuterBorder?: boolean;
|
||||
withInnerBorders?: boolean;
|
||||
withScrollSnapColumns?: boolean;
|
||||
withScrollSnapRows?: boolean;
|
||||
}) => {
|
||||
const { selectionMode, selectionBehavior } = 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}
|
||||
>
|
||||
{/* {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>
|
||||
)
|
||||
)}
|
||||
</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,
|
||||
}: { children: React.ReactNode } & StyleProps) => {
|
||||
const { rowGroupProps } = useTableRowGroup();
|
||||
|
||||
return (
|
||||
<Styled.Tbody
|
||||
{...rowGroupProps}
|
||||
withGradientCardRows={withGradientCardRows}
|
||||
withInnerBorders={withInnerBorders}
|
||||
>
|
||||
{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 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;
|
||||
|
||||
// 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: 2rem;
|
||||
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(1.1);
|
||||
}
|
||||
`};
|
||||
|
||||
${({ 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;
|
||||
|
||||
> * {
|
||||
height: var(--stickyArea-topHeight);
|
||||
}
|
||||
|
||||
color: var(--tableHeader-textColor);
|
||||
background-color: var(--tableHeader-backgroundColor);
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
${({ withInnerBorders, withGradientCardRows }) =>
|
||||
withInnerBorders &&
|
||||
!withGradientCardRows &&
|
||||
css`
|
||||
${layoutMixins.withInnerHorizontalBorders}
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Tbody = styled.tbody<StyleProps>`
|
||||
${layoutMixins.stickyArea2}
|
||||
|
||||
// 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);
|
||||
`}
|
||||
|
||||
${({ 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);
|
||||
`;
|
||||
Reference in New Issue
Block a user