feat: implement table component

This commit is contained in:
jinhojang6 2023-03-08 04:14:43 +09:00 committed by jeangovil
parent e1622255cd
commit eb51b59354
31 changed files with 623 additions and 0 deletions

View File

@ -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 }> = ({

View File

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

View File

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

View File

@ -19,3 +19,4 @@ export * from './SearchIcon'
export * from './PickIcon'
export * from './RadioButtonIcon'
export * from './RadioButtonFilledIcon'
export * from './RemoveIcon'

View File

@ -0,0 +1,7 @@
export const tableClasses = {
root: `lsd-table`,
small: 'lsd-table--small',
medium: 'lsd-table--medium',
large: 'lsd-table--large',
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}`,
})),
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
export const tableHeaderClasses = {
root: `lsd-table-header`,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
export const tableRowClasses = {
root: `lsd-table-row`,
}

View File

@ -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 = {}

View File

@ -0,0 +1,8 @@
import { css } from '@emotion/react'
import { tableRowClasses } from './TableRow.classes'
export const TableRowStyles = css`
.${tableRowClasses.root} {
align-items: center;
}
`

View File

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

View File

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

View File

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