mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Improved Table component and its storybook (#18169)
refs https://github.com/TryGhost/Product/issues/3890 - Made Table and its children semantic - Updated Table and TableRow storybooks with new examples - Fixed smaller visual bugs
This commit is contained in:
parent
a750ed847c
commit
4fc03e5f25
5 changed files with 118 additions and 29 deletions
|
@ -1,6 +1,7 @@
|
|||
import {ReactNode} from 'react';
|
||||
import type {Meta, StoryObj} from '@storybook/react';
|
||||
|
||||
import * as TableRowStories from './TableRow.stories';
|
||||
import Table from './Table';
|
||||
import TableCell from './TableCell';
|
||||
import TableHead from './TableHead';
|
||||
|
@ -12,37 +13,34 @@ const meta = {
|
|||
tags: ['autodocs']
|
||||
} satisfies Meta<typeof Table>;
|
||||
|
||||
const {/*id,*/ ...tableRowProps} = TableRowStories.HiddenAction.args || {};
|
||||
|
||||
const tableHeader = (
|
||||
<>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
</>
|
||||
);
|
||||
|
||||
const tableRows = (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRow {...tableRowProps}>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRow {...tableRowProps}>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRow {...tableRowProps}>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRow {...tableRowProps}>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRow {...tableRowProps}>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</TableRow>
|
||||
|
@ -59,6 +57,13 @@ export const Default: Story = {
|
|||
decorators: [(_story: () => ReactNode) => (<div style={{maxWidth: '600px'}}>{_story()}</div>)]
|
||||
};
|
||||
|
||||
export const WithHeader: Story = {
|
||||
args: {
|
||||
header: tableHeader,
|
||||
children: tableRows
|
||||
}
|
||||
};
|
||||
|
||||
export const WithPageTitle: Story = {
|
||||
args: {
|
||||
pageTitle: 'This is a page title',
|
||||
|
@ -66,11 +71,28 @@ export const WithPageTitle: Story = {
|
|||
}
|
||||
};
|
||||
|
||||
export const WithRowAction: Story = {
|
||||
args: {
|
||||
header: tableHeader,
|
||||
children: tableRows
|
||||
}
|
||||
};
|
||||
|
||||
export const WithHint: Story = {
|
||||
args: {
|
||||
header: tableHeader,
|
||||
children: tableRows,
|
||||
hint: 'This is a hint',
|
||||
hintSeparator: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
header: tableHeader,
|
||||
children: tableRows,
|
||||
isLoading: true,
|
||||
hint: 'This is a hint',
|
||||
hintSeparator: true
|
||||
}
|
||||
};
|
||||
};
|
|
@ -3,6 +3,7 @@ import Hint from './Hint';
|
|||
import Pagination from './Pagination';
|
||||
import React from 'react';
|
||||
import Separator from './Separator';
|
||||
import TableRow from './TableRow';
|
||||
import clsx from 'clsx';
|
||||
import {LoadingIndicator} from './LoadingIndicator';
|
||||
import {PaginationData} from '../../hooks/usePagination';
|
||||
|
@ -12,6 +13,7 @@ interface TableProps {
|
|||
* If the table is the primary content on a page (e.g. Members table) then you can set a pagetitle to be consistent
|
||||
*/
|
||||
pageTitle?: string;
|
||||
header?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
borderTop?: boolean;
|
||||
hint?: string;
|
||||
|
@ -29,7 +31,7 @@ const OptionalPagination = ({pagination}: {pagination?: PaginationData}) => {
|
|||
return <Pagination {...pagination}/>;
|
||||
};
|
||||
|
||||
const Table: React.FC<TableProps> = ({children, borderTop, hint, hintSeparator, pageTitle, className, pagination, isLoading}) => {
|
||||
const Table: React.FC<TableProps> = ({header, children, borderTop, hint, hintSeparator, pageTitle, className, pagination, isLoading}) => {
|
||||
const tableClasses = clsx(
|
||||
(borderTop || pageTitle) && 'border-t border-grey-300',
|
||||
'w-full',
|
||||
|
@ -38,7 +40,7 @@ const Table: React.FC<TableProps> = ({children, borderTop, hint, hintSeparator,
|
|||
);
|
||||
|
||||
// We want to avoid layout jumps when we load a new page of the table, or when data is invalidated
|
||||
const table = React.useRef<HTMLTableElement>(null);
|
||||
const table = React.useRef<HTMLTableSectionElement>(null);
|
||||
const [tableHeight, setTableHeight] = React.useState<number | undefined>(undefined);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -72,11 +74,14 @@ const Table: React.FC<TableProps> = ({children, borderTop, hint, hintSeparator,
|
|||
|
||||
{/* TODO: make this div have the same height across all pages */}
|
||||
<div>
|
||||
{!isLoading && <table ref={table} className={tableClasses}>
|
||||
<tbody>
|
||||
<table className={tableClasses}>
|
||||
{header && <thead className='border-b border-grey-200 dark:border-grey-600'>
|
||||
<TableRow bgOnHover={false} separator={false}>{header}</TableRow>
|
||||
</thead>}
|
||||
{!isLoading && <tbody ref={table}>
|
||||
{children}
|
||||
</tbody>
|
||||
</table>}
|
||||
</tbody>}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{isLoading && <LoadingIndicator delay={200} size='lg' style={loadingStyle} />}
|
||||
|
|
|
@ -4,16 +4,16 @@ import clsx from 'clsx';
|
|||
|
||||
const TableHead: React.FC<HTMLProps<HTMLTableCellElement>> = ({className, children, ...props}) => {
|
||||
const tableCellClasses = clsx(
|
||||
'!py-3 !pl-0 !pr-6 align-top',
|
||||
'!py-2 !pl-0 !pr-6 text-left align-top',
|
||||
props.onClick && 'hover:cursor-pointer',
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<td className={tableCellClasses} {...props}>
|
||||
<th className={tableCellClasses} {...props}>
|
||||
<Heading className='whitespace-nowrap' level={6}>{children}</Heading>
|
||||
</td>
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHead;
|
||||
export default TableHead;
|
|
@ -0,0 +1,62 @@
|
|||
import {ReactNode} from 'react';
|
||||
import type {Meta, StoryObj} from '@storybook/react';
|
||||
|
||||
import Button from './Button';
|
||||
import TableCell from './TableCell';
|
||||
import TableHead from './TableHead';
|
||||
import TableRow from './TableRow';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Table / Table Row',
|
||||
component: TableRow,
|
||||
tags: ['autodocs']
|
||||
} satisfies Meta<typeof TableRow>;
|
||||
|
||||
const tableHeaderCells = (
|
||||
<>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
</>
|
||||
);
|
||||
|
||||
const tableCells = (
|
||||
<>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
</>
|
||||
);
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TableRow>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: tableCells,
|
||||
action: <Button color='green' label='Edit' link={true} />,
|
||||
onClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const clickedDiv = e.currentTarget;
|
||||
alert(`Clicked on "${clickedDiv.id}"`);
|
||||
}
|
||||
},
|
||||
decorators: [(_story: () => ReactNode) => (<div style={{maxWidth: '600px'}}>{_story()}</div>)]
|
||||
};
|
||||
|
||||
export const HiddenAction: Story = {
|
||||
args: {
|
||||
children: tableCells,
|
||||
hideActions: true,
|
||||
action: <Button color='green' label='Edit' link={true} />,
|
||||
onClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const clickedDiv = e.currentTarget;
|
||||
alert(`Clicked on "${clickedDiv.id}"`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const HeaderRow: Story = {
|
||||
args: {
|
||||
children: tableHeaderCells,
|
||||
separator: false,
|
||||
bgOnHover: false
|
||||
}
|
||||
};
|
|
@ -28,7 +28,7 @@ const TableRow: React.FC<TableRowProps> = ({id, action, hideActions, className,
|
|||
'group/table-row',
|
||||
bgOnHover && 'hover:bg-gradient-to-r hover:from-white hover:to-grey-50 dark:hover:from-black dark:hover:to-grey-950',
|
||||
onClick && 'cursor-pointer',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200 dark:border-grey-900 dark:hover:border-grey-800' : 'border-y border-transparent first-of-type:hover:border-t-transparent',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200 dark:border-grey-900 dark:hover:border-grey-800' : 'border-y border-none first-of-type:hover:border-t-transparent',
|
||||
className
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue