Skip to content

Commit

Permalink
Merge pull request #45 from igor-tech/QUIZ-19
Browse files Browse the repository at this point in the history
Add Table Component
  • Loading branch information
igor-tech authored May 15, 2024
2 parents c2d0725 + bba19bb commit 5efac5d
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/components/ui/table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './table'
74 changes: 74 additions & 0 deletions src/components/ui/table/table.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.root {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
border: 1px solid var(--color-dark-500);
}

.head {
background-color: var(--color-dark-500);
}

.row {
transition: all 0.3s ease;

&:not(:last-child) {
border-bottom: 1px solid var(--color-dark-500);
}
}

.body {
.row {
&:hover {
background-color: var(--color-dark-700);
}
}
}

.headCell {
padding: 6px 24px;
font-weight: var(--font-weight-bold);
line-height: var(--line-height-m);
text-align: start;
}

.cell {
padding: 6px 24px;
font-size: var(--font-size-s);
line-height: var(--line-height-m);
}

.head th:last-child {
width: 15%;
}

.headSort {
}

.hover {
cursor: pointer;
transition: all 0.5s ease;

&:hover {
background-color: var(--color-dark-300);
}
}

.arrow {
position: absolute;

width: 16px;
height: 16px;
margin: 5px;

transition: 0.3s;

&.arrowUp {
transform: rotate(180deg);
}

svg {
width: 16px;
height: 16px;
}
}
192 changes: 192 additions & 0 deletions src/components/ui/table/table.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import type { Meta, StoryObj } from '@storybook/react'

import { useState } from 'react'

import { Edit } from '@/assets/icons/components/edit'
import { PlayCircle } from '@/assets/icons/components/play-circle'
import { Trash } from '@/assets/icons/components/trash'
import { IconButton } from '@/components/ui/icon-button'
import { Sort, Table } from '@/components/ui/table/table'

const meta = {
component: Table.Root,
tags: ['autodocs'],
title: 'Components/UI/Table',
} satisfies Meta<typeof Table.Root>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {},
render: () => {
return (
<Table.Root>
<Table.Head>
<Table.HeadCell>Name</Table.HeadCell>
<Table.HeadCell>Cards</Table.HeadCell>
<Table.HeadCell>Last Updated</Table.HeadCell>
<Table.HeadCell>Created by</Table.HeadCell>
<Table.HeadCell></Table.HeadCell>
</Table.Head>
<Table.Body>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
)
},
}

const columns = [
{
key: 'name',
title: 'Name',
},
{
key: 'cards',
title: 'Cards',
},
{
key: 'last-updated',
title: 'Last Updated',
},
{
key: 'created-by',
title: 'Created by',
},
{
key: 'icons',
sortable: false,
title: '',
},
]

export const TableWithSort: Story = {
args: {},
render: () => {
const [sort, setSort] = useState<Sort>({ direction: 'desc', key: 'name' })

return (
<Table.Root>
<Table.HeadWithSort columns={columns} onSort={setSort} sort={sort}>
afsd
</Table.HeadWithSort>
<Table.Body>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Pack Name</Table.Cell>
<Table.Cell>4</Table.Cell>
<Table.Cell>18.03.2021</Table.Cell>
<Table.Cell>Ivan Ivanov</Table.Cell>
<Table.Cell>
<div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>
<IconButton variant={'small'}>
<PlayCircle />
</IconButton>
<IconButton variant={'small'}>
<Edit />
</IconButton>
<IconButton variant={'small'}>
<Trash />
</IconButton>
</div>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
)
},
}
112 changes: 112 additions & 0 deletions src/components/ui/table/table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { ComponentPropsWithoutRef, HTMLAttributes, ReactNode, forwardRef } from 'react'

import { ArrowDown } from '@/assets/icons/components/arrow-down'
import { clsx } from 'clsx'

import styles from './table.module.scss'

const Root = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => {
return <table className={clsx(styles.root, className)} ref={ref} {...props} />
}
)

const Head = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => {
return <thead className={clsx(styles.head, className)} ref={ref} {...props} />
}
)

const Body = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => {
return <tbody className={clsx(styles.body, className)} ref={ref} {...props} />
}
)

const Row = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElement>>(
({ className, ...props }, ref) => {
return <tr className={clsx(styles.row, className)} ref={ref} {...props} />
}
)

const HeadCell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => {
return <th className={clsx(styles.headCell, className)} ref={ref} {...props} />
}
)

const Cell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => {
return <td className={clsx(styles.cell, className)} ref={ref} {...props} />
}
)

export type Column = {
key: string
sortable?: boolean
title: string
}

export type Sort = {
direction: 'asc' | 'desc'
key: string
} | null

type HeadWithSortProps = {
children: ReactNode
columns: Column[]
onSort?: (sort: Sort) => void
sort?: Sort
} & ComponentPropsWithoutRef<'thead'>

export const HeadWithSort = ({ columns, onSort, sort, ...restProps }: HeadWithSortProps) => {
const handleSort = (key: string, sortable?: boolean) => () => {
if (!onSort || !sortable) {
return
}

if (sort?.key !== key) {
return onSort({ direction: 'asc', key })
}

if (sort.direction === 'desc') {
return onSort(null)
}

return onSort({
direction: sort?.direction === 'asc' ? 'desc' : 'asc',
key,
})
}

return (
<Head {...restProps}>
<Row>
{columns.map(({ key, sortable = true, title }) => (
<HeadCell
className={clsx(styles.headSort, sortable && styles.hover)}
key={key}
onClick={handleSort(key, sortable)}
>
{title}
{sort && sort.key === key && (
<span className={clsx(styles.arrow, sort.direction === 'desc' && styles.arrowUp)}>
<ArrowDown />
</span>
)}
</HeadCell>
))}
</Row>
</Head>
)
}

export const Table = {
Body,
Cell,
Head,
HeadCell,
HeadWithSort,
Root,
Row,
}

0 comments on commit 5efac5d

Please sign in to comment.