Skip to content

Commit

Permalink
Merge pull request #409 from omnifed/408-feat-make-th-actionable
Browse files Browse the repository at this point in the history
feat: add clickable headers to Table component
  • Loading branch information
caseybaggz committed Aug 28, 2024
2 parents a99660f + 6cdc674 commit 73e6ea1
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 7 deletions.
60 changes: 60 additions & 0 deletions docs/app/react/table/components/table-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { SortAscending, SortDescending } from '@cerberus-design/icons'
import {
Table,
Tbody,
Expand All @@ -10,9 +11,11 @@ import {
Toggle,
Field,
useToggle,
Show,
} from '@cerberus-design/react'
import { css } from '@cerberus/styled-system/css'
import { hstack } from '@cerberus/styled-system/patterns'
import { useCallback, useMemo, useState } from 'react'

export function SizesPreview() {
return (
Expand Down Expand Up @@ -165,6 +168,63 @@ export function BasicTablePreview() {
)
}

export function ClickablePreview() {
const [order, setOrder] = useState<'asc' | 'desc'>('asc')
const data = useMemo(
() => [
{
id: '1',
name: 'John Doe',
age: 30,
},
{
id: '2',
name: 'Jane Doe',
age: 25,
},
],
[],
)
const sortedData = useMemo(() => {
return order === 'asc'
? data.sort((a, b) => a.age - b.age)
: data.sort((a, b) => b.age - a.age)
}, [data, order])

const handleClick = useCallback(() => {
setOrder((prev) => (prev === 'asc' ? 'desc' : 'asc'))
}, [])

return (
<Table caption="Clickable table">
<Thead>
<Tr>
<Th>Name</Th>
<Th
className={css({
w: '6rem',
})}
onClick={handleClick}
>
Age
<Show when={order === 'asc'} fallback={<SortDescending />}>
<SortAscending />
</Show>
</Th>
</Tr>
</Thead>
<Tbody>
{sortedData.map((person) => (
<Tr key={person.id}>
<Td>{person.name}</Td>
<Td>{person.age}</Td>
</Tr>
))}
</Tbody>
</Table>
)
}

export function CustomizedPreview() {
return (
<Table caption="Customized table">
Expand Down
142 changes: 141 additions & 1 deletion docs/app/react/table/dev.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
BasicTablePreview,
SizesPreview,
DecorationPreview,
ClickablePreview,
CustomizedPreview
} from '@/app/react/table/components/table-preview'

Expand Down Expand Up @@ -170,6 +171,142 @@ function SizesPreview() {
```
</CodePreview>

## Decoration

<CodePreview preview={<DecorationPreview />}>
```tsx title="table.tsx" {20,38}
import {
Table,
Tbody,
Th,
Thead,
Tr,
Td,
} from '@cerberus-design/react'
import { hstack } from '@cerberus/styled-system/patterns'

function DecorationPreview() {
return (
<div className={hstack()}>
<Table caption="Table with default decorations">
<Thead>
<Tr>
<Th>Default</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>one</Td>
</Tr>
<Tr>
<Td>two</Td>
</Tr>
<Tr>
<Td>three</Td>
</Tr>
</Tbody>
</Table>
<Table caption="Table with zebra decorations">
<Thead>
<Tr>
<Th>Zebra</Th>
</Tr>
</Thead>
<Tbody decoration="zebra">
<Tr>
<Td>one</Td>
</Tr>
<Tr>
<Td>two</Td>
</Tr>
<Tr>
<Td>three</Td>
</Tr>
</Tbody>
</Table>
</div>
)
}
```
</CodePreview>

## Clickable

<CodePreview preview={<ClickablePreview />}>
```tsx title="table.tsx" {51}
'use client'

import {
Show,
Table,
Tbody,
Th,
Thead,
Tr,
Td,
} from '@cerberus-design/react'
import { SortAscending, SortDescending } from '@cerberus-design/icons'
imort { useCallback, useMemo, useState } from 'react'

function ClickablePreview() {
const [order, setOrder] = useState<'asc' | 'desc'>('asc')
const data = useMemo(
() => [
{
id: '1',
name: 'John Doe',
age: 30,
},
{
id: '2',
name: 'Jane Doe',
age: 25,
},
],
[],
)
const sortedData = useMemo(() => {
return order === 'asc'
? data.sort((a, b) => a.age - b.age)
: data.sort((a, b) => b.age - a.age)
}, [data, order])

const handleClick = useCallback(() => {
setOrder((prev) => (prev === 'asc' ? 'desc' : 'asc'))
}, [])

return (
<Table caption="Clickable table">
<Thead>
<Tr>
<Th>Name</Th>
<Th
className={css({
w: '6rem',
})}
onClick={handleClick}
>
Age
<Show when={order === 'asc'} fallback={<SortDescending />}>
<SortAscending />
</Show>
</Th>
</Tr>
</Thead>
<Tbody>
{sortedData.map((person) => (
<Tr key={person.id}>
<Td>{person.name}</Td>
<Td>{person.age}</Td>
</Tr>
))}
</Tbody>
</Table>
)
}
```
</CodePreview>

## Customization

You can customize the table by utilizing the `css` function. For full control, we recommend extending the recipes provided in your panda config.
Expand Down Expand Up @@ -288,7 +425,9 @@ define function Table(props: PropsWithChildren<TableProps>): ReactNode
### Th

```ts showLineNumbers=false
export type ThBaseProps = TableHTMLAttributes<HTMLTableCellElement>
export type ThBaseProps = TableHTMLAttributes<HTMLTableCellElement> & {
onClick?: MouseEventHandler<HTMLButtonElement>
}
export type ThProps = ThBaseProps & ThVariantProps
define function Th(props: ThProps): ReactNode
Expand All @@ -297,6 +436,7 @@ define function Th(props: ThProps): ReactNode
| Name | Default | Description |
| -------- | ------- | -------------------------------------------- |
| size | `md` | The cell size of the th. |
| onClick | | Converts the table cell to a button element. |
### Tbody
Expand Down
40 changes: 35 additions & 5 deletions packages/react/src/components/Th.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { cx } from '@cerberus/styled-system/css'
import { css, cx } from '@cerberus/styled-system/css'
import { th, type ThVariantProps } from '@cerberus/styled-system/recipes'
import type { TableHTMLAttributes } from 'react'
import type { MouseEvent, TableHTMLAttributes } from 'react'
import { Show } from './Show'

/**
* Th component for the Table component
* @module
*/

export type ThBaseProps = TableHTMLAttributes<HTMLTableCellElement>
export type ThBaseProps = TableHTMLAttributes<HTMLTableCellElement> & {
onClick?: (e: MouseEvent<HTMLButtonElement>) => void
}
export type ThProps = ThBaseProps & ThVariantProps

/**
Expand All @@ -19,8 +22,35 @@ export type ThProps = ThBaseProps & ThVariantProps
* ```
*/
export function Th(props: ThProps) {
const { size, ...nativeProps } = props
const { size, onClick, ...nativeProps } = props
return (
<th {...nativeProps} className={cx(nativeProps.className, th({ size }))} />
<Show
when={Boolean(onClick)}
fallback={
<th
{...nativeProps}
className={cx(nativeProps.className, th({ size }))}
/>
}
>
<th {...nativeProps}>
<button
className={cx(
nativeProps.className,
th({ size }),
css({
alignItems: 'center',
display: 'inline-flex',
justifyContent: 'space-between',
userSelect: 'none',
w: 'full',
}),
)}
onClick={onClick}
>
{props.children}
</button>
</th>
</Show>
)
}
42 changes: 41 additions & 1 deletion tests/react/components/table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, test, expect, afterEach } from 'bun:test'
import { describe, test, expect, afterEach, jest } from 'bun:test'
import { cleanup, render, screen } from '@testing-library/react'
import { Table, Tbody, Td, Th, Thead, Tr } from '@cerberus-design/react'
import { setupStrictMode } from '@/utils'
import userEvent from '@testing-library/user-event'

describe('Table', () => {
setupStrictMode()
Expand Down Expand Up @@ -52,4 +53,43 @@ describe('Table', () => {
expect(screen.getByText(/Row 3, Cell 2/i)).toBeTruthy()
expect(screen.getByText(/Row 3, Cell 3/i)).toBeTruthy()
})

test('should allow clickable headers', async () => {
const handleClick = jest.fn()
render(
<Table caption="Basic table">
<Thead>
<Tr>
<Th onClick={handleClick}>Header 1</Th>
<Th>Header 2</Th>
<Th>Header 3</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>Row 1, Cell 1</Td>
<Td>Row 1, Cell 2</Td>
<Td>Row 1, Cell 3</Td>
</Tr>
<Tr>
<Td>Row 2, Cell 1</Td>
<Td>Row 2, Cell 2</Td>
<Td>Row 2, Cell 3</Td>
</Tr>
<Tr>
<Td>Row 3, Cell 1</Td>
<Td>Row 3, Cell 2</Td>
<Td>Row 3, Cell 3</Td>
</Tr>
</Tbody>
</Table>,
)

expect(screen.getByText(/Header 1/i)).toBeTruthy()
expect(screen.getByText(/Header 2/i)).toBeTruthy()
expect(screen.getByText(/Header 3/i)).toBeTruthy()

await userEvent.click(screen.getByText(/Header 1/i))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})

0 comments on commit 73e6ea1

Please sign in to comment.