diff --git a/src/components/TableModule/TableModule.stories.tsx b/src/components/TableModule/TableModule.stories.tsx index df005513..627a4bed 100644 --- a/src/components/TableModule/TableModule.stories.tsx +++ b/src/components/TableModule/TableModule.stories.tsx @@ -1,64 +1,34 @@ -import React, { useRef, useState } from 'react'; -import { ComponentStory, ComponentMeta } from '@storybook/react'; +import React from 'react'; +import { StoryObj, StoryFn, Meta } from '@storybook/react'; import { TableModule } from './TableModule'; import { RowSelectionRow, RowSelectionState, TableConfiguration, - TableSortClickProps, } from './types'; -import { TableModuleActions } from './TableModuleActions'; import { Checkbox } from '../Checkbox'; import { IconButton } from '../IconButton'; import { SpinButton } from '../SpinButton'; -import { Share, Trash } from '@lifeomic/chromicons'; +import { Share, Trash, Smile } from '@lifeomic/chromicons'; import { Button } from '../Button'; import { TableActionDivider } from './TableActionDivider'; import { Tooltip } from '../Tooltip'; -export default { - title: 'Components/TableModule', +const meta: Meta = { component: TableModule, - argTypes: {}, - subcomponents: { TableModuleActions, TableActionDivider }, -} as ComponentMeta; - -const data = [ - { - description: 'Frozen yoghurt', - calories: '159', - fat: '6.0', - carbs: '24', - category: 'yogurt', - }, - { - description: 'Ice cream sandwich', - calories: '237', - fat: '9.0', - carbs: '37', - category: 'ice cream', - }, - { - description: 'Eclair', - calories: '262', - fat: '16.0', - carbs: '24', - category: 'dessert', - }, - { - description: 'Cupcake', - calories: '305', - fat: '3.7', - carbs: '67', - category: 'cake', - }, -]; +}; +export default meta; +type Story = StoryObj; +import { data } from './storyData'; const dataLong = [...data, ...data, ...data, ...data]; -const Template: ComponentStory = (args) => { - const tableRef = useRef(null); +import { configBasic, configSticky, configWrapped } from './config'; +import { buildSortConfig } from './configSort'; + +const Template: StoryFn = (args) => { + const tableRef = React.useRef(null); return (
@@ -66,305 +36,52 @@ const Template: ComponentStory = (args) => { ); }; -export const Default = Template.bind({}); -Default.args = { - data, - config: [ - { - header: { - label: 'Description', - }, - cell: { - content: (dataValue: any) => { - return dataValue.description; - }, - }, - }, - { - header: { - label: 'Calories', - }, - cell: { - content: (dataValue: any) => { - return dataValue.calories; - }, - }, - }, - { - header: { - label: 'Fat', - }, - cell: { - content: (dataValue: any) => { - return dataValue.fat; - }, - }, - }, - { - header: { - label: 'Carbs', - }, - cell: { - content: (dataValue: any) => { - return dataValue.carbs; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - ] as Array, +export const Default: Story = { + render: Template, + args: { + data, + config: configBasic as Array, + }, }; -export const WrappedCells = Template.bind({}); -WrappedCells.args = { - data, - maxCellWidth: 3, - wrapCellContent: true, - config: [ - { - header: { - label: 'Description', - }, - cell: { - content: (dataValue: any) => { - return dataValue.description; - }, - }, - }, - { - header: { - label: 'Calories', - }, - cell: { - content: (dataValue: any) => { - if (dataValue.calories === '159') { - return 'Vanilla: 250\nStrawberry: 175\nPlain: 80\n\nYogurt is known to provide probiotics through naturally included bacterial cultures. Beware of increaing your sugar intake though.\nNon-dairy options exist for those with lactose intolerance.'; - } - return dataValue.calories; - }, - }, - }, - { - header: { - label: 'Fat', - }, - cell: { - content: (dataValue: any) => { - return dataValue.fat; - }, - }, - }, - { - header: { - label: 'Carbs', - }, - cell: { - content: (dataValue: any) => { - return dataValue.carbs; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, +export const WrappedCells: Story = { + render: Template, + args: { + data, + maxCellWidth: 3, + wrapCellContent: true, + config: configWrapped as Array, + }, + parameters: { + docs: { + description: { + story: `If a table involves content that is very long and needs to be fully displayed, use \`wrapCellContent\` to show it all. This will also cover content that needs to honor new lines, i.e. a couple paragraphs with a break between or a list of items.`, }, }, - ] as Array, -}; -WrappedCells.parameters = { - docs: { - description: { - story: `If a table involves content that is very long and needs to be fully displayed, use wrapCellContent to show it all. This will also cover content that needs to honor new lines, i.e. a couple paragraphs with a break between or a list of items.`, - }, }, }; -export const Sticky = Template.bind({}); -Sticky.args = { - data: dataLong, - config: [ - { - header: { - label: 'Description', - }, - cell: { - content: (dataValue: any) => { - return dataValue.description; - }, - }, - isSticky: true, - }, - { - header: { - label: 'Calories', - }, - cell: { - content: (dataValue: any) => { - return dataValue.calories; - }, - }, - isSticky: true, - }, - { - header: { - label: 'Fat', - }, - cell: { - content: (dataValue: any) => { - return dataValue.fat; - }, - }, - }, - { - header: { - label: 'Carbs', - }, - cell: { - content: (dataValue: any) => { - return dataValue.carbs; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - isSticky: true, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, - }, - }, - { - header: { - label: 'Category', - }, - cell: { - content: (dataValue: any) => { - return dataValue.category; - }, +export const Sticky: Story = { + render: Template, + args: { + data: dataLong, + config: configSticky as Array, + }, + parameters: { + docs: { + description: { + story: `Columns can be made "sticky" or so they don't travel off-screen when scrolling the + table horizontally. This helps keep track of what row one is looking at in tables with more + columns than can be visible at one time in the document. \n \n Any number of columns can be made + sticky. They don't have to be consecutive, and can start and end at any column. However, + most common use-cases will likely just involve the first one or two consecutive columns + being sticky.`, }, }, - ] as Array, -}; -Sticky.parameters = { - docs: { - description: { - story: `Columns can be made "sticky" or so they don't travel off-screen when scrolling the - table horizontally. This helps keep track of what row one is looking at in tables with more - columns than can be visible at one time in the document. \n \n Any number of columns can be made - sticky. They don't have to be consecutive, and can start and end at any column. However, - most common use-cases will likely just involve the first one or two consecutive columns - being sticky.`, - }, }, }; -export const Sort: ComponentStory = (args) => { +const TemplateSort: StoryFn = (args) => { const [sort, setSort] = React.useState({ key: null, direction: null }); const handleSortClick = ({ key, direction }: any) => { @@ -385,57 +102,8 @@ export const Sort: ComponentStory = (args) => { return 0; }); } - - const sortConfig: Array = [ - { - header: { - label: 'Description', - onSort: (header: TableSortClickProps) => { - handleSortClick({ - key: 'description', - direction: header.sortDirection, - }); - }, - }, - cell: { - valuePath: 'description', - }, - }, - { - header: { - label: 'Calories', - onSort: (header: TableSortClickProps) => { - handleSortClick({ key: 'calories', direction: header.sortDirection }); - }, - }, - cell: { - valuePath: 'calories', - }, - }, - { - header: { - label: 'Fat', - onSort: (header: TableSortClickProps) => { - handleSortClick({ key: 'fat', direction: header.sortDirection }); - }, - }, - cell: { - valuePath: 'fat', - }, - }, - { - header: { - label: 'Carbs', - onSort: (header: TableSortClickProps) => { - handleSortClick({ key: 'carbs', direction: header.sortDirection }); - }, - }, - cell: { - valuePath: 'carbs', - }, - }, - ]; - const tableRef = useRef(null); + const sortConfig = buildSortConfig(handleSortClick); + const tableRef = React.useRef(null); return ( = (args) => { /> ); }; -Sort.parameters = { - docs: { - description: { - story: `The Table Module components provides callbacks for allowing consumers to perform -their own sorting. Out of the box, no default sorting is provided to allow -consumers to control how they want to sort their data. This allows folks to use -an API to sort their data (header clicked, determine header name clicked, and -send API request to sort the data) or allow the frontend to sort the data -themselves (header clicked, determine header name clicked, and manually sort -data from the frontend).`, + +export const Sort: Story = { + render: TemplateSort, + parameters: { + docs: { + description: { + story: `The Table Module components provides callbacks for allowing consumers to perform + their own sorting. Out of the box, no default sorting is provided to allow + consumers to control how they want to sort their data. This allows folks to use + an API to sort their data (header clicked, determine header name clicked, and + send API request to sort the data) or allow the frontend to sort the data + themselves (header clicked, determine header name clicked, and manually sort + data from the frontend).`, + }, }, }, }; -export const HoverActions = Template.bind({}); -HoverActions.args = { - data, - config: [ - { - header: { - label: 'Description', - }, - cell: { - content: (dataValue: any) => { - return dataValue.description; - }, - }, - }, - { - header: { - label: 'Calories', - }, - cell: { - content: (dataValue: any) => { - return dataValue.calories; - }, - }, - }, - { - header: { - label: 'Fat', - }, - cell: { - content: (dataValue: any) => { - return dataValue.fat; - }, - }, - }, - ] as Array, - rowActions: (row: any) => ( - <> - - - - console.log(`search ${row}!`)} - /> - - - console.log(`trash ${row}!`)} - /> - - {row.fat === '9.0' && ( - +export const HoverActions: Story = { + render: Template, + args: { + data, + config: configBasic as Array, + rowActions: (row: any) => ( + <> + + + console.log(`trash ${row}!`)} + onClick={() => console.log(`search ${row}!`)} /> - )} - - ), -}; -HoverActions.parameters = { - docs: { - description: { - story: `It is a common design pattern to include actionable buttons for a table row. The -Table Module exposes a \`rowActions\` prop to help. Hover over a row to view the -actions toolbar.`, + {row.description !== 'Cupcake' && ( + + console.log(`trash ${row}!`)} + /> + + )} + {row.fat === '9.0' && ( + + console.log(`Amaze ${row}!`)} + /> + + )} + + ), + }, + parameters: { + docs: { + description: { + story: `It is a common design pattern to include actionable buttons for a table row. The + Table Module exposes a \`rowActions\` prop to help. Hover over a row to view the + actions toolbar.\nActions can be different per row, in this example the row where fat = 9.0 has + an extra action "Amaze" and the row where description = "Cupcake" does not have a "Trash" action.`, + }, }, }, }; -export const RowSelection: ComponentStory = (args) => { - const tableRef = useRef(null); +const TemplateRowSelection: StoryFn = (args) => { + const tableRef = React.useRef(null); const initialRowSelection: RowSelectionState = { '0': true }; const [rowSelection, setRowSelection] = React.useState(initialRowSelection); const selectionColumn: TableConfiguration = { @@ -564,33 +210,7 @@ export const RowSelection: ComponentStory = (args) => { = (args) => { ); }; -RowSelection.parameters = { - docs: { - description: { - story: `It is a common design pattern to include multi-selection for a - table. The TableModule exposes properties like \`enableRowSelection\` to - enable this capability and use \`state.rowSelection\` to initialize row - selection state. In order to access row selection state, use the \`onRowSelectionChange\` - handler.`, +export const RowSelection: Story = { + render: TemplateRowSelection, + parameters: { + docs: { + description: { + story: `It is a common design pattern to include multi-selection for a + table. The TableModule exposes properties like \`enableRowSelection\` to + enable this capability and use \`state.rowSelection\` to initialize row + selection state. In order to access row selection state, use the \`onRowSelectionChange\` + handler.`, + }, }, }, }; -export const RowAddRemove = () => { - const [tableData, setTableData] = useState(data); - const tableRef = useRef(null); +const TemplateRowAddRemove: StoryFn = (args) => { + const [tableData, setTableData] = React.useState(data); + const tableRef = React.useRef(null); return (
( + rowActions={(_row: any, index?: number) => (