mirror of https://github.com/acid-info/lsd.git
feat: implement table component
This commit is contained in:
parent
e1622255cd
commit
eb51b59354
|
@ -18,7 +18,14 @@ import { IconButtonStyles } from '../IconButton/IconButton.styles'
|
|||
import { LsdIconStyles } from '../Icons/LsdIcon/LsdIcon.styles'
|
||||
import { ListBoxStyles } from '../ListBox/ListBox.styles'
|
||||
import { QuoteStyles } from '../Quote/Quote.styles'
|
||||
import { RadioButtonStyles } from '../RadioButton/RadioButton.styles'
|
||||
import { RadioButtonGroupStyles } from '../RadioButtonGroup/RadioButtonGroup.styles'
|
||||
import { TabItemStyles } from '../TabItem/TabItem.styles'
|
||||
import { TableStyles } from '../Table/Table.styles'
|
||||
import { TableBodyStyles } from '../TableBody/TableBody.styles'
|
||||
import { TableHeaderStyles } from '../TableHeader/TableHeader.styles'
|
||||
import { TableItemStyles } from '../TableItem/TableItem.styles'
|
||||
import { TableRowStyles } from '../TableRow/TableRow.styles'
|
||||
import { TabsStyles } from '../Tabs/Tabs.styles'
|
||||
import { TagStyles } from '../Tag/Tag.styles'
|
||||
import { TextFieldStyles } from '../TextField/TextField.styles'
|
||||
|
@ -50,6 +57,13 @@ const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
|
|||
CollapseHeaderStyles,
|
||||
CheckboxGroupStyles,
|
||||
BadgeStyles,
|
||||
RadioButtonStyles,
|
||||
RadioButtonGroupStyles,
|
||||
TableStyles,
|
||||
TableHeaderStyles,
|
||||
TableBodyStyles,
|
||||
TableItemStyles,
|
||||
TableRowStyles,
|
||||
]
|
||||
|
||||
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { LsdIcon } from '../LsdIcon'
|
||||
|
||||
export const RemoveIcon = LsdIcon(
|
||||
(props) => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.32812 7.58329V6.41663H11.6581V7.58329H2.32812Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
{ filled: true },
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
export * from './RemoveIcon'
|
|
@ -19,3 +19,4 @@ export * from './SearchIcon'
|
|||
export * from './PickIcon'
|
||||
export * from './RadioButtonIcon'
|
||||
export * from './RadioButtonFilledIcon'
|
||||
export * from './RemoveIcon'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const tableClasses = {
|
||||
root: `lsd-table`,
|
||||
|
||||
small: 'lsd-table--small',
|
||||
medium: 'lsd-table--medium',
|
||||
large: 'lsd-table--large',
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import { DropdownOption } from '../Dropdown'
|
||||
import { TableProps } from './Table'
|
||||
|
||||
export type TableContextType = {
|
||||
size?: TableProps['size']
|
||||
type?: 'default' | 'checkbox' | 'radio'
|
||||
value?: string
|
||||
headerOptions?: DropdownOption[]
|
||||
}
|
||||
|
||||
export const TableContext = React.createContext<TableContextType>(null as any)
|
||||
|
||||
export const useTableContext = () => React.useContext(TableContext)
|
|
@ -0,0 +1,126 @@
|
|||
import { Meta, Story } from '@storybook/react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from '../Button'
|
||||
import { Dropdown } from '../Dropdown'
|
||||
import { IconButton } from '../IconButton'
|
||||
import { AddIcon, RemoveIcon } from '../Icons'
|
||||
import { TableItem } from '../TableItem'
|
||||
import { TableRow } from '../TableRow'
|
||||
import { Table, TableProps } from './Table'
|
||||
|
||||
export default {
|
||||
title: 'Table',
|
||||
component: Table,
|
||||
argTypes: {
|
||||
size: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['small', 'medium', 'large'],
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['default', 'checkbox', 'radio'],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
||||
const content = (
|
||||
<TableRow>
|
||||
<TableItem>Content 1</TableItem>
|
||||
<TableItem>Content 2</TableItem>
|
||||
<TableItem>Content 3</TableItem>
|
||||
<TableItem>Content 4</TableItem>
|
||||
<TableItem>Content 5</TableItem>
|
||||
<TableItem>Content 6</TableItem>
|
||||
<TableItem>Content 7</TableItem>
|
||||
<TableItem>Content 8</TableItem>
|
||||
</TableRow>
|
||||
)
|
||||
|
||||
const header = (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 18px 28px',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '16px' }}>Table header</div>
|
||||
<div style={{ fontSize: '12px' }}>Table description</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const headerOptions = new Array(8).fill(null).map((value, index) => ({
|
||||
value: `${index}`,
|
||||
name: `Title ${index + 1}`,
|
||||
}))
|
||||
|
||||
export const Root: Story<TableProps> = ({ type, ...args }) => {
|
||||
const [rows, setRows] = useState(1)
|
||||
|
||||
const toolbar = (
|
||||
<>
|
||||
<Dropdown size="small" label="Label" options={headerOptions} />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => setRows((prev: number) => prev + 1)}
|
||||
size="small"
|
||||
>
|
||||
<AddIcon color="primary" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
setRows((prev: number) => (prev > 1 ? prev - 1 : prev))
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
<RemoveIcon color="primary" />
|
||||
</IconButton>
|
||||
<Button
|
||||
size="small"
|
||||
style={{
|
||||
height: '28px',
|
||||
background: 'rgb(var(--lsd-border-primary))',
|
||||
color: 'rgb(var(--lsd-icon-secondary))',
|
||||
}}
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 800 }}>
|
||||
<Table
|
||||
type={type}
|
||||
header={header}
|
||||
toolbar={toolbar}
|
||||
headerOptions={headerOptions}
|
||||
{...args}
|
||||
>
|
||||
<TableRow>
|
||||
{headerOptions.map((item) => (
|
||||
<TableItem>{item.name}</TableItem>
|
||||
))}
|
||||
</TableRow>
|
||||
{Array(rows)
|
||||
.fill(true)
|
||||
.map(() => content)}
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Root.args = {
|
||||
size: 'large',
|
||||
type: 'default',
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { css } from '@emotion/react'
|
||||
import { tableHeaderClasses } from '../TableHeader/TableHeader.classes'
|
||||
import { tableClasses } from './Table.classes'
|
||||
|
||||
export const TableStyles = css`
|
||||
.${tableClasses.root} {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.${tableClasses.root} > .${tableHeaderClasses.root} {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.${tableClasses.large} {
|
||||
}
|
||||
|
||||
.${tableClasses.medium} {
|
||||
}
|
||||
|
||||
.${tableClasses.small} {
|
||||
}
|
||||
`
|
|
@ -0,0 +1,40 @@
|
|||
import clsx from 'clsx'
|
||||
import React, { useState } from 'react'
|
||||
import { DropdownOption } from '../Dropdown'
|
||||
import { TableBody } from '../TableBody'
|
||||
import { TableHeader } from '../TableHeader'
|
||||
import { tableClasses } from './Table.classes'
|
||||
import { TableContext } from './Table.context'
|
||||
|
||||
export type TableProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'label'> & {
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
type?: 'default' | 'checkbox' | 'radio'
|
||||
headerOptions?: DropdownOption[]
|
||||
header?: React.ReactNode
|
||||
toolbar?: React.ReactNode
|
||||
}
|
||||
|
||||
export const Table: React.FC<TableProps> & {
|
||||
classes: typeof tableClasses
|
||||
} = ({
|
||||
size = 'large',
|
||||
type = 'default',
|
||||
headerOptions,
|
||||
header,
|
||||
toolbar,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<TableContext.Provider value={{ size, type, headerOptions }}>
|
||||
<div {...props} className={clsx(tableClasses.root, tableClasses[size])}>
|
||||
<TableHeader>{header}</TableHeader>
|
||||
<TableBody toolbar={toolbar} options={headerOptions}>
|
||||
{children}
|
||||
</TableBody>
|
||||
</div>
|
||||
</TableContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
Table.classes = tableClasses
|
|
@ -0,0 +1 @@
|
|||
export * from './Table'
|
|
@ -0,0 +1,10 @@
|
|||
export const tableBodyClasses = {
|
||||
root: `lsd-table-body`,
|
||||
|
||||
toolbar: `lsd-table-body__toolbar`,
|
||||
buttons: `lsd-table-body__buttons`,
|
||||
button: `lsd-table-body__button`,
|
||||
|
||||
container: `lsd-table-container`,
|
||||
row: `lsd-table-body__row`,
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Meta, Story } from '@storybook/react'
|
||||
import { TableBody, TableBodyProps } from './TableBody'
|
||||
|
||||
export default {
|
||||
title: 'TableBody',
|
||||
component: TableBody,
|
||||
argTypes: {
|
||||
size: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['small', 'medium', 'large'],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
||||
export const Root: Story<TableBodyProps & { body: string }> = ({
|
||||
body,
|
||||
...args
|
||||
}) => (
|
||||
<div style={{ maxWidth: 800 }}>
|
||||
<TableBody {...args}>{body}</TableBody>
|
||||
</div>
|
||||
)
|
||||
|
||||
Root.args = {
|
||||
size: 'large',
|
||||
options: new Array(4).fill(null).map((value, index) => ({
|
||||
value: `${index}`,
|
||||
name: `Title ${index + 1}`,
|
||||
})),
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { css } from '@emotion/react'
|
||||
import { tableBodyClasses } from './TableBody.classes'
|
||||
|
||||
export const TableBodyStyles = css`
|
||||
.${tableBodyClasses.root} {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
table tr:first-child td label:has(input[type='radio']) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.${tableBodyClasses.toolbar} {
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.${tableBodyClasses.buttons} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.${tableBodyClasses.button} {
|
||||
height: 28px;
|
||||
background: rgb(var(--lsd-border-primary));
|
||||
color: rgb(var(--lsd-icon-secondary));
|
||||
}
|
||||
|
||||
.${tableBodyClasses.container} {
|
||||
display: table;
|
||||
width: 100%;
|
||||
/* display: inline-grid;
|
||||
grid-template-columns: auto auto auto auto; */
|
||||
|
||||
/* tr,
|
||||
td {
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
} */
|
||||
}
|
||||
`
|
|
@ -0,0 +1,36 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { DropdownOption } from '../Dropdown'
|
||||
import { tableBodyClasses } from './TableBody.classes'
|
||||
|
||||
export type TableBodyProps = Omit<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
'buttonLabel'
|
||||
> & {
|
||||
options?: DropdownOption[]
|
||||
buttonLabel?: 'Button'
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
toolbar?: React.ReactNode
|
||||
}
|
||||
|
||||
export const TableBody: React.FC<TableBodyProps> & {
|
||||
classes: typeof tableBodyClasses
|
||||
} = ({
|
||||
options = [],
|
||||
size: _size = 'large',
|
||||
buttonLabel = 'Button',
|
||||
toolbar,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<div {...props} className={clsx(props.className, tableBodyClasses.root)}>
|
||||
{toolbar && (
|
||||
<div className={clsx(tableBodyClasses.toolbar)}>{toolbar}</div>
|
||||
)}
|
||||
<table>{children}</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TableBody.classes = tableBodyClasses
|
|
@ -0,0 +1 @@
|
|||
export * from './TableBody'
|
|
@ -0,0 +1,3 @@
|
|||
export const tableHeaderClasses = {
|
||||
root: `lsd-table-header`,
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { Meta, Story } from '@storybook/react'
|
||||
import { TableHeader, TableHeaderProps } from './TableHeader'
|
||||
|
||||
export default {
|
||||
title: 'TableHeader',
|
||||
component: TableHeader,
|
||||
argTypes: {
|
||||
size: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['small', 'medium', 'large'],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
||||
export const Root: Story<TableHeaderProps & { title: string }> = ({
|
||||
title,
|
||||
...args
|
||||
}) => (
|
||||
<div style={{ maxWidth: '800px' }}>
|
||||
<TableHeader {...args}>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 18px 28px',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '16px' }}>Table header</div>
|
||||
<div style={{ fontSize: '12px' }}>Table description</div>
|
||||
</div>
|
||||
</TableHeader>
|
||||
</div>
|
||||
)
|
||||
|
||||
Root.args = {
|
||||
size: 'large',
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { css } from '@emotion/react'
|
||||
import { tableHeaderClasses } from './TableHeader.classes'
|
||||
|
||||
export const TableHeaderStyles = css`
|
||||
.${tableHeaderClasses.root} {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
}
|
||||
`
|
|
@ -0,0 +1,22 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { tableHeaderClasses } from './TableHeader.classes'
|
||||
|
||||
export type TableHeaderProps = Omit<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
'label'
|
||||
> & {
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
}
|
||||
|
||||
export const TableHeader: React.FC<TableHeaderProps> & {
|
||||
classes: typeof tableHeaderClasses
|
||||
} = ({ size: _size = 'large', children, ...props }) => {
|
||||
return (
|
||||
<div {...props} className={clsx(props.className, tableHeaderClasses.root)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TableHeader.classes = tableHeaderClasses
|
|
@ -0,0 +1 @@
|
|||
export * from './TableHeader'
|
|
@ -0,0 +1,7 @@
|
|||
export const tableItemClasses = {
|
||||
root: `lsd-table-item`,
|
||||
|
||||
large: `lsd-table-item--large`,
|
||||
medium: `lsd-table-item--medium`,
|
||||
small: `lsd-table-item--small`,
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { Meta, Story } from '@storybook/react'
|
||||
import { TableItem, TableItemProps } from './TableItem'
|
||||
|
||||
export default {
|
||||
title: 'TableItem',
|
||||
component: TableItem,
|
||||
argTypes: {
|
||||
size: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['small', 'medium', 'large'],
|
||||
},
|
||||
defaultValue: 'large',
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
||||
export const Root: Story<TableItemProps> = ({ ...args }) => {
|
||||
return <TableItem {...args}>Content</TableItem>
|
||||
}
|
||||
|
||||
Root.args = {}
|
|
@ -0,0 +1,33 @@
|
|||
import { css } from '@emotion/react'
|
||||
import { tableItemClasses } from './TableItem.classes'
|
||||
|
||||
export const TableItemStyles = css`
|
||||
.${tableItemClasses.root} {
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
}
|
||||
|
||||
.${tableItemClasses.root}:has(> label) {
|
||||
width: 40px;
|
||||
input {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: auto;
|
||||
}
|
||||
span {
|
||||
margin-left: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.${tableItemClasses.large} {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.${tableItemClasses.medium} {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.${tableItemClasses.small} {
|
||||
padding: 6px;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,29 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { useTableContext } from '../Table/Table.context'
|
||||
import { tableItemClasses } from './TableItem.classes'
|
||||
|
||||
export type TableItemProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
size?: 'large' | 'medium' | 'small'
|
||||
}
|
||||
|
||||
export const TableItem: React.FC<TableItemProps> & {
|
||||
classes: typeof tableItemClasses
|
||||
} = ({ size: _size = 'large', children, ...props }) => {
|
||||
const table = useTableContext()
|
||||
const size = table?.size ?? _size
|
||||
return (
|
||||
<td
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
tableItemClasses.root,
|
||||
tableItemClasses[size],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
)
|
||||
}
|
||||
|
||||
TableItem.classes = tableItemClasses
|
|
@ -0,0 +1 @@
|
|||
export * from './TableItem'
|
|
@ -0,0 +1,3 @@
|
|||
export const tableRowClasses = {
|
||||
root: `lsd-table-row`,
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Meta, Story } from '@storybook/react'
|
||||
import { TableRow, TableRowProps } from './TableRow'
|
||||
|
||||
export default {
|
||||
title: 'TableRow',
|
||||
component: TableRow,
|
||||
argTypes: {
|
||||
type: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['default', 'checkbox', 'radio'],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
||||
export const Root: Story<TableRowProps> = ({ ...args }) => {
|
||||
return <TableRow {...args}>Content</TableRow>
|
||||
}
|
||||
|
||||
Root.args = {}
|
|
@ -0,0 +1,8 @@
|
|||
import { css } from '@emotion/react'
|
||||
import { tableRowClasses } from './TableRow.classes'
|
||||
|
||||
export const TableRowStyles = css`
|
||||
.${tableRowClasses.root} {
|
||||
align-items: center;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,42 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { Checkbox } from '../Checkbox'
|
||||
import { RadioButton } from '../RadioButton'
|
||||
import { useTableContext } from '../Table/Table.context'
|
||||
import { tableItemClasses } from '../TableItem/TableItem.classes'
|
||||
import { tableRowClasses } from './TableRow.classes'
|
||||
|
||||
export type TableRowProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
size?: 'large' | 'medium' | 'small'
|
||||
type?: 'default' | 'checkbox' | 'radio'
|
||||
}
|
||||
|
||||
export const TableRow: React.FC<TableRowProps> & {
|
||||
classes: typeof tableRowClasses
|
||||
} = ({
|
||||
size: _size = 'large',
|
||||
type: _type = 'default',
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const table = useTableContext()
|
||||
const type = table?.type ?? _type
|
||||
|
||||
return (
|
||||
<tr {...props} className={clsx(props.className, tableRowClasses.root)}>
|
||||
{type === 'checkbox' && (
|
||||
<td className={tableItemClasses.root}>
|
||||
<Checkbox />
|
||||
</td>
|
||||
)}
|
||||
{type === 'radio' && (
|
||||
<td className={tableItemClasses.root}>
|
||||
<RadioButton value="1" />
|
||||
</td>
|
||||
)}
|
||||
{children}
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
TableRow.classes = tableRowClasses
|
|
@ -0,0 +1 @@
|
|||
export * from './TableRow'
|
|
@ -21,3 +21,8 @@ export * from './components/Theme'
|
|||
export * from './components/Checkbox'
|
||||
export * from './components/CheckboxGroup'
|
||||
export * from './components/Badge'
|
||||
export * from './components/Table'
|
||||
export * from './components/TableHeader'
|
||||
export * from './components/TableBody'
|
||||
export * from './components/TableItem'
|
||||
export * from './components/TableRow'
|
||||
|
|
Loading…
Reference in New Issue