0
Fork 0
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:
Djordje Vlaisavljevic 2023-09-15 13:09:19 +01:00 committed by GitHub
parent a750ed847c
commit 4fc03e5f25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 29 deletions

View file

@ -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
}
};
};

View file

@ -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} />}

View file

@ -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;

View file

@ -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
}
};

View file

@ -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
);