feat: add table footer component and minor CSS updates

This commit is contained in:
jongomez 2023-10-03 12:57:31 +01:00
parent a7bfa14d9d
commit 067c2b0235
10 changed files with 294 additions and 2 deletions

View File

@ -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<ReturnType<typeof withTheme> | SerializedStyles> =
[
@ -66,6 +67,7 @@ const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
TableBodyStyles,
TableItemStyles,
TableRowStyles,
TableFooterStyles,
]
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({

View File

@ -57,8 +57,13 @@ const headerOptions = new Array(8).fill(null).map((value, index) => ({
name: `Title ${index + 1}`,
}))
export const Root: Story<TableProps> = ({ type, ...args }) => {
export const Root: Story<TableProps> = ({ 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<TableProps> = ({ type, ...args }) => {
header={header}
toolbar={toolbar}
headerOptions={headerOptions}
footerContent={footerContent}
pages={pages}
{...args}
onPageChange={onPageChange}
>
<TableRow>
{headerOptions.map((item) => (
@ -123,4 +131,6 @@ export const Root: Story<TableProps> = ({ type, ...args }) => {
Root.args = {
size: 'large',
type: 'default',
hasFooter: true,
pages: 10,
}

View File

@ -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<React.HTMLAttributes<HTMLDivElement>, '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<TableProps> & {
@ -28,7 +33,11 @@ export const Table: React.FC<TableProps> & {
headerOptions,
header,
toolbar,
hasFooter,
onPageChange,
pages,
children,
footerContent,
...props
}) => {
const commonProps = useCommonProps(props)
@ -47,6 +56,11 @@ export const Table: React.FC<TableProps> & {
<TableBody toolbar={toolbar} options={headerOptions}>
{children}
</TableBody>
{hasFooter && !!footerContent && (
<TableFooter onPageChange={onPageChange} pages={pages} size={size}>
{footerContent}
</TableFooter>
)}
</div>
</TableContext.Provider>
)

View File

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

View File

@ -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',
}

View File

@ -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<TableFooterProps & { title: string }> = ({
size,
pages,
...props
}) => {
const [content, setContent] = useState('Footer content goes here')
const onPageChange = (page: number) => {
setContent(`Page ${page} of ${pages}`)
}
return (
<div style={{ maxWidth: '800px' }}>
<TableFooter
{...pickCommonProps(props)}
pages={pages}
onPageChange={onPageChange}
size={size}
>
{content}
</TableFooter>
</div>
)
}
Root.args = {
size: 'large',
pages: 10,
}

View File

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

View File

@ -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<React.HTMLAttributes<HTMLDivElement>, 'label'> & {
size?: 'small' | 'medium' | 'large'
pages: number
onPageChange?: (page: number) => void
}
export const TableFooter: React.FC<TableFooterProps> & {
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 (
<div
{...omitCommonProps(props)}
className={clsx(
commonProps.className,
props.className,
tableFooterClasses.root,
tableFooterClasses[size],
)}
>
<div className={tableFooterClasses.content}>{children}</div>
{pages > 1 && (
<div className={tableFooterClasses.paginationControls}>
<IconButton
size={size}
onClick={() => dispatch({ type: 'DECREMENT_PAGE' })}
>
<NavigateBeforeIcon />
</IconButton>
<Dropdown
{...commonProps}
size={size}
options={dropdownOptions}
onChange={(newPage) =>
dispatch({ type: 'SET_PAGE', payload: newPage })
}
value={dropdownOptions[pageNumber - 1].value}
/>
<IconButton
size={size}
onClick={() => dispatch({ type: 'INCREMENT_PAGE' })}
>
<NavigateNextIcon />
</IconButton>
</div>
)}
</div>
)
}
TableFooter.classes = tableFooterClasses

View File

@ -0,0 +1 @@
export * from './TableFooter'

View File

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