@delightui/components 0.1.105 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -1
- package/dist/cjs/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/cjs/components/molecules/Modal/index.d.ts +2 -0
- package/dist/cjs/components/molecules/index.d.ts +2 -0
- package/dist/cjs/library.css +19 -6
- package/dist/cjs/library.js +3 -3
- package/dist/cjs/library.js.map +1 -1
- package/dist/esm/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/esm/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/esm/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/esm/components/molecules/Modal/index.d.ts +2 -0
- package/dist/esm/components/molecules/index.d.ts +2 -0
- package/dist/esm/library.css +19 -6
- package/dist/esm/library.js +3 -3
- package/dist/esm/library.js.map +1 -1
- package/dist/index.d.ts +108 -2
- package/docs/README.md +264 -0
- package/docs/components/atoms/ActionImage.md +119 -0
- package/docs/components/atoms/Button.md +197 -0
- package/docs/components/atoms/Checkbox.md +299 -0
- package/docs/components/atoms/CheckboxItem.md +314 -0
- package/docs/components/atoms/Chip.md +380 -0
- package/docs/components/atoms/CustomToggle.md +270 -0
- package/docs/components/atoms/Icon.md +365 -0
- package/docs/components/atoms/IconButton.md +407 -0
- package/docs/components/atoms/Image.md +448 -0
- package/docs/components/atoms/Input.md +430 -0
- package/docs/components/atoms/ListItem.md +502 -0
- package/docs/components/atoms/Password.md +472 -0
- package/docs/components/atoms/RadioButton.md +614 -0
- package/docs/components/atoms/RadioButtonItem.md +588 -0
- package/docs/components/atoms/ResponsiveComponent.md +612 -0
- package/docs/components/atoms/SelectListItem.md +609 -0
- package/docs/components/atoms/Slider.md +605 -0
- package/docs/components/atoms/Spinner.md +605 -0
- package/docs/components/atoms/Text.md +463 -0
- package/docs/components/atoms/TextArea.md +670 -0
- package/docs/components/atoms/ToastNotification.md +668 -0
- package/docs/components/atoms/Toggle.md +737 -0
- package/docs/components/atoms/ToggleButton.md +751 -0
- package/docs/components/atoms/Tooltip.md +391 -0
- package/docs/components/molecules/Accordion.md +440 -0
- package/docs/components/molecules/AccordionGroup.md +547 -0
- package/docs/components/molecules/ActionCard.md +546 -0
- package/docs/components/molecules/Breadcrumb.md +403 -0
- package/docs/components/molecules/Breadcrumbs.md +485 -0
- package/docs/components/molecules/ButtonGroup.md +383 -0
- package/docs/components/molecules/Card.md +298 -0
- package/docs/components/molecules/ChipInput.md +646 -0
- package/docs/components/molecules/ContextMenu.md +768 -0
- package/docs/components/molecules/CustomTimeSelector.md +116 -0
- package/docs/components/molecules/DatePicker.md +516 -0
- package/docs/components/molecules/DateTimeSelector.md +166 -0
- package/docs/components/molecules/FormField.md +312 -0
- package/docs/components/molecules/Grid.md +577 -0
- package/docs/components/molecules/GridItem.md +834 -0
- package/docs/components/molecules/GridList.md +244 -0
- package/docs/components/molecules/List.md +485 -0
- package/docs/components/molecules/Modal.md +470 -0
- package/docs/components/molecules/ModalFooter.md +702 -0
- package/docs/components/molecules/ModalHeader.md +756 -0
- package/docs/components/molecules/ModalProvider.md +205 -0
- package/docs/components/molecules/Nav.md +530 -0
- package/docs/components/molecules/NavItem.md +572 -0
- package/docs/components/molecules/NavLink.md +499 -0
- package/docs/components/molecules/Option.md +521 -0
- package/docs/components/molecules/Pagination.md +592 -0
- package/docs/components/molecules/PaginationNumberField.md +722 -0
- package/docs/components/molecules/Popover.md +516 -0
- package/docs/components/molecules/ProgressBar.md +624 -0
- package/docs/components/molecules/RadioGroup.md +831 -0
- package/docs/components/molecules/RepeaterList.md +185 -0
- package/docs/components/molecules/Select.md +402 -0
- package/docs/components/molecules/SortableTrigger.md +82 -0
- package/docs/components/molecules/useModal.md +379 -0
- package/docs/components/organisms/Dropzone.md +346 -0
- package/docs/components/organisms/DropzoneClear.md +135 -0
- package/docs/components/organisms/DropzoneContent.md +216 -0
- package/docs/components/organisms/DropzoneFilename.md +191 -0
- package/docs/components/organisms/DropzoneSupportedFormats.md +184 -0
- package/docs/components/organisms/DropzoneTrigger.md +209 -0
- package/docs/components/organisms/Form.md +533 -0
- package/docs/components/organisms/SlideOutPanel.md +662 -0
- package/docs/components/organisms/TabContent.md +902 -0
- package/docs/components/organisms/TabItem.md +1091 -0
- package/docs/components/organisms/Table.md +611 -0
- package/docs/components/organisms/TableBody.md +679 -0
- package/docs/components/organisms/TableCell.md +482 -0
- package/docs/components/organisms/TableHeader.md +513 -0
- package/docs/components/organisms/TableHeaderCell.md +661 -0
- package/docs/components/organisms/TableRow.md +715 -0
- package/docs/components/organisms/Tabs.md +1330 -0
- package/docs/components/utils/ConditionalView.md +568 -0
- package/docs/components/utils/RenderStateView.md +726 -0
- package/docs/components/utils/WrapTextNodes.md +614 -0
- package/package.json +3 -2
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
# TableBody
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
A semantic container component that wraps the main content section of a table. TableBody provides proper HTML structure for table data rows and ensures accessibility compliance by creating the `<tbody>` element that groups the table's primary content together.
|
|
6
|
+
|
|
7
|
+
## Aliases
|
|
8
|
+
|
|
9
|
+
- TableBody
|
|
10
|
+
- TBody
|
|
11
|
+
- TableContent
|
|
12
|
+
- BodySection
|
|
13
|
+
- TableDataGroup
|
|
14
|
+
|
|
15
|
+
## Props Breakdown
|
|
16
|
+
|
|
17
|
+
**Extends:** `HTMLAttributes<HTMLTableSectionElement>`
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Required | Description |
|
|
20
|
+
|------|------|---------|----------|-------------|
|
|
21
|
+
| `children` | `ReactNode` | - | Yes | Table body content, typically TableRow components containing TableCell elements |
|
|
22
|
+
|
|
23
|
+
Plus all standard HTML table section attributes (id, className, style, role, etc.).
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Basic Table Body
|
|
28
|
+
```tsx
|
|
29
|
+
import { Table, TableHeader, TableBody, TableRow, TableHeaderCell, TableCell } from '@delightui/components';
|
|
30
|
+
|
|
31
|
+
function BasicBodyExample() {
|
|
32
|
+
const users = [
|
|
33
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
34
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
|
|
35
|
+
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Table>
|
|
40
|
+
<TableHeader>
|
|
41
|
+
<TableRow>
|
|
42
|
+
<TableHeaderCell>Name</TableHeaderCell>
|
|
43
|
+
<TableHeaderCell>Email</TableHeaderCell>
|
|
44
|
+
<TableHeaderCell>Role</TableHeaderCell>
|
|
45
|
+
</TableRow>
|
|
46
|
+
</TableHeader>
|
|
47
|
+
<TableBody>
|
|
48
|
+
{users.map(user => (
|
|
49
|
+
<TableRow key={user.id}>
|
|
50
|
+
<TableCell>{user.name}</TableCell>
|
|
51
|
+
<TableCell>{user.email}</TableCell>
|
|
52
|
+
<TableCell>{user.role}</TableCell>
|
|
53
|
+
</TableRow>
|
|
54
|
+
))}
|
|
55
|
+
</TableBody>
|
|
56
|
+
</Table>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Dynamic Content Loading
|
|
62
|
+
```tsx
|
|
63
|
+
function LoadingBodyExample() {
|
|
64
|
+
const [data, setData] = useState([]);
|
|
65
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
66
|
+
const [error, setError] = useState(null);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const fetchData = async () => {
|
|
70
|
+
try {
|
|
71
|
+
setIsLoading(true);
|
|
72
|
+
// Simulate API call
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
74
|
+
setData([
|
|
75
|
+
{ id: 1, product: 'Laptop', category: 'Electronics', price: 999.99, stock: 15 },
|
|
76
|
+
{ id: 2, product: 'Desk Chair', category: 'Furniture', price: 299.99, stock: 8 },
|
|
77
|
+
{ id: 3, product: 'Coffee Mug', category: 'Kitchen', price: 12.99, stock: 50 }
|
|
78
|
+
]);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
setError('Failed to load data');
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
fetchData();
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Table>
|
|
91
|
+
<TableHeader>
|
|
92
|
+
<TableRow>
|
|
93
|
+
<TableHeaderCell>Product</TableHeaderCell>
|
|
94
|
+
<TableHeaderCell>Category</TableHeaderCell>
|
|
95
|
+
<TableHeaderCell>Price</TableHeaderCell>
|
|
96
|
+
<TableHeaderCell>Stock</TableHeaderCell>
|
|
97
|
+
</TableRow>
|
|
98
|
+
</TableHeader>
|
|
99
|
+
<TableBody>
|
|
100
|
+
{isLoading ? (
|
|
101
|
+
Array.from({ length: 3 }).map((_, index) => (
|
|
102
|
+
<TableRow key={`loading-${index}`}>
|
|
103
|
+
<TableCell>
|
|
104
|
+
<div className="skeleton-loader skeleton-text"></div>
|
|
105
|
+
</TableCell>
|
|
106
|
+
<TableCell>
|
|
107
|
+
<div className="skeleton-loader skeleton-text-small"></div>
|
|
108
|
+
</TableCell>
|
|
109
|
+
<TableCell>
|
|
110
|
+
<div className="skeleton-loader skeleton-text-small"></div>
|
|
111
|
+
</TableCell>
|
|
112
|
+
<TableCell>
|
|
113
|
+
<div className="skeleton-loader skeleton-text-small"></div>
|
|
114
|
+
</TableCell>
|
|
115
|
+
</TableRow>
|
|
116
|
+
))
|
|
117
|
+
) : error ? (
|
|
118
|
+
<TableRow>
|
|
119
|
+
<TableCell colSpan={4} className="error-state">
|
|
120
|
+
<div className="error-message">
|
|
121
|
+
<Icon icon="Error" className="error-icon" />
|
|
122
|
+
<Text type="BodyMedium">{error}</Text>
|
|
123
|
+
<Button size="Small" onClick={() => window.location.reload()}>
|
|
124
|
+
Retry
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
</TableCell>
|
|
128
|
+
</TableRow>
|
|
129
|
+
) : data.length === 0 ? (
|
|
130
|
+
<TableRow>
|
|
131
|
+
<TableCell colSpan={4} className="empty-state">
|
|
132
|
+
<div className="empty-message">
|
|
133
|
+
<Icon icon="Inventory" className="empty-icon" />
|
|
134
|
+
<Text type="BodyMedium">No products found</Text>
|
|
135
|
+
<Text type="BodySmall">Add your first product to get started</Text>
|
|
136
|
+
</div>
|
|
137
|
+
</TableCell>
|
|
138
|
+
</TableRow>
|
|
139
|
+
) : (
|
|
140
|
+
data.map(item => (
|
|
141
|
+
<TableRow key={item.id}>
|
|
142
|
+
<TableCell>{item.product}</TableCell>
|
|
143
|
+
<TableCell>{item.category}</TableCell>
|
|
144
|
+
<TableCell>${item.price}</TableCell>
|
|
145
|
+
<TableCell>{item.stock}</TableCell>
|
|
146
|
+
</TableRow>
|
|
147
|
+
))
|
|
148
|
+
)}
|
|
149
|
+
</TableBody>
|
|
150
|
+
</Table>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Striped Rows Body
|
|
156
|
+
```tsx
|
|
157
|
+
function StripedBodyExample() {
|
|
158
|
+
const transactions = [
|
|
159
|
+
{ id: 'T001', date: '2023-10-15', description: 'Online Purchase', amount: -89.99, status: 'Completed' },
|
|
160
|
+
{ id: 'T002', date: '2023-10-14', description: 'Salary Deposit', amount: 3500.00, status: 'Completed' },
|
|
161
|
+
{ id: 'T003', date: '2023-10-13', description: 'ATM Withdrawal', amount: -100.00, status: 'Completed' },
|
|
162
|
+
{ id: 'T004', date: '2023-10-12', description: 'Restaurant', amount: -45.67, status: 'Pending' },
|
|
163
|
+
{ id: 'T005', date: '2023-10-11', description: 'Gas Station', amount: -55.20, status: 'Completed' }
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Table className="striped-table">
|
|
168
|
+
<TableHeader>
|
|
169
|
+
<TableRow>
|
|
170
|
+
<TableHeaderCell>Transaction ID</TableHeaderCell>
|
|
171
|
+
<TableHeaderCell>Date</TableHeaderCell>
|
|
172
|
+
<TableHeaderCell>Description</TableHeaderCell>
|
|
173
|
+
<TableHeaderCell>Amount</TableHeaderCell>
|
|
174
|
+
<TableHeaderCell>Status</TableHeaderCell>
|
|
175
|
+
</TableRow>
|
|
176
|
+
</TableHeader>
|
|
177
|
+
<TableBody className="striped-body">
|
|
178
|
+
{transactions.map((transaction, index) => (
|
|
179
|
+
<TableRow key={transaction.id} className={index % 2 === 0 ? 'even-row' : 'odd-row'}>
|
|
180
|
+
<TableCell>{transaction.id}</TableCell>
|
|
181
|
+
<TableCell>{transaction.date}</TableCell>
|
|
182
|
+
<TableCell>{transaction.description}</TableCell>
|
|
183
|
+
<TableCell className={transaction.amount > 0 ? 'positive-amount' : 'negative-amount'}>
|
|
184
|
+
${Math.abs(transaction.amount).toFixed(2)}
|
|
185
|
+
</TableCell>
|
|
186
|
+
<TableCell>
|
|
187
|
+
<Chip style={transaction.status === 'Completed' ? 'Success' : 'Warning'}>
|
|
188
|
+
{transaction.status}
|
|
189
|
+
</Chip>
|
|
190
|
+
</TableCell>
|
|
191
|
+
</TableRow>
|
|
192
|
+
))}
|
|
193
|
+
</TableBody>
|
|
194
|
+
</Table>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Grouped Data Body
|
|
200
|
+
```tsx
|
|
201
|
+
function GroupedBodyExample() {
|
|
202
|
+
const groupedData = {
|
|
203
|
+
'Engineering': [
|
|
204
|
+
{ id: 1, name: 'Alice Johnson', role: 'Senior Developer', salary: 95000 },
|
|
205
|
+
{ id: 2, name: 'Bob Smith', role: 'Frontend Developer', salary: 75000 },
|
|
206
|
+
{ id: 3, name: 'Carol Wilson', role: 'Backend Developer', salary: 80000 }
|
|
207
|
+
],
|
|
208
|
+
'Design': [
|
|
209
|
+
{ id: 4, name: 'David Brown', role: 'UI Designer', salary: 70000 },
|
|
210
|
+
{ id: 5, name: 'Eve Davis', role: 'UX Designer', salary: 75000 }
|
|
211
|
+
],
|
|
212
|
+
'Marketing': [
|
|
213
|
+
{ id: 6, name: 'Frank Miller', role: 'Marketing Manager', salary: 85000 },
|
|
214
|
+
{ id: 7, name: 'Grace Taylor', role: 'Content Specialist', salary: 55000 }
|
|
215
|
+
]
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<Table>
|
|
220
|
+
<TableHeader>
|
|
221
|
+
<TableRow>
|
|
222
|
+
<TableHeaderCell>Employee</TableHeaderCell>
|
|
223
|
+
<TableHeaderCell>Role</TableHeaderCell>
|
|
224
|
+
<TableHeaderCell>Salary</TableHeaderCell>
|
|
225
|
+
<TableHeaderCell>Actions</TableHeaderCell>
|
|
226
|
+
</TableRow>
|
|
227
|
+
</TableHeader>
|
|
228
|
+
<TableBody className="grouped-body">
|
|
229
|
+
{Object.entries(groupedData).map(([department, employees]) => (
|
|
230
|
+
<React.Fragment key={department}>
|
|
231
|
+
<TableRow className="group-header-row">
|
|
232
|
+
<TableCell colSpan={4} className="group-header">
|
|
233
|
+
<div className="group-title">
|
|
234
|
+
<Icon icon="Group" size="Small" />
|
|
235
|
+
<Text type="BodyMedium">{department} Department</Text>
|
|
236
|
+
<Chip size="Small">{employees.length} employees</Chip>
|
|
237
|
+
</div>
|
|
238
|
+
</TableCell>
|
|
239
|
+
</TableRow>
|
|
240
|
+
{employees.map(employee => (
|
|
241
|
+
<TableRow key={employee.id} className="group-item-row">
|
|
242
|
+
<TableCell>{employee.name}</TableCell>
|
|
243
|
+
<TableCell>{employee.role}</TableCell>
|
|
244
|
+
<TableCell>${employee.salary.toLocaleString()}</TableCell>
|
|
245
|
+
<TableCell>
|
|
246
|
+
<Button size="Small" type="Ghost">Edit</Button>
|
|
247
|
+
</TableCell>
|
|
248
|
+
</TableRow>
|
|
249
|
+
))}
|
|
250
|
+
</React.Fragment>
|
|
251
|
+
))}
|
|
252
|
+
</TableBody>
|
|
253
|
+
</Table>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Interactive Row Selection
|
|
259
|
+
```tsx
|
|
260
|
+
function SelectableBodyExample() {
|
|
261
|
+
const [selectedRows, setSelectedRows] = useState(new Set());
|
|
262
|
+
const [hoveredRow, setHoveredRow] = useState(null);
|
|
263
|
+
|
|
264
|
+
const data = [
|
|
265
|
+
{ id: 1, task: 'Update documentation', assignee: 'John', priority: 'High', status: 'In Progress' },
|
|
266
|
+
{ id: 2, task: 'Fix login bug', assignee: 'Jane', priority: 'Critical', status: 'Todo' },
|
|
267
|
+
{ id: 3, task: 'Design new feature', assignee: 'Bob', priority: 'Medium', status: 'Review' },
|
|
268
|
+
{ id: 4, task: 'Write unit tests', assignee: 'Alice', priority: 'Low', status: 'Done' }
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const handleRowSelect = (id, selected) => {
|
|
272
|
+
const newSelected = new Set(selectedRows);
|
|
273
|
+
if (selected) {
|
|
274
|
+
newSelected.add(id);
|
|
275
|
+
} else {
|
|
276
|
+
newSelected.delete(id);
|
|
277
|
+
}
|
|
278
|
+
setSelectedRows(newSelected);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const isRowSelected = (id) => selectedRows.has(id);
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<Table>
|
|
285
|
+
<TableHeader>
|
|
286
|
+
<TableRow>
|
|
287
|
+
<TableHeaderCell>
|
|
288
|
+
<Checkbox
|
|
289
|
+
checked={selectedRows.size === data.length}
|
|
290
|
+
indeterminate={selectedRows.size > 0 && selectedRows.size < data.length}
|
|
291
|
+
onChange={(e) => {
|
|
292
|
+
if (e.target.checked) {
|
|
293
|
+
setSelectedRows(new Set(data.map(item => item.id)));
|
|
294
|
+
} else {
|
|
295
|
+
setSelectedRows(new Set());
|
|
296
|
+
}
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
</TableHeaderCell>
|
|
300
|
+
<TableHeaderCell>Task</TableHeaderCell>
|
|
301
|
+
<TableHeaderCell>Assignee</TableHeaderCell>
|
|
302
|
+
<TableHeaderCell>Priority</TableHeaderCell>
|
|
303
|
+
<TableHeaderCell>Status</TableHeaderCell>
|
|
304
|
+
</TableRow>
|
|
305
|
+
</TableHeader>
|
|
306
|
+
<TableBody className="selectable-body">
|
|
307
|
+
{data.map(item => (
|
|
308
|
+
<TableRow
|
|
309
|
+
key={item.id}
|
|
310
|
+
className={`
|
|
311
|
+
${isRowSelected(item.id) ? 'selected-row' : ''}
|
|
312
|
+
${hoveredRow === item.id ? 'hovered-row' : ''}
|
|
313
|
+
`}
|
|
314
|
+
onMouseEnter={() => setHoveredRow(item.id)}
|
|
315
|
+
onMouseLeave={() => setHoveredRow(null)}
|
|
316
|
+
>
|
|
317
|
+
<TableCell>
|
|
318
|
+
<Checkbox
|
|
319
|
+
checked={isRowSelected(item.id)}
|
|
320
|
+
onChange={(e) => handleRowSelect(item.id, e.target.checked)}
|
|
321
|
+
/>
|
|
322
|
+
</TableCell>
|
|
323
|
+
<TableCell>{item.task}</TableCell>
|
|
324
|
+
<TableCell>{item.assignee}</TableCell>
|
|
325
|
+
<TableCell>
|
|
326
|
+
<Chip
|
|
327
|
+
style={
|
|
328
|
+
item.priority === 'Critical' ? 'Error' :
|
|
329
|
+
item.priority === 'High' ? 'Warning' :
|
|
330
|
+
item.priority === 'Medium' ? 'Default' : 'Success'
|
|
331
|
+
}
|
|
332
|
+
>
|
|
333
|
+
{item.priority}
|
|
334
|
+
</Chip>
|
|
335
|
+
</TableCell>
|
|
336
|
+
<TableCell>
|
|
337
|
+
<Chip
|
|
338
|
+
style={
|
|
339
|
+
item.status === 'Done' ? 'Success' :
|
|
340
|
+
item.status === 'In Progress' ? 'Warning' : 'Default'
|
|
341
|
+
}
|
|
342
|
+
>
|
|
343
|
+
{item.status}
|
|
344
|
+
</Chip>
|
|
345
|
+
</TableCell>
|
|
346
|
+
</TableRow>
|
|
347
|
+
))}
|
|
348
|
+
</TableBody>
|
|
349
|
+
</Table>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Expandable Rows Body
|
|
355
|
+
```tsx
|
|
356
|
+
function ExpandableBodyExample() {
|
|
357
|
+
const [expandedRows, setExpandedRows] = useState(new Set());
|
|
358
|
+
|
|
359
|
+
const orders = [
|
|
360
|
+
{
|
|
361
|
+
id: 'ORD-001',
|
|
362
|
+
customer: 'John Doe',
|
|
363
|
+
date: '2023-10-15',
|
|
364
|
+
total: 299.99,
|
|
365
|
+
status: 'Delivered',
|
|
366
|
+
items: [
|
|
367
|
+
{ name: 'Wireless Headphones', quantity: 1, price: 199.99 },
|
|
368
|
+
{ name: 'Phone Case', quantity: 2, price: 50.00 }
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: 'ORD-002',
|
|
373
|
+
customer: 'Jane Smith',
|
|
374
|
+
date: '2023-10-14',
|
|
375
|
+
total: 159.97,
|
|
376
|
+
status: 'Shipped',
|
|
377
|
+
items: [
|
|
378
|
+
{ name: 'Bluetooth Speaker', quantity: 1, price: 89.99 },
|
|
379
|
+
{ name: 'USB Cable', quantity: 1, price: 19.99 },
|
|
380
|
+
{ name: 'Wall Charger', quantity: 2, price: 25.00 }
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
const toggleExpanded = (orderId) => {
|
|
386
|
+
const newExpanded = new Set(expandedRows);
|
|
387
|
+
if (newExpanded.has(orderId)) {
|
|
388
|
+
newExpanded.delete(orderId);
|
|
389
|
+
} else {
|
|
390
|
+
newExpanded.add(orderId);
|
|
391
|
+
}
|
|
392
|
+
setExpandedRows(newExpanded);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<Table>
|
|
397
|
+
<TableHeader>
|
|
398
|
+
<TableRow>
|
|
399
|
+
<TableHeaderCell></TableHeaderCell>
|
|
400
|
+
<TableHeaderCell>Order ID</TableHeaderCell>
|
|
401
|
+
<TableHeaderCell>Customer</TableHeaderCell>
|
|
402
|
+
<TableHeaderCell>Date</TableHeaderCell>
|
|
403
|
+
<TableHeaderCell>Total</TableHeaderCell>
|
|
404
|
+
<TableHeaderCell>Status</TableHeaderCell>
|
|
405
|
+
</TableRow>
|
|
406
|
+
</TableHeader>
|
|
407
|
+
<TableBody className="expandable-body">
|
|
408
|
+
{orders.map(order => (
|
|
409
|
+
<React.Fragment key={order.id}>
|
|
410
|
+
<TableRow className="main-row">
|
|
411
|
+
<TableCell>
|
|
412
|
+
<Button
|
|
413
|
+
size="Small"
|
|
414
|
+
type="Ghost"
|
|
415
|
+
onClick={() => toggleExpanded(order.id)}
|
|
416
|
+
>
|
|
417
|
+
<Icon
|
|
418
|
+
icon={expandedRows.has(order.id) ? 'ExpandLess' : 'ExpandMore'}
|
|
419
|
+
size="Small"
|
|
420
|
+
/>
|
|
421
|
+
</Button>
|
|
422
|
+
</TableCell>
|
|
423
|
+
<TableCell>{order.id}</TableCell>
|
|
424
|
+
<TableCell>{order.customer}</TableCell>
|
|
425
|
+
<TableCell>{order.date}</TableCell>
|
|
426
|
+
<TableCell>${order.total}</TableCell>
|
|
427
|
+
<TableCell>
|
|
428
|
+
<Chip style={order.status === 'Delivered' ? 'Success' : 'Warning'}>
|
|
429
|
+
{order.status}
|
|
430
|
+
</Chip>
|
|
431
|
+
</TableCell>
|
|
432
|
+
</TableRow>
|
|
433
|
+
{expandedRows.has(order.id) && (
|
|
434
|
+
<TableRow className="expanded-row">
|
|
435
|
+
<TableCell colSpan={6}>
|
|
436
|
+
<div className="expanded-content">
|
|
437
|
+
<Text type="BodyMedium">Order Items</Text>
|
|
438
|
+
<div className="items-list">
|
|
439
|
+
{order.items.map((item, index) => (
|
|
440
|
+
<div key={index} className="order-item">
|
|
441
|
+
<Text type="BodySmall">{item.name}</Text>
|
|
442
|
+
<Text type="BodySmall">Qty: {item.quantity}</Text>
|
|
443
|
+
<Text type="BodySmall">${item.price}</Text>
|
|
444
|
+
</div>
|
|
445
|
+
))}
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</TableCell>
|
|
449
|
+
</TableRow>
|
|
450
|
+
)}
|
|
451
|
+
</React.Fragment>
|
|
452
|
+
))}
|
|
453
|
+
</TableBody>
|
|
454
|
+
</Table>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Infinite Scroll Body
|
|
460
|
+
```tsx
|
|
461
|
+
function InfiniteScrollBodyExample() {
|
|
462
|
+
const [data, setData] = useState([]);
|
|
463
|
+
const [loading, setLoading] = useState(false);
|
|
464
|
+
const [hasMore, setHasMore] = useState(true);
|
|
465
|
+
|
|
466
|
+
const loadMoreData = useCallback(async () => {
|
|
467
|
+
if (loading || !hasMore) return;
|
|
468
|
+
|
|
469
|
+
setLoading(true);
|
|
470
|
+
// Simulate API call
|
|
471
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
472
|
+
|
|
473
|
+
const newItems = Array.from({ length: 20 }, (_, i) => ({
|
|
474
|
+
id: data.length + i + 1,
|
|
475
|
+
name: `Item ${data.length + i + 1}`,
|
|
476
|
+
value: Math.floor(Math.random() * 1000),
|
|
477
|
+
status: ['Active', 'Inactive', 'Pending'][Math.floor(Math.random() * 3)]
|
|
478
|
+
}));
|
|
479
|
+
|
|
480
|
+
setData(prev => [...prev, ...newItems]);
|
|
481
|
+
setLoading(false);
|
|
482
|
+
|
|
483
|
+
// Stop loading after 100 items
|
|
484
|
+
if (data.length + newItems.length >= 100) {
|
|
485
|
+
setHasMore(false);
|
|
486
|
+
}
|
|
487
|
+
}, [data.length, loading, hasMore]);
|
|
488
|
+
|
|
489
|
+
useEffect(() => {
|
|
490
|
+
loadMoreData();
|
|
491
|
+
}, []);
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<div className="infinite-scroll-container" style={{ height: '400px', overflow: 'auto' }}>
|
|
495
|
+
<Table>
|
|
496
|
+
<TableHeader>
|
|
497
|
+
<TableRow>
|
|
498
|
+
<TableHeaderCell>Name</TableHeaderCell>
|
|
499
|
+
<TableHeaderCell>Value</TableHeaderCell>
|
|
500
|
+
<TableHeaderCell>Status</TableHeaderCell>
|
|
501
|
+
</TableRow>
|
|
502
|
+
</TableHeader>
|
|
503
|
+
<TableBody className="infinite-scroll-body">
|
|
504
|
+
{data.map(item => (
|
|
505
|
+
<TableRow key={item.id}>
|
|
506
|
+
<TableCell>{item.name}</TableCell>
|
|
507
|
+
<TableCell>{item.value}</TableCell>
|
|
508
|
+
<TableCell>
|
|
509
|
+
<Chip style={item.status === 'Active' ? 'Success' : 'Default'}>
|
|
510
|
+
{item.status}
|
|
511
|
+
</Chip>
|
|
512
|
+
</TableCell>
|
|
513
|
+
</TableRow>
|
|
514
|
+
))}
|
|
515
|
+
{loading && (
|
|
516
|
+
<TableRow>
|
|
517
|
+
<TableCell colSpan={3} className="loading-row">
|
|
518
|
+
<div className="loading-indicator">
|
|
519
|
+
<Spinner size="Small" />
|
|
520
|
+
<Text type="BodySmall">Loading more items...</Text>
|
|
521
|
+
</div>
|
|
522
|
+
</TableCell>
|
|
523
|
+
</TableRow>
|
|
524
|
+
)}
|
|
525
|
+
{!hasMore && data.length > 0 && (
|
|
526
|
+
<TableRow>
|
|
527
|
+
<TableCell colSpan={3} className="end-row">
|
|
528
|
+
<Text type="BodySmall">No more items to load</Text>
|
|
529
|
+
</TableCell>
|
|
530
|
+
</TableRow>
|
|
531
|
+
)}
|
|
532
|
+
</TableBody>
|
|
533
|
+
</Table>
|
|
534
|
+
</div>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Drag and Drop Body
|
|
540
|
+
```tsx
|
|
541
|
+
function DragDropBodyExample() {
|
|
542
|
+
const [items, setItems] = useState([
|
|
543
|
+
{ id: 1, task: 'High priority task', priority: 1 },
|
|
544
|
+
{ id: 2, task: 'Medium priority task', priority: 2 },
|
|
545
|
+
{ id: 3, task: 'Low priority task', priority: 3 },
|
|
546
|
+
{ id: 4, task: 'Another task', priority: 4 }
|
|
547
|
+
]);
|
|
548
|
+
|
|
549
|
+
const [draggedItem, setDraggedItem] = useState(null);
|
|
550
|
+
|
|
551
|
+
const handleDragStart = (e, item) => {
|
|
552
|
+
setDraggedItem(item);
|
|
553
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const handleDragOver = (e) => {
|
|
557
|
+
e.preventDefault();
|
|
558
|
+
e.dataTransfer.dropEffect = 'move';
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const handleDrop = (e, targetItem) => {
|
|
562
|
+
e.preventDefault();
|
|
563
|
+
if (!draggedItem || draggedItem.id === targetItem.id) return;
|
|
564
|
+
|
|
565
|
+
const newItems = [...items];
|
|
566
|
+
const draggedIndex = newItems.findIndex(item => item.id === draggedItem.id);
|
|
567
|
+
const targetIndex = newItems.findIndex(item => item.id === targetItem.id);
|
|
568
|
+
|
|
569
|
+
// Remove dragged item and insert at target position
|
|
570
|
+
const [draggedItemData] = newItems.splice(draggedIndex, 1);
|
|
571
|
+
newItems.splice(targetIndex, 0, draggedItemData);
|
|
572
|
+
|
|
573
|
+
// Update priority values
|
|
574
|
+
const updatedItems = newItems.map((item, index) => ({
|
|
575
|
+
...item,
|
|
576
|
+
priority: index + 1
|
|
577
|
+
}));
|
|
578
|
+
|
|
579
|
+
setItems(updatedItems);
|
|
580
|
+
setDraggedItem(null);
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
return (
|
|
584
|
+
<Table>
|
|
585
|
+
<TableHeader>
|
|
586
|
+
<TableRow>
|
|
587
|
+
<TableHeaderCell>Priority</TableHeaderCell>
|
|
588
|
+
<TableHeaderCell>Task</TableHeaderCell>
|
|
589
|
+
<TableHeaderCell>Actions</TableHeaderCell>
|
|
590
|
+
</TableRow>
|
|
591
|
+
</TableHeader>
|
|
592
|
+
<TableBody className="draggable-body">
|
|
593
|
+
{items.map(item => (
|
|
594
|
+
<TableRow
|
|
595
|
+
key={item.id}
|
|
596
|
+
draggable
|
|
597
|
+
onDragStart={(e) => handleDragStart(e, item)}
|
|
598
|
+
onDragOver={handleDragOver}
|
|
599
|
+
onDrop={(e) => handleDrop(e, item)}
|
|
600
|
+
className={`draggable-row ${draggedItem?.id === item.id ? 'dragging' : ''}`}
|
|
601
|
+
>
|
|
602
|
+
<TableCell>
|
|
603
|
+
<div className="priority-cell">
|
|
604
|
+
<Icon icon="DragIndicator" className="drag-handle" />
|
|
605
|
+
<Text type="BodyMedium">{item.priority}</Text>
|
|
606
|
+
</div>
|
|
607
|
+
</TableCell>
|
|
608
|
+
<TableCell>{item.task}</TableCell>
|
|
609
|
+
<TableCell>
|
|
610
|
+
<Button size="Small" type="Ghost">Edit</Button>
|
|
611
|
+
</TableCell>
|
|
612
|
+
</TableRow>
|
|
613
|
+
))}
|
|
614
|
+
</TableBody>
|
|
615
|
+
</Table>
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Virtual Scrolling Body
|
|
621
|
+
```tsx
|
|
622
|
+
function VirtualScrollBodyExample() {
|
|
623
|
+
const [data] = useState(
|
|
624
|
+
Array.from({ length: 10000 }, (_, i) => ({
|
|
625
|
+
id: i + 1,
|
|
626
|
+
name: `User ${i + 1}`,
|
|
627
|
+
email: `user${i + 1}@example.com`,
|
|
628
|
+
score: Math.floor(Math.random() * 1000)
|
|
629
|
+
}))
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
633
|
+
const itemHeight = 50;
|
|
634
|
+
const containerHeight = 400;
|
|
635
|
+
const visibleCount = Math.ceil(containerHeight / itemHeight);
|
|
636
|
+
const startIndex = Math.floor(scrollTop / itemHeight);
|
|
637
|
+
const endIndex = Math.min(startIndex + visibleCount + 5, data.length);
|
|
638
|
+
const visibleItems = data.slice(startIndex, endIndex);
|
|
639
|
+
|
|
640
|
+
const handleScroll = (e) => {
|
|
641
|
+
setScrollTop(e.target.scrollTop);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
return (
|
|
645
|
+
<div
|
|
646
|
+
className="virtual-scroll-container"
|
|
647
|
+
style={{ height: containerHeight, overflow: 'auto' }}
|
|
648
|
+
onScroll={handleScroll}
|
|
649
|
+
>
|
|
650
|
+
<Table>
|
|
651
|
+
<TableHeader>
|
|
652
|
+
<TableRow>
|
|
653
|
+
<TableHeaderCell>ID</TableHeaderCell>
|
|
654
|
+
<TableHeaderCell>Name</TableHeaderCell>
|
|
655
|
+
<TableHeaderCell>Email</TableHeaderCell>
|
|
656
|
+
<TableHeaderCell>Score</TableHeaderCell>
|
|
657
|
+
</TableRow>
|
|
658
|
+
</TableHeader>
|
|
659
|
+
<TableBody
|
|
660
|
+
className="virtual-body"
|
|
661
|
+
style={{
|
|
662
|
+
height: data.length * itemHeight,
|
|
663
|
+
paddingTop: startIndex * itemHeight
|
|
664
|
+
}}
|
|
665
|
+
>
|
|
666
|
+
{visibleItems.map(item => (
|
|
667
|
+
<TableRow key={item.id} style={{ height: itemHeight }}>
|
|
668
|
+
<TableCell>{item.id}</TableCell>
|
|
669
|
+
<TableCell>{item.name}</TableCell>
|
|
670
|
+
<TableCell>{item.email}</TableCell>
|
|
671
|
+
<TableCell>{item.score}</TableCell>
|
|
672
|
+
</TableRow>
|
|
673
|
+
))}
|
|
674
|
+
</TableBody>
|
|
675
|
+
</Table>
|
|
676
|
+
</div>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
```
|