mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -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 {ReactNode} from 'react';
|
||||||
import type {Meta, StoryObj} from '@storybook/react';
|
import type {Meta, StoryObj} from '@storybook/react';
|
||||||
|
|
||||||
|
import * as TableRowStories from './TableRow.stories';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import TableCell from './TableCell';
|
import TableCell from './TableCell';
|
||||||
import TableHead from './TableHead';
|
import TableHead from './TableHead';
|
||||||
|
@ -12,37 +13,34 @@ const meta = {
|
||||||
tags: ['autodocs']
|
tags: ['autodocs']
|
||||||
} satisfies Meta<typeof Table>;
|
} satisfies Meta<typeof Table>;
|
||||||
|
|
||||||
|
const {/*id,*/ ...tableRowProps} = TableRowStories.HiddenAction.args || {};
|
||||||
|
|
||||||
|
const tableHeader = (
|
||||||
|
<>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Email</TableHead>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const tableRows = (
|
const tableRows = (
|
||||||
<>
|
<>
|
||||||
<TableRow>
|
<TableRow {...tableRowProps}>
|
||||||
<TableHead>Name</TableHead>
|
|
||||||
<TableHead>Email</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Jamie Larson</TableCell>
|
<TableCell>Jamie Larson</TableCell>
|
||||||
<TableCell>jamie@example.com</TableCell>
|
<TableCell>jamie@example.com</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow {...tableRowProps}>
|
||||||
<TableCell>Jamie Larson</TableCell>
|
<TableCell>Jamie Larson</TableCell>
|
||||||
<TableCell>jamie@example.com</TableCell>
|
<TableCell>jamie@example.com</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow {...tableRowProps}>
|
||||||
<TableCell>Jamie Larson</TableCell>
|
<TableCell>Jamie Larson</TableCell>
|
||||||
<TableCell>jamie@example.com</TableCell>
|
<TableCell>jamie@example.com</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow {...tableRowProps}>
|
||||||
<TableCell>Jamie Larson</TableCell>
|
<TableCell>Jamie Larson</TableCell>
|
||||||
<TableCell>jamie@example.com</TableCell>
|
<TableCell>jamie@example.com</TableCell>
|
||||||
</TableRow>
|
</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 Larson</TableCell>
|
||||||
<TableCell>jamie@example.com</TableCell>
|
<TableCell>jamie@example.com</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
@ -59,6 +57,13 @@ export const Default: Story = {
|
||||||
decorators: [(_story: () => ReactNode) => (<div style={{maxWidth: '600px'}}>{_story()}</div>)]
|
decorators: [(_story: () => ReactNode) => (<div style={{maxWidth: '600px'}}>{_story()}</div>)]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithHeader: Story = {
|
||||||
|
args: {
|
||||||
|
header: tableHeader,
|
||||||
|
children: tableRows
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const WithPageTitle: Story = {
|
export const WithPageTitle: Story = {
|
||||||
args: {
|
args: {
|
||||||
pageTitle: 'This is a page title',
|
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 = {
|
export const Loading: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
header: tableHeader,
|
||||||
children: tableRows,
|
children: tableRows,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
hint: 'This is a hint',
|
hint: 'This is a hint',
|
||||||
hintSeparator: true
|
hintSeparator: true
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -3,6 +3,7 @@ import Hint from './Hint';
|
||||||
import Pagination from './Pagination';
|
import Pagination from './Pagination';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Separator from './Separator';
|
import Separator from './Separator';
|
||||||
|
import TableRow from './TableRow';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {LoadingIndicator} from './LoadingIndicator';
|
import {LoadingIndicator} from './LoadingIndicator';
|
||||||
import {PaginationData} from '../../hooks/usePagination';
|
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
|
* 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;
|
pageTitle?: string;
|
||||||
|
header?: React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
borderTop?: boolean;
|
borderTop?: boolean;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
|
@ -29,7 +31,7 @@ const OptionalPagination = ({pagination}: {pagination?: PaginationData}) => {
|
||||||
return <Pagination {...pagination}/>;
|
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(
|
const tableClasses = clsx(
|
||||||
(borderTop || pageTitle) && 'border-t border-grey-300',
|
(borderTop || pageTitle) && 'border-t border-grey-300',
|
||||||
'w-full',
|
'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
|
// 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);
|
const [tableHeight, setTableHeight] = React.useState<number | undefined>(undefined);
|
||||||
|
|
||||||
React.useEffect(() => {
|
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 */}
|
{/* TODO: make this div have the same height across all pages */}
|
||||||
<div>
|
<div>
|
||||||
{!isLoading && <table ref={table} className={tableClasses}>
|
<table className={tableClasses}>
|
||||||
<tbody>
|
{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}
|
{children}
|
||||||
</tbody>
|
</tbody>}
|
||||||
</table>}
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading && <LoadingIndicator delay={200} size='lg' style={loadingStyle} />}
|
{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 TableHead: React.FC<HTMLProps<HTMLTableCellElement>> = ({className, children, ...props}) => {
|
||||||
const tableCellClasses = clsx(
|
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',
|
props.onClick && 'hover:cursor-pointer',
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<td className={tableCellClasses} {...props}>
|
<th className={tableCellClasses} {...props}>
|
||||||
<Heading className='whitespace-nowrap' level={6}>{children}</Heading>
|
<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',
|
'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',
|
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',
|
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
|
className
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue