Skip to content

Commit 59e59c7

Browse files
committed
feat(table): add Table component
Data table with column definitions, sorting, row selection, bordered/striped variants, sizes, and custom cell rendering.
1 parent 461148d commit 59e59c7

15 files changed

Lines changed: 1118 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<Table /> should match the snapshot 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="ty-table ty-table_md"
7+
>
8+
<div
9+
class="ty-table__wrapper"
10+
>
11+
<table
12+
class="ty-table__table"
13+
>
14+
<thead
15+
class="ty-table__thead"
16+
>
17+
<tr>
18+
<th
19+
class="ty-table__cell"
20+
>
21+
<span
22+
class="ty-table__col-title"
23+
>
24+
Name
25+
</span>
26+
</th>
27+
<th
28+
class="ty-table__cell ty-table__cell_sortable"
29+
>
30+
<span
31+
class="ty-table__col-title"
32+
>
33+
Age
34+
</span>
35+
<span
36+
class="ty-table__sorter"
37+
>
38+
<span
39+
class="ty-table__sorter-icon"
40+
>
41+
42+
</span>
43+
<span
44+
class="ty-table__sorter-icon"
45+
>
46+
47+
</span>
48+
</span>
49+
</th>
50+
</tr>
51+
</thead>
52+
<tbody
53+
class="ty-table__tbody"
54+
>
55+
<tr
56+
class="ty-table__row"
57+
>
58+
<td
59+
class="ty-table__cell"
60+
>
61+
Alice
62+
</td>
63+
<td
64+
class="ty-table__cell"
65+
>
66+
30
67+
</td>
68+
</tr>
69+
<tr
70+
class="ty-table__row"
71+
>
72+
<td
73+
class="ty-table__cell"
74+
>
75+
Bob
76+
</td>
77+
<td
78+
class="ty-table__cell"
79+
>
80+
25
81+
</td>
82+
</tr>
83+
<tr
84+
class="ty-table__row"
85+
>
86+
<td
87+
class="ty-table__cell"
88+
>
89+
Charlie
90+
</td>
91+
<td
92+
class="ty-table__cell"
93+
>
94+
35
95+
</td>
96+
</tr>
97+
</tbody>
98+
</table>
99+
</div>
100+
</div>
101+
</DocumentFragment>
102+
`;
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
import { render, fireEvent } from '@testing-library/react';
3+
import Table from '../index';
4+
5+
const columns = [
6+
{ title: 'Name', dataIndex: 'name', key: 'name' },
7+
{ title: 'Age', dataIndex: 'age', key: 'age', sorter: (a: any, b: any) => a.age - b.age },
8+
];
9+
10+
const dataSource = [
11+
{ key: '1', name: 'Alice', age: 30 },
12+
{ key: '2', name: 'Bob', age: 25 },
13+
{ key: '3', name: 'Charlie', age: 35 },
14+
];
15+
16+
describe('<Table />', () => {
17+
it('should match the snapshot', () => {
18+
const { asFragment } = render(<Table columns={columns} dataSource={dataSource} />);
19+
expect(asFragment()).toMatchSnapshot();
20+
});
21+
22+
it('should render correctly', () => {
23+
const { container } = render(<Table columns={columns} dataSource={dataSource} />);
24+
expect(container.firstChild).toHaveClass('ty-table');
25+
});
26+
27+
it('should render data rows', () => {
28+
const { getByText } = render(<Table columns={columns} dataSource={dataSource} />);
29+
expect(getByText('Alice')).toBeInTheDocument();
30+
expect(getByText('Bob')).toBeInTheDocument();
31+
});
32+
33+
it('should render column headers', () => {
34+
const { getByText } = render(<Table columns={columns} dataSource={dataSource} />);
35+
expect(getByText('Name')).toBeInTheDocument();
36+
expect(getByText('Age')).toBeInTheDocument();
37+
});
38+
39+
it('should render bordered', () => {
40+
const { container } = render(<Table columns={columns} dataSource={dataSource} bordered />);
41+
expect(container.firstChild).toHaveClass('ty-table_bordered');
42+
});
43+
44+
it('should show empty state', () => {
45+
const { getByText } = render(<Table columns={columns} dataSource={[]} />);
46+
expect(getByText('No Data')).toBeInTheDocument();
47+
});
48+
49+
it('should sort on header click', () => {
50+
const { container, getAllByRole } = render(
51+
<Table columns={columns} dataSource={dataSource} pagination={false} />
52+
);
53+
const ageHeader = container.querySelector('.ty-table__cell_sortable');
54+
fireEvent.click(ageHeader!);
55+
const rows = container.querySelectorAll('.ty-table__tbody .ty-table__row');
56+
expect(rows[0].textContent).toContain('Bob');
57+
});
58+
59+
it('should handle row selection', () => {
60+
const onChange = jest.fn();
61+
const { container } = render(
62+
<Table
63+
columns={columns}
64+
dataSource={dataSource}
65+
rowSelection={{ onChange }}
66+
pagination={false}
67+
/>
68+
);
69+
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
70+
// first is select all, second is first row
71+
fireEvent.click(checkboxes[1]);
72+
expect(onChange).toHaveBeenCalled();
73+
});
74+
75+
it('should show loading state', () => {
76+
const { getByText } = render(<Table columns={columns} dataSource={dataSource} loading />);
77+
expect(getByText('Loading...')).toBeInTheDocument();
78+
});
79+
});

components/table/demo/basic.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<demo>
2+
3+
### Basic
4+
5+
A simple table with columns, sorting, and row selection.
6+
7+
```jsx live
8+
() => {
9+
const columns = [
10+
{ title: 'Name', dataIndex: 'name', key: 'name' },
11+
{ title: 'Age', dataIndex: 'age', key: 'age', sorter: (a, b) => a.age - b.age },
12+
{ title: 'Address', dataIndex: 'address', key: 'address' },
13+
];
14+
15+
const data = [
16+
{ key: '1', name: 'John Brown', age: 32, address: 'New York No. 1' },
17+
{ key: '2', name: 'Jim Green', age: 42, address: 'London No. 1' },
18+
{ key: '3', name: 'Joe Black', age: 28, address: 'Sydney No. 1' },
19+
{ key: '4', name: 'Jane White', age: 35, address: 'Tokyo No. 1' },
20+
];
21+
22+
return (
23+
<Table
24+
columns={columns}
25+
dataSource={data}
26+
bordered
27+
/>
28+
);
29+
}
30+
```
31+
32+
</demo>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<demo>
2+
3+
### Custom Render
4+
5+
Use the `render` property on columns to customize cell content with tags, links, and actions.
6+
7+
```jsx live
8+
() => {
9+
const columns = [
10+
{ title: 'Name', dataIndex: 'name' },
11+
{
12+
title: 'Status',
13+
dataIndex: 'status',
14+
render: (status) => {
15+
const color = status === 'Active' ? '#52c41a' : status === 'Inactive' ? '#f44336' : '#ff9800';
16+
return <Tag color={color}>{status}</Tag>;
17+
},
18+
},
19+
{
20+
title: 'Progress',
21+
dataIndex: 'progress',
22+
render: (val) => (
23+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
24+
<div style={{ flex: 1, height: 6, background: '#eee', borderRadius: 3 }}>
25+
<div style={{ width: `${val}%`, height: '100%', background: '#6e41bf', borderRadius: 3 }} />
26+
</div>
27+
<span style={{ fontSize: 12 }}>{val}%</span>
28+
</div>
29+
),
30+
width: 200,
31+
},
32+
{
33+
title: 'Action',
34+
dataIndex: 'action',
35+
render: (_, record) => (
36+
<a onClick={() => alert(`View ${record.name}`)}>View</a>
37+
),
38+
},
39+
];
40+
41+
const data = [
42+
{ key: '1', name: 'Project Alpha', status: 'Active', progress: 75 },
43+
{ key: '2', name: 'Project Beta', status: 'Pending', progress: 30 },
44+
{ key: '3', name: 'Project Gamma', status: 'Active', progress: 100 },
45+
{ key: '4', name: 'Project Delta', status: 'Inactive', progress: 0 },
46+
];
47+
48+
return (
49+
<Table
50+
columns={columns}
51+
dataSource={data}
52+
pagination={false}
53+
/>
54+
);
55+
}
56+
```
57+
58+
</demo>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<demo>
2+
3+
### Pagination
4+
5+
Table with built-in pagination. Data is automatically sliced by page.
6+
7+
```jsx live
8+
() => {
9+
const columns = [
10+
{ title: '#', dataIndex: 'id', width: 60 },
11+
{ title: 'Title', dataIndex: 'title' },
12+
{ title: 'Author', dataIndex: 'author' },
13+
];
14+
15+
const data = Array.from({ length: 25 }, (_, i) => ({
16+
key: String(i + 1),
17+
id: i + 1,
18+
title: `Article ${i + 1}`,
19+
author: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'][i % 5],
20+
}));
21+
22+
return (
23+
<Table
24+
columns={columns}
25+
dataSource={data}
26+
pagination={{ pageSize: 5 }}
27+
/>
28+
);
29+
}
30+
```
31+
32+
</demo>

components/table/demo/selection.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<demo>
2+
3+
### Row Selection
4+
5+
Select rows with checkboxes. Use `rowSelection.type` for radio mode.
6+
7+
```jsx live
8+
() => {
9+
const [selectedRowKeys, setSelectedRowKeys] = React.useState([]);
10+
11+
const columns = [
12+
{ title: 'Name', dataIndex: 'name' },
13+
{ title: 'Age', dataIndex: 'age' },
14+
{ title: 'Address', dataIndex: 'address' },
15+
];
16+
17+
const data = [
18+
{ key: '1', name: 'John Brown', age: 32, address: 'New York No. 1' },
19+
{ key: '2', name: 'Jim Green', age: 42, address: 'London No. 1' },
20+
{ key: '3', name: 'Joe Black', age: 28, address: 'Sydney No. 1' },
21+
{ key: '4', name: 'Jane White', age: 35, address: 'Tokyo No. 1' },
22+
];
23+
24+
return (
25+
<div>
26+
<p style={{ marginBottom: 8 }}>Selected: {selectedRowKeys.join(', ') || 'none'}</p>
27+
<Table
28+
columns={columns}
29+
dataSource={data}
30+
rowSelection={{
31+
selectedRowKeys,
32+
onChange: (keys) => setSelectedRowKeys(keys),
33+
}}
34+
pagination={false}
35+
/>
36+
</div>
37+
);
38+
}
39+
```
40+
41+
</demo>

components/table/demo/sizes.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<demo>
2+
3+
### Sizes
4+
5+
Three built-in sizes: `sm`, `md`, and `lg`.
6+
7+
```jsx live
8+
() => {
9+
const [size, setSize] = React.useState('md');
10+
11+
const columns = [
12+
{ title: 'Name', dataIndex: 'name' },
13+
{ title: 'Age', dataIndex: 'age' },
14+
{ title: 'Address', dataIndex: 'address' },
15+
];
16+
17+
const data = [
18+
{ key: '1', name: 'John Brown', age: 32, address: 'New York' },
19+
{ key: '2', name: 'Jim Green', age: 42, address: 'London' },
20+
{ key: '3', name: 'Joe Black', age: 28, address: 'Sydney' },
21+
];
22+
23+
return (
24+
<div>
25+
<Segmented
26+
options={['sm', 'md', 'lg']}
27+
value={size}
28+
onChange={(val) => setSize(val)}
29+
style={{ marginBottom: 16 }}
30+
/>
31+
<Table
32+
columns={columns}
33+
dataSource={data}
34+
size={size}
35+
bordered
36+
pagination={false}
37+
/>
38+
</div>
39+
);
40+
}
41+
```
42+
43+
</demo>

0 commit comments

Comments
 (0)