Skip to content

Commit

Permalink
feat(componenets): table sticky header (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao authored Mar 18, 2024
1 parent 92ce02d commit efe9f04
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 31 deletions.
14 changes: 9 additions & 5 deletions packages/components/src/Table/BaseTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ import { TableHeaderRow } from './TableHeaderRow';
import { TableRow } from './TableRow';
import { TableRowGroup } from './TableRowGroup';

type InheritAttrs = TableStateProps<Record<string, any>> & AriaTableProps<Record<string, any>>;
type Props = {
isStickyHeader?: boolean;
};

type NativeAttrs = Omit<HTMLAttributes<HTMLTableElement>, keyof InheritAttrs>;
type InheritAttrs = Omit<TableStateProps<Record<string, any>> & AriaTableProps<Record<string, any>>, keyof Props>;

type BaseTableProps = InheritAttrs & NativeAttrs;
type NativeAttrs = Omit<HTMLAttributes<HTMLTableElement>, keyof (InheritAttrs & Props)>;

type BaseTableProps = Props & InheritAttrs & NativeAttrs;

const BaseTable = forwardRef<HTMLTableElement, BaseTableProps>(
({ onRowAction, onCellAction, onSelectionChange, onSortChange, ...props }, ref): JSX.Element => {
({ onRowAction, onCellAction, onSelectionChange, onSortChange, isStickyHeader, ...props }, ref): JSX.Element => {
const tableRef = useDOMRef(ref);

const statelyProps = { onSelectionChange, onSortChange, ...props };
Expand All @@ -31,7 +35,7 @@ const BaseTable = forwardRef<HTMLTableElement, BaseTableProps>(

return (
<StyledTable ref={tableRef} {...mergeProps(props, gridProps)}>
<TableRowGroup elementType='thead'>
<TableRowGroup elementType='thead' isStickyHeader={isStickyHeader}>
{collection.headerRows.map((headerRow) => (
<TableHeaderRow key={headerRow.key} item={headerRow} state={state}>
{[...(collection.getChildren?.(headerRow.key) || [])].map((column) => (
Expand Down
20 changes: 19 additions & 1 deletion packages/components/src/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ const rows: RowProps[] = [
},
{ id: 2, coin: 'DOT', price: '$8.13', action },
{ id: 3, coin: 'KINT', price: '$2.80', action },
{ id: 4, coin: 'kBTC', price: '$23,074.29', action }
{ id: 5, coin: 'kBTC', price: '$23,074.29', action },
{ id: 6, coin: 'kBTC', price: '$23,074.29', action },
{ id: 7, coin: 'kBTC', price: '$23,074.29', action },
{ id: 8, coin: 'kBTC', price: '$23,074.29', action },
{ id: 9, coin: 'kBTC', price: '$23,074.29', action },
{ id: 10, coin: 'kBTC', price: '$23,074.29', action },
{ id: 11, coin: 'kBTC', price: '$23,074.29', action },
{ id: 12, coin: 'kBTC', price: '$23,074.29', action },
{ id: 13, coin: 'kBTC', price: '$23,074.29', action },
{ id: 14, coin: 'kBTC', price: '$23,074.29', action }
];

export default {
Expand Down Expand Up @@ -54,3 +63,12 @@ export const Selection: StoryObj<TableProps> = {
selectedKeys: [1]
}
};

export const StickyHeader: StoryObj<TableProps> = {
args: {
isStickyHeader: true,
style: {
maxHeight: 300
}
}
};
42 changes: 35 additions & 7 deletions packages/components/src/Table/Table.style.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import styled, { css } from 'styled-components';

import { Card } from '../Card';

type StyledTableRowProps = {
$isHovered: boolean;
$isSelected: boolean;
};

type StyledTableRowGroupProps = {
$isStickyHeader: boolean;
};

const StyledTable = styled.table`
width: 100%;
border-collapse: separate;
border-spacing: 0;
isolation: isolate;
overflow: hidden;
overflow: auto;
${({ theme }) => theme.table.base};
`;
Expand All @@ -27,11 +38,6 @@ const StyledTableHeaderRow = styled.tr`
${({ theme }) => theme.table.headerRow};
`;

type StyledTableRowProps = {
$isHovered: boolean;
$isSelected: boolean;
};

const StyledTableRow = styled.tr<StyledTableRowProps>`
outline: none;
cursor: ${({ $isHovered }) => $isHovered && 'pointer'};
Expand All @@ -51,4 +57,26 @@ const StyledTableRow = styled.tr<StyledTableRowProps>`
}};
`;

export { StyledTable, StyledTableCell, StyledTableColumnHeader, StyledTableHeaderRow, StyledTableRow };
const StyledTableRowGroup = styled.div<StyledTableRowGroupProps>`
${({ $isStickyHeader }) =>
$isStickyHeader &&
css`
position: sticky;
top: 0;
z-index: 20;
`}
`;

const StyledCard = styled(Card)`
overflow: auto;
`;

export {
StyledTable,
StyledTableCell,
StyledTableColumnHeader,
StyledTableHeaderRow,
StyledTableRow,
StyledTableRowGroup,
StyledCard
};
35 changes: 21 additions & 14 deletions packages/components/src/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
import { Cell, Column, Row, TableBody, TableHeader } from '@react-stately/table';
import { forwardRef, ReactNode } from 'react';
import { ReactNode, forwardRef } from 'react';

import { CardProps } from '../Card';

import { BaseTable, BaseTableProps } from './BaseTable';
import { StyledCard } from './Table.style';

type ColumnProps = { name: ReactNode; id: string | number; hideHeader?: boolean };

type RowProps = {
id: string | number;
[field: string]: any;
isStickyHeader?: boolean;
};

type Props = {
columns: ColumnProps[];
rows: RowProps[];
wrapperProps?: CardProps;
};

type InheritAttrs = Omit<BaseTableProps, keyof Props | 'children'>;

type TableProps = Props & InheritAttrs;

const Table = forwardRef<HTMLTableElement, TableProps>(
({ columns, rows, ...props }, ref): JSX.Element => (
<BaseTable ref={ref} {...props}>
<TableHeader columns={columns}>
{({ id, name, ...columnProps }) => (
<Column key={id} {...columnProps}>
{name}
</Column>
)}
</TableHeader>
<TableBody items={rows}>
{(item: any) => <Row>{(columnKey) => <Cell>{item[columnKey.toString()]}</Cell>}</Row>}
</TableBody>
</BaseTable>
({ columns, rows, isStickyHeader, style, className, hidden, wrapperProps, ...props }, ref): JSX.Element => (
<StyledCard className={className} hidden={hidden} style={style} {...wrapperProps}>
<BaseTable ref={ref} isStickyHeader={isStickyHeader} {...props}>
<TableHeader columns={columns}>
{({ id, name, ...columnProps }) => (
<Column key={id} {...columnProps}>
{name}
</Column>
)}
</TableHeader>
<TableBody items={rows}>
{(item: any) => <Row>{(columnKey) => <Cell>{item[columnKey.toString()]}</Cell>}</Row>}
</TableBody>
</BaseTable>
</StyledCard>
)
);

Expand Down
23 changes: 19 additions & 4 deletions packages/components/src/Table/TableRowGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,29 @@ import { HTMLAttributes } from 'react';

import { ElementTypeProp } from '../utils/types';

type NativeAttrs = HTMLAttributes<HTMLTableSectionElement>;
import { StyledTableRowGroup } from './Table.style';

type TableRowGroupProps = NativeAttrs & ElementTypeProp;
type Props = {
isStickyHeader?: boolean;
};

type NativeAttrs = Omit<HTMLAttributes<HTMLTableSectionElement>, keyof Props>;

type TableRowGroupProps = Props & NativeAttrs & ElementTypeProp;

const TableRowGroup = ({ elementType: Component = 'thead', children, ...props }: TableRowGroupProps): JSX.Element => {
const TableRowGroup = ({
elementType = 'thead',
children,
isStickyHeader,
...props
}: TableRowGroupProps): JSX.Element => {
const { rowGroupProps } = useTableRowGroup();

return <Component {...mergeProps(props, rowGroupProps)}>{children}</Component>;
return (
<StyledTableRowGroup as={elementType} {...mergeProps(props, rowGroupProps)} $isStickyHeader={isStickyHeader}>
{children}
</StyledTableRowGroup>
);
};

export { TableRowGroup };
Expand Down

0 comments on commit efe9f04

Please sign in to comment.