From 067c2b0235bea18ac04cf50a41626fe55b6761dc Mon Sep 17 00:00:00 2001 From: jongomez Date: Tue, 3 Oct 2023 12:57:31 +0100 Subject: [PATCH] feat: add table footer component and minor CSS updates --- .../components/CSSBaseline/CSSBaseline.tsx | 2 + .../src/components/Table/Table.stories.tsx | 12 +- .../lsd-react/src/components/Table/Table.tsx | 14 +++ .../components/TableBody/TableBody.styles.ts | 2 +- .../TableFooter/TableFooter.classes.ts | 8 ++ .../TableFooter/TableFooter.stories.tsx | 47 ++++++++ .../TableFooter/TableFooter.styles.ts | 102 +++++++++++++++++ .../components/TableFooter/TableFooter.tsx | 107 ++++++++++++++++++ .../src/components/TableFooter/index.ts | 1 + packages/lsd-react/src/index.ts | 1 + 10 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 packages/lsd-react/src/components/TableFooter/TableFooter.classes.ts create mode 100644 packages/lsd-react/src/components/TableFooter/TableFooter.stories.tsx create mode 100644 packages/lsd-react/src/components/TableFooter/TableFooter.styles.ts create mode 100644 packages/lsd-react/src/components/TableFooter/TableFooter.tsx create mode 100644 packages/lsd-react/src/components/TableFooter/index.ts diff --git a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx index a68261c..6a82b14 100644 --- a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx +++ b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx @@ -32,6 +32,7 @@ import { TagStyles } from '../Tag/Tag.styles' import { TextFieldStyles } from '../TextField/TextField.styles' import { defaultThemes, Theme, withTheme } from '../Theme' import { TypographyStyles } from '../Typography/Typography.styles' +import { TableFooterStyles } from '../TableFooter/TableFooter.styles' const componentStyles: Array | SerializedStyles> = [ @@ -66,6 +67,7 @@ const componentStyles: Array | SerializedStyles> = TableBodyStyles, TableItemStyles, TableRowStyles, + TableFooterStyles, ] export const CSSBaseline: React.FC<{ theme?: Theme }> = ({ diff --git a/packages/lsd-react/src/components/Table/Table.stories.tsx b/packages/lsd-react/src/components/Table/Table.stories.tsx index ced4e17..cfe2ff2 100644 --- a/packages/lsd-react/src/components/Table/Table.stories.tsx +++ b/packages/lsd-react/src/components/Table/Table.stories.tsx @@ -57,8 +57,13 @@ const headerOptions = new Array(8).fill(null).map((value, index) => ({ name: `Title ${index + 1}`, })) -export const Root: Story = ({ type, ...args }) => { +export const Root: Story = ({ type, pages, ...args }) => { const [rows, setRows] = useState(1) + const [footerContent, setFooterContent] = useState('Footer content goes here') + + const onPageChange = (page: number) => { + setFooterContent(`Page ${page} of ${pages}`) + } const toolbar = ( <> @@ -105,7 +110,10 @@ export const Root: Story = ({ type, ...args }) => { header={header} toolbar={toolbar} headerOptions={headerOptions} + footerContent={footerContent} + pages={pages} {...args} + onPageChange={onPageChange} > {headerOptions.map((item) => ( @@ -123,4 +131,6 @@ export const Root: Story = ({ type, ...args }) => { Root.args = { size: 'large', type: 'default', + hasFooter: true, + pages: 10, } diff --git a/packages/lsd-react/src/components/Table/Table.tsx b/packages/lsd-react/src/components/Table/Table.tsx index f19b829..4a73ca1 100644 --- a/packages/lsd-react/src/components/Table/Table.tsx +++ b/packages/lsd-react/src/components/Table/Table.tsx @@ -10,6 +10,7 @@ import { TableBody } from '../TableBody' import { TableHeader } from '../TableHeader' import { tableClasses } from './Table.classes' import { TableContext } from './Table.context' +import { TableFooter } from '../TableFooter' export type TableProps = CommonProps & Omit, 'label'> & { @@ -18,6 +19,10 @@ export type TableProps = CommonProps & headerOptions?: DropdownOption[] header?: React.ReactNode toolbar?: React.ReactNode + hasFooter?: boolean + pages: number + onPageChange?: (page: number) => void + footerContent?: React.ReactNode } export const Table: React.FC & { @@ -28,7 +33,11 @@ export const Table: React.FC & { headerOptions, header, toolbar, + hasFooter, + onPageChange, + pages, children, + footerContent, ...props }) => { const commonProps = useCommonProps(props) @@ -47,6 +56,11 @@ export const Table: React.FC & { {children} + {hasFooter && !!footerContent && ( + + {footerContent} + + )} ) diff --git a/packages/lsd-react/src/components/TableBody/TableBody.styles.ts b/packages/lsd-react/src/components/TableBody/TableBody.styles.ts index 0d84517..030b017 100644 --- a/packages/lsd-react/src/components/TableBody/TableBody.styles.ts +++ b/packages/lsd-react/src/components/TableBody/TableBody.styles.ts @@ -18,7 +18,7 @@ export const TableBodyStyles = css` .${tableBodyClasses.toolbar} { box-sizing: border-box; - padding: 10px; + padding: 15px; border: 1px solid rgb(var(--lsd-border-primary)); border-bottom: none; display: flex; diff --git a/packages/lsd-react/src/components/TableFooter/TableFooter.classes.ts b/packages/lsd-react/src/components/TableFooter/TableFooter.classes.ts new file mode 100644 index 0000000..c046a2d --- /dev/null +++ b/packages/lsd-react/src/components/TableFooter/TableFooter.classes.ts @@ -0,0 +1,8 @@ +export const tableFooterClasses = { + root: `lsd-table-footer`, + large: 'lsd-table-footer--large', + medium: 'lsd-table-footer--medium', + small: 'lsd-table-footer--small', + content: 'lsd-table-footer__content', + paginationControls: 'lsd-table-footer__pagination-controls', +} diff --git a/packages/lsd-react/src/components/TableFooter/TableFooter.stories.tsx b/packages/lsd-react/src/components/TableFooter/TableFooter.stories.tsx new file mode 100644 index 0000000..f92a158 --- /dev/null +++ b/packages/lsd-react/src/components/TableFooter/TableFooter.stories.tsx @@ -0,0 +1,47 @@ +import { Meta, Story } from '@storybook/react' +import { TableFooter, TableFooterProps } from './TableFooter' +import { useState } from 'react' +import { pickCommonProps } from '../../utils/useCommonProps' + +export default { + title: 'TableFooter', + component: TableFooter, + argTypes: { + size: { + type: { + name: 'enum', + value: ['small', 'medium', 'large'], + }, + }, + }, +} as Meta + +export const Root: Story = ({ + size, + pages, + ...props +}) => { + const [content, setContent] = useState('Footer content goes here') + + const onPageChange = (page: number) => { + setContent(`Page ${page} of ${pages}`) + } + + return ( +
+ + {content} + +
+ ) +} + +Root.args = { + size: 'large', + pages: 10, +} diff --git a/packages/lsd-react/src/components/TableFooter/TableFooter.styles.ts b/packages/lsd-react/src/components/TableFooter/TableFooter.styles.ts new file mode 100644 index 0000000..06326a4 --- /dev/null +++ b/packages/lsd-react/src/components/TableFooter/TableFooter.styles.ts @@ -0,0 +1,102 @@ +import { css } from '@emotion/react' +import { tableFooterClasses } from './TableFooter.classes' +import { iconButtonClasses } from '../IconButton/IconButton.classes' +import { dropdownClasses } from '../Dropdown/Dropdown.classes' +import { typographyClasses } from '../Typography/Typography.classes' + +export const TableFooterStyles = css` + .${tableFooterClasses.root} { + display: flex; + box-sizing: border-box; + border: 1px solid rgb(var(--lsd-border-primary)); + + .${dropdownClasses.root} { + width: auto; + } + + .${dropdownClasses.buttonContainer} { + box-sizing: border-box; + border-left: 0px !important; + border-right: 0px !important; + } + + .${dropdownClasses.trigger} { + padding-left: 16px !important; + padding-right: 16px !important; + justify-content: center; + } + + .${dropdownClasses.icons} { + padding-left: 10px; + } + } + + .${tableFooterClasses.content} { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; + padding: 0 16px; + } + + .${tableFooterClasses.paginationControls} { + display: flex; + justify-content: center; + align-items: center; + } + + .${tableFooterClasses.large} { + height: 64px; + + .${iconButtonClasses.large} { + height: 32px; + width: 32px; + } + + .${dropdownClasses.buttonContainer} { + height: 32px; + } + + .${tableFooterClasses.paginationControls} { + padding: 0 16px; + } + } + + .${tableFooterClasses.medium} { + height: 60px; + + .${iconButtonClasses.medium} { + height: 32px; + width: 32px; + } + + .${dropdownClasses.buttonContainer} { + height: 32px; + } + + .${tableFooterClasses.paginationControls} { + padding: 0 14px; + } + } + + .${tableFooterClasses.small} { + height: 56px; + + .${iconButtonClasses.small} { + height: 28px; + width: 28px; + } + + .${typographyClasses.label1} { + font-size: 12px; + } + + .${dropdownClasses.buttonContainer} { + height: 28px; + } + + .${tableFooterClasses.paginationControls} { + padding: 0 12px; + } + } +` diff --git a/packages/lsd-react/src/components/TableFooter/TableFooter.tsx b/packages/lsd-react/src/components/TableFooter/TableFooter.tsx new file mode 100644 index 0000000..e95fa53 --- /dev/null +++ b/packages/lsd-react/src/components/TableFooter/TableFooter.tsx @@ -0,0 +1,107 @@ +import clsx from 'clsx' +import React, { useEffect, useReducer, useState } from 'react' +import { + CommonProps, + omitCommonProps, + useCommonProps, +} from '../../utils/useCommonProps' +import { tableFooterClasses } from './TableFooter.classes' +import { table } from 'console' +import { IconButton } from '../IconButton' +import { NavigateBeforeIcon, NavigateNextIcon } from '../Icons' +import { Dropdown, DropdownOption } from '../Dropdown' + +// 1. Define the reducer and its actions +type Action = + | { type: 'SET_PAGE'; payload: string | string[] } + | { type: 'INCREMENT_PAGE' } + | { type: 'DECREMENT_PAGE' } + +export type TableFooterProps = CommonProps & + Omit, 'label'> & { + size?: 'small' | 'medium' | 'large' + pages: number + onPageChange?: (page: number) => void + } + +export const TableFooter: React.FC & { + classes: typeof tableFooterClasses +} = ({ size = 'large', pages, onPageChange, children, ...props }) => { + const commonProps = useCommonProps(props) + + const [pageNumber, dispatch] = useReducer( + (state: number, action: Action): number => { + switch (action.type) { + case 'SET_PAGE': + const pageNumber = parseInt(action.payload as string) + // Ensure the page is between 1 and the maximum page number + return Math.max(1, Math.min(pageNumber, pages)) + case 'INCREMENT_PAGE': + // Ensure the page doesn't exceed the maximum + return state < pages ? state + 1 : state + case 'DECREMENT_PAGE': + // Ensure the page doesn't go below 1 + return state > 1 ? state - 1 : state + default: + return state + } + }, + 1, + ) + + useEffect(() => { + if (onPageChange) { + onPageChange(pageNumber) + } + }, [pageNumber, onPageChange]) + + const dropdownOptions: DropdownOption[] = + pages > 1 + ? new Array(pages).fill(null).map((value, index) => ({ + value: `${index + 1}`, + name: `${index + 1}/${pages}`, + })) + : [] + + return ( +
+
{children}
+ + {pages > 1 && ( +
+ dispatch({ type: 'DECREMENT_PAGE' })} + > + + + + dispatch({ type: 'SET_PAGE', payload: newPage }) + } + value={dropdownOptions[pageNumber - 1].value} + /> + dispatch({ type: 'INCREMENT_PAGE' })} + > + + +
+ )} +
+ ) +} + +TableFooter.classes = tableFooterClasses diff --git a/packages/lsd-react/src/components/TableFooter/index.ts b/packages/lsd-react/src/components/TableFooter/index.ts new file mode 100644 index 0000000..c4f3329 --- /dev/null +++ b/packages/lsd-react/src/components/TableFooter/index.ts @@ -0,0 +1 @@ +export * from './TableFooter' diff --git a/packages/lsd-react/src/index.ts b/packages/lsd-react/src/index.ts index 31605ee..4136895 100644 --- a/packages/lsd-react/src/index.ts +++ b/packages/lsd-react/src/index.ts @@ -25,6 +25,7 @@ export * from './components/TableBody' export * from './components/TableHeader' export * from './components/TableItem' export * from './components/TableRow' +export * from './components/TableFooter' export * from './components/Tabs' export * from './components/Tag' export * from './components/TextField'