Skip to content

Commit

Permalink
feat(tables): add $isLoading prop to SimpleTable.Td
Browse files Browse the repository at this point in the history
- add `$isLoading` prop to `SimpleTable.Td (& `TableWithSelectableRows.Td`)
- add `$width` prop to `SimpleTable.Th` (& `TableWithSelectableRows.Th`)
- add & expose `SimpleTable.CellLoader` (& `TableWithSelectableRows.CellLoader`)
- fix ellipsis when `SimpleTable.Th` width is set while not set in `SimpleTable.Td` (// `TableWithSelectableRows`)
- deprecate `$width` prop from `TableWithSelectableRows.Td`
  • Loading branch information
ivangabriele committed May 27, 2024
1 parent f655623 commit 6cc137e
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 50 deletions.
1 change: 1 addition & 0 deletions config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
'@fields/*': ['fields/*'],
'@hooks/*': ['hooks/*'],
'@libs/*': ['libs/*'],
'@tables/*': ['tables/*'],
'@types_/*': ['types/*'],
'@utils/*': ['utils/*']
},
Expand Down
29 changes: 29 additions & 0 deletions src/tables/SimpleTable/CellLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled, { keyframes } from 'styled-components'

const cellLoaderAnimation = keyframes`
from {
left: -100%;
}
to {
left: 100%;
}
`
export const CellLoader = styled.div`
background: ${p => p.theme.color.gainsboro};
height: 18px;
overflow: hidden;
position: relative;
&:before {
animation: ${cellLoaderAnimation} 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
background: linear-gradient(to right, transparent 0%, ${p => p.theme.color.white} 50%, transparent 100%);
content: '';
display: block;
height: 100%;
left: -100%;
position: absolute;
top: 0;
width: 100%;
}
`
25 changes: 25 additions & 0 deletions src/tables/SimpleTable/Td.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from 'styled-components'

import { CellLoader } from './CellLoader'

import type { TdHTMLAttributes } from 'react'

type TdProps = TdHTMLAttributes<HTMLTableCellElement> & {
$isCenter?: boolean | undefined
$isLoading?: boolean | undefined
}
export const Td = styled.td.attrs<TdProps, TdProps>(props => ({
children: props.$isLoading ? <CellLoader /> : props.children
}))`
border-bottom: 1px solid ${p => p.theme.color.lightGray};
border-right: 1px solid ${p => p.theme.color.lightGray};
color: ${p => p.theme.color.gunMetal};
font-size: 13px;
font-weight: 500;
max-width: 0;
overflow: hidden;
padding: 10px;
text-align: ${p => (p.$isCenter ? 'center' : 'left')};
text-overflow: ellipsis;
white-space: nowrap;
`
55 changes: 25 additions & 30 deletions src/tables/SimpleTable.tsx → src/tables/SimpleTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
import classnames from 'classnames'
import styled from 'styled-components'

import { CellLoader } from './CellLoader'
import { Td } from './Td'

import type { TableHTMLAttributes } from 'react'

const Table = styled.table.attrs<TableHTMLAttributes<HTMLTableElement>>(props => ({
className: classnames('Table-SimpleTable', props.className)
}))`
width: 100%;
table-layout: auto;
overflow: auto;
border-collapse: separate;
overflow: auto;
table-layout: auto;
`

const Head = styled.thead`
position: sticky;
top: 0;
z-index: 1;
th:first-child {
> th:first-child {
border-left: 1px solid ${p => p.theme.color.lightGray};
}
`

const SortContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
cursor: default;
&.cursor-pointer {
cursor: pointer;
}
`
const Th = styled.th`
const Th = styled.th<{
$width?: number | undefined
}>`
background-color: ${p => p.theme.color.gainsboro};
border-top: 1px solid ${p => p.theme.color.lightGray};
border-bottom: 1px solid ${p => p.theme.color.lightGray};
Expand All @@ -42,35 +37,35 @@ const Th = styled.th`
padding: 10px;
overflow: hidden;
text-overflow: ellipsis;
${p => !!p.$width && `width: ${p.$width}px;`}
white-space: nowrap;
`

const SortContainer = styled.div`
align-items: center;
cursor: default;
display: flex;
justify-content: space-between;
&.cursor-pointer {
cursor: pointer;
}
`

const BodyTr = styled.tr`
:hover {
&:hover {
> td {
background-color: ${p => p.theme.color.blueYonder25};
}
}
td:first-child {
> td:first-child {
border-left: 1px solid ${p => p.theme.color.lightGray};
}
`

const Td = styled.td<{ $isCenter?: boolean }>`
font-size: 13px;
font-weight: 500;
color: ${p => p.theme.color.gunMetal};
text-align: ${p => (p.$isCenter ? 'center' : 'left')};
border-bottom: 1px solid ${p => p.theme.color.lightGray};
border-right: 1px solid ${p => p.theme.color.lightGray};
overflow: hidden;
padding: 10px;
text-overflow: ellipsis;
white-space: nowrap;
`

export const SimpleTable = {
BodyTr,
CellLoader,
Head,
SortContainer,
Table,
Expand Down
36 changes: 21 additions & 15 deletions src/tables/TableWithSelectableRows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,64 @@ import styled from 'styled-components'
import { RowCheckbox } from './RowCheckbox'
import { SimpleTable } from '../SimpleTable'

export { RowCheckbox }

const Table = styled(SimpleTable.Table)`
border-collapse: separate;
border-spacing: 0 5px;
table-layout: fixed;
`

const Head = styled(SimpleTable.Head)`
th:last-child {
> th:last-child {
border-right: 1px solid ${p => p.theme.color.lightGray};
}
`

const SortContainer = styled(SimpleTable.SortContainer)`
justify-content: start;
gap: 8px;
`
const Th = styled(SimpleTable.Th)<{ $width: number }>`
const Th = styled(SimpleTable.Th)`
background-color: ${p => p.theme.color.white};
border-top: 1px solid ${p => p.theme.color.lightGray};
border-bottom: 1px solid ${p => p.theme.color.lightGray};
border-right: none;
padding: 2px 16px;
width: ${p => p.$width}px;
`

const SortContainer = styled(SimpleTable.SortContainer)`
gap: 8px;
justify-content: start;
`

const BodyTr = styled(SimpleTable.BodyTr)<{ $isHighlighted?: boolean }>`
td:first-child {
> td:first-child {
border-left: ${p =>
p.$isHighlighted ? `2px solid ${p.theme.color.blueGray}` : `1px solid ${p.theme.color.lightGray}`};
}
td:last-child {
> td:last-child {
border-right: ${p =>
p.$isHighlighted ? `2px solid ${p.theme.color.blueGray}` : `1px solid ${p.theme.color.lightGray}`};
overflow: visible;
}
`

const Td = styled(SimpleTable.Td)<{ $hasRightBorder: boolean; $isHighlighted?: boolean; $width: number }>`
const Td = styled(SimpleTable.Td)<{
$hasRightBorder: boolean
$isHighlighted?: boolean
// TODO This should be removed, a table column width should only be set via its `th` width.
/** @deprecated Will be removed in the next major version. Use `Td.$width` instead to set columns width. */
$width?: number | undefined
}>`
background-color: ${p => p.theme.color.cultured};
border-top: ${p =>
p.$isHighlighted ? `2px solid ${p.theme.color.blueGray}` : `1px solid ${p.theme.color.lightGray}`};
border-bottom: ${p =>
p.$isHighlighted ? `2px solid ${p.theme.color.blueGray}` : `1px solid ${p.theme.color.lightGray}`};
border-right: none;
padding: 4px 16px;
border-right: ${p => (p.$hasRightBorder ? `1px solid ${p.theme.color.lightGray}` : '')};
border-top: ${p =>
p.$isHighlighted ? `2px solid ${p.theme.color.blueGray}` : `1px solid ${p.theme.color.lightGray}`};
padding: 4px 16px;
width: ${p => p.$width}px;
`

export const TableWithSelectableRows = {
BodyTr,
CellLoader: SimpleTable.CellLoader,
Head,
RowCheckbox,
SortContainer,
Expand Down
2 changes: 1 addition & 1 deletion stories/fields/CheckPicker/WithCustomSearch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const meta: Meta<CheckPickerProps<{}>> = {

export default meta

export function _CheckPickerWithCustomSearch(props: CheckPickerProps<Specy>) {
export function WithCustomSearch(props: CheckPickerProps<Specy>) {
const optionsRef = useRef(
(SPECIES as Specy[]).map(specy => ({
label: `${specy.code} - ${specy.name}`,
Expand Down
4 changes: 2 additions & 2 deletions stories/fields/MultiCascader/WithThreeColumns.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const args: MultiCascaderProps<FakeCity> = {
/* eslint-disable sort-keys-fix/sort-keys-fix */
const meta: Meta<MultiCascaderProps<FakeCity>> = {
title: 'Fields/MultiCascader (variations)',
component: MultiCascaderWithThreeColumns,
component: MultiCascader,

argTypes: {
value: {
Expand All @@ -53,7 +53,7 @@ const meta: Meta<MultiCascaderProps<FakeCity>> = {

export default meta

export function MultiCascaderWithThreeColumns(props: MultiCascaderProps<FakeCity>) {
export function WithThreeColumns(props: MultiCascaderProps<FakeCity>) {
const [outputValue, setOutputValue] = useState<FakeCity[] | undefined | '∅'>(props.value ?? '∅')

const { controlledOnChange, controlledValue } = useFieldControl(props.value, setOutputValue)
Expand Down
2 changes: 1 addition & 1 deletion stories/fields/MultiSelect/WithCustomSearch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const meta: Meta<MultiSelectProps<Specy>> = {

export default meta

export function MultiSelectWithCustomSearch(props: MultiSelectProps<Specy>) {
export function WithCustomSearch(props: MultiSelectProps<Specy>) {
const optionsRef = useRef(
(SPECIES as Specy[]).map(specy => ({
label: `${specy.code} - ${specy.name}`,
Expand Down
2 changes: 1 addition & 1 deletion stories/fields/Select/WithCustomSearch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const meta: Meta<SelectProps<Specy>> = {

export default meta

export function SelectWithCustomSearch(props: SelectProps<Specy>) {
export function WithCustomSearch(props: SelectProps<Specy>) {
const optionsRef = useRef(
(SPECIES as Specy[]).map(specy => ({
label: `${specy.code} - ${specy.name}`,
Expand Down
70 changes: 70 additions & 0 deletions stories/tables/SimpleTable/WithLoader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ky from 'ky'
import { useEffect, useState } from 'react'

import { META_DEFAULTS } from '../../../.storybook/constants'
import { generateStoryDecorator } from '../../../.storybook/utils/generateStoryDecorator'
import { SimpleTable } from '../../../src'

import type { Meta } from '@storybook/react'

/* eslint-disable sort-keys-fix/sort-keys-fix */
const meta: Meta<{}> = {
...META_DEFAULTS,

title: 'Tables/SimpleTable (variations)',

decorators: [
generateStoryDecorator({
withBackgroundButton: true
})
]
}
/* eslint-enable sort-keys-fix/sort-keys-fix */

export default meta

export function WithLoader() {
const [data, setData] = useState<any[] | undefined>(undefined)

const emptyRows = new Array(10).fill(undefined)
const isLoading = !data

useEffect(() => {
const timer = setTimeout(async () => {
const nextData: any = await ky.get('https://api.openbrewerydb.org/v1/breweries?per_page=10').json()

setData(nextData)
}, 5000)

return () => clearTimeout(timer)
}, [])

return (
<SimpleTable.Table>
<SimpleTable.Head>
<SimpleTable.Th $width={48}>ID</SimpleTable.Th>
<SimpleTable.Th $width={240}>Name</SimpleTable.Th>
<SimpleTable.Th $width={480}>Address</SimpleTable.Th>
</SimpleTable.Head>
<tbody>
{isLoading &&
emptyRows.map((_, index) => (
// eslint-disable-next-line react/no-array-index-key
<SimpleTable.BodyTr key={`row-${index}`}>
<SimpleTable.Td $isLoading />
<SimpleTable.Td $isLoading />
<SimpleTable.Td $isLoading />
</SimpleTable.BodyTr>
))}
{!isLoading &&
data?.map(brewery => (
<SimpleTable.BodyTr key={brewery.id}>
<SimpleTable.Td>{brewery.id}</SimpleTable.Td>
<SimpleTable.Td>{brewery.name}</SimpleTable.Td>
<SimpleTable.Td>{`${brewery.street}, ${brewery.city} ${brewery.postalCode}, ${brewery.state}`}</SimpleTable.Td>
</SimpleTable.BodyTr>
))}
</tbody>
</SimpleTable.Table>
)
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@fields/*": ["fields/*"],
"@hooks/*": ["hooks/*"],
"@libs/*": ["libs/*"],
"@tables/*": ["tables/*"],
"@types_/*": ["types/*"],
"@utils/*": ["utils/*"]
},
Expand Down

0 comments on commit 6cc137e

Please sign in to comment.