@ceed/cds 1.18.0 → 1.19.0-next.1

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.
@@ -1,8 +1,9 @@
1
1
  # DataTable
2
2
 
3
- DataTable 구조화된 데이터를 효율적으로 표시하고 상호작용할 있는 고급 테이블 컴포넌트입니다. 정렬, 편집, 페이지네이션, 선택 다양한 기능을 제공합니다.
3
+ DataTable is a table component that displays structured data and provides various interactions such as sorting, selection, editing, and pagination.
4
+ The examples below can be tested in the story canvas, and the displayed code can be reused as-is.
4
5
 
5
- ## 기본 사용법
6
+ ## Basic Usage
6
7
 
7
8
  ```tsx
8
9
  <DataTable
@@ -38,116 +39,1091 @@ DataTable은 구조화된 데이터를 효율적으로 표시하고 상호작용
38
39
  | -------- | ----------- | ------- |
39
40
  | editMode | — | false |
40
41
 
42
+ ### Required Data Structure
43
+
44
+ - `rows` is an array, and by default, each row must include an `id` field.
45
+ - If there is no `id`, you can specify a row identifier using `getId`.
46
+
47
+ ```tsx
48
+ <DataTable
49
+ rows={rows}
50
+ columns={columns}
51
+ />
52
+
53
+ <DataTable
54
+ rows={rows}
55
+ columns={columns}
56
+ getId={(row) => row.uuid}
57
+ />
58
+ ```
59
+
60
+ ## Column Definition (ColumnDef)
61
+
62
+ ### Basic Options
63
+
64
+ ```tsx
65
+ const columns = [
66
+ {
67
+ field: 'name',
68
+ headerName: 'Name',
69
+ width: '30%',
70
+ minWidth: '120px',
71
+ maxWidth: '320px',
72
+ sortable: true,
73
+ description: 'Description displayed as a tooltip',
74
+ headerClassName: 'table-head',
75
+ cellClassName: ({ row }) => (row.isHot ? 'cell-hot' : undefined),
76
+ },
77
+ ];
78
+ ```
79
+
80
+ ### Column Types
81
+
82
+ DataTable supports various column types. Each type provides an appropriate input UI in edit mode.
83
+
84
+ #### text (default)
85
+
86
+ The default text type. If `type` is not specified, it defaults to text.
87
+
88
+ ```tsx
89
+ {
90
+ field: 'name',
91
+ headerName: 'Name',
92
+ type: 'text', // can be omitted
93
+ }
94
+ ```
95
+
96
+ #### number
97
+
98
+ A type for numeric input.
99
+
100
+ ```tsx
101
+ {
102
+ field: 'quantity',
103
+ headerName: 'Quantity',
104
+ type: 'number',
105
+ }
106
+ ```
107
+
108
+ #### currency
109
+
110
+ A type for currency input. Uses the `CurrencyInput` component.
111
+
112
+ ```tsx
113
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
114
+ ```
115
+
116
+ ```tsx
117
+ {
118
+ field: 'price',
119
+ headerName: 'Price',
120
+ type: 'currency',
121
+ componentProps: {
122
+ currencyDisplay: 'narrowSymbol',
123
+ currency: 'USD',
124
+ },
125
+ }
126
+ ```
127
+
128
+ #### date
129
+
130
+ A type for date selection. Uses the `DatePicker` component.
131
+
132
+ ```tsx
133
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
134
+ ```
135
+
136
+ ```tsx
137
+ {
138
+ field: 'dueDate',
139
+ headerName: 'Due Date',
140
+ type: 'date',
141
+ componentProps: {
142
+ format: 'yyyy-MM-dd',
143
+ },
144
+ }
145
+ ```
146
+
147
+ #### select
148
+
149
+ A type for dropdown selection. Uses the `Select` component.
150
+
151
+ ```tsx
152
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
153
+ ```
154
+
155
+ ```tsx
156
+ {
157
+ field: 'status',
158
+ headerName: 'Status',
159
+ type: 'select',
160
+ componentProps: {
161
+ options: ['Pending', 'In Progress', 'Completed', 'Cancelled'],
162
+ },
163
+ }
164
+ ```
165
+
166
+ #### autocomplete
167
+
168
+ A type for autocomplete selection. Uses the `Autocomplete` component.
169
+
170
+ ```tsx
171
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
172
+ ```
173
+
174
+ ```tsx
175
+ {
176
+ field: 'assignee',
177
+ headerName: 'Assignee',
178
+ type: 'autocomplete',
179
+ componentProps: {
180
+ options: ['Alice', 'Bob', 'Charlie', 'Diana'],
181
+ placeholder: 'Select assignee...',
182
+ },
183
+ }
184
+ ```
185
+
186
+ #### longText
187
+
188
+ A type for long text input. Uses the `Textarea` component.
189
+
190
+ ```tsx
191
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
192
+ ```
193
+
194
+ ```tsx
195
+ {
196
+ field: 'description',
197
+ headerName: 'Description',
198
+ type: 'longText',
199
+ componentProps: {
200
+ minRows: 2,
201
+ maxRows: 4,
202
+ },
203
+ }
204
+ ```
205
+
206
+ #### link
207
+
208
+ A link type. Renders as a clickable link.
209
+
210
+ ```tsx
211
+ <DataTable rows={tableRows} columns={columns} noWrap />
212
+ ```
213
+
214
+ ```tsx
215
+ {
216
+ field: 'website',
217
+ headerName: 'Website',
218
+ type: 'link',
219
+ componentProps: ({ row }) => ({
220
+ href: row.website,
221
+ target: '_blank',
222
+ }),
223
+ }
224
+ ```
225
+
226
+ #### actions
227
+
228
+ A type for row-level action buttons.
229
+
230
+ ```tsx
231
+ <DataTable rows={rows4} columns={columns} pinnedColumns={{
232
+ right: ['actions']
233
+ }} />
234
+ ```
235
+
236
+ ```tsx
237
+ {
238
+ field: 'actions',
239
+ type: 'actions',
240
+ getActions: ({ row }) => [
241
+ <Button key="view" size="sm">View</Button>,
242
+ <Button key="delete" size="sm" color="danger">Delete</Button>,
243
+ ],
244
+ }
245
+ ```
246
+
247
+ ### Using componentProps
248
+
249
+ `componentProps` can be specified as an object or a function. When specified as a function, you can dynamically set props based on row data.
250
+
251
+ ```tsx
252
+ // Static props
253
+ componentProps: {
254
+ options: ['A', 'B', 'C'],
255
+ }
256
+
257
+ // Dynamic props (function)
258
+ componentProps: ({ row, id }) => ({
259
+ options: row.availableOptions,
260
+ disabled: row.isLocked,
261
+ })
262
+ ```
263
+
264
+ ## Row Interactions
265
+
266
+ ### Row Hover
267
+
268
+ ```tsx
269
+ <DataTable {...args} />
270
+ ```
271
+
272
+ ### Checkbox Selection
273
+
274
+ ```tsx
275
+ <DataTable {...args} />
276
+ ```
277
+
278
+ #### Controlling Selection Model
279
+
280
+ To control checkbox selection, use `selectionModel` and `onSelectionModelChange` together.
281
+
282
+ ```tsx
283
+ const [selectionModel, setSelectionModel] = useState<string[]>([]);
284
+
285
+ <DataTable
286
+ rows={rows}
287
+ columns={columns}
288
+ checkboxSelection
289
+ selectionModel={selectionModel}
290
+ onSelectionModelChange={setSelectionModel}
291
+ />;
292
+ ```
293
+
294
+ #### Notes on Selection Control
295
+
296
+ - `isRowSelectable` can control whether each row is selectable.
297
+ - `disableSelectionOnClick` prevents selection when clicking on a row.
298
+ - `isTotalSelected` is used to represent the total selection state in server pagination scenarios.
299
+
300
+ ```tsx
301
+ <DataTable
302
+ rows={rows}
303
+ columns={columns}
304
+ checkboxSelection
305
+ isRowSelectable={({ row }) => row.status !== 'Closed'}
306
+ disableSelectionOnClick
307
+ isTotalSelected
308
+ />
309
+ ```
310
+
311
+ ### Applying Selection Conditions
312
+
313
+ ```tsx
314
+ <Stack height="500px" gap={1}>
315
+ <Typography level="title-sm" textColor="text.secondary">
316
+ status가 Closed인 행은 선택할 수 없습니다.
317
+ </Typography>
318
+ <DataTable checkboxSelection stripe="even" hoverRow stickyHeader rows={rows3} columns={columns} selectionModel={selectionModel} onSelectionModelChange={newSelection => setSelectionModel(newSelection)} isRowSelectable={({
319
+ row
320
+ }) => row.status !== 'Closed'} />
321
+ </Stack>
322
+ ```
323
+
324
+ ### Disabling Click Selection
325
+
326
+ ```tsx
327
+ <Stack height="500px">
328
+ <Typography level="title-sm" textColor="text.secondary">
329
+ Disable selection on click.
330
+ </Typography>
331
+ <DataTable checkboxSelection stripe="even" hoverRow selectionModel={selectedId} stickyHeader onSelectionModelChange={newSelection => setSelectedId(newSelection)} disableSelectionOnClick rows={rows4} columns={columns} />
332
+ </Stack>
333
+ ```
334
+
335
+ ### Total Selection (isTotalSelected)
336
+
337
+ Used to manage the total selection state including data beyond the current page in server pagination environments.
338
+
339
+ ```tsx
340
+ <Stack gap={2}>
341
+ <Box sx={{
342
+ p: 2,
343
+ bgcolor: 'primary.50',
344
+ borderRadius: '8px'
345
+ }}>
346
+ <Typography level="body-sm">
347
+ {isTotalSelected ? `All ${allRows.length} items are selected (across all pages)` : `${selectionModel.length} items selected on current page`}
348
+ </Typography>
349
+ </Box>
350
+ <DataTable rows={pagedRows} columns={columns} checkboxSelection selectionModel={selectionModel} onSelectionModelChange={(newSelection, totalSelected) => {
351
+ setSelectionModel(newSelection);
352
+ if (totalSelected !== undefined) {
353
+ setIsTotalSelected(totalSelected);
354
+ }
355
+ }} isTotalSelected={isTotalSelected} pagination paginationMode="server" rowCount={allRows.length} paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} noWrap />
356
+ </Stack>
357
+ ```
358
+
359
+ ```tsx
360
+ const [isTotalSelected, setIsTotalSelected] = useState(false);
361
+ const [selectionModel, setSelectionModel] = useState<string[]>([]);
362
+
363
+ <DataTable
364
+ rows={rows}
365
+ columns={columns}
366
+ checkboxSelection
367
+ pagination
368
+ paginationMode="server"
369
+ isTotalSelected={isTotalSelected}
370
+ selectionModel={selectionModel}
371
+ onSelectionModelChange={(newModel, totalSelected) => {
372
+ setSelectionModel(newModel);
373
+ if (totalSelected !== undefined) {
374
+ setIsTotalSelected(totalSelected);
375
+ }
376
+ }}
377
+ />;
378
+ ```
379
+
380
+ ## Styles and Layout
381
+
382
+ ### Back Office Style
383
+
384
+ ```tsx
385
+ <DataTable rows={args.rows} columns={args.columns} checkboxSelection={args.checkboxSelection} hoverRow={args.hoverRow} noWrap={args.noWrap} stripe={args.stripe} stickyHeader={args.stickyHeader} slots={args.slots} slotProps={args.slotProps} selectionModel={selectionModel} onSelectionModelChange={setSelectionModel} />
386
+ ```
387
+
388
+ #### Commonly Used Table Options
389
+
390
+ - `stickyHeader`: Fixes the header
391
+ - `stripe`: Row striping (`"even" | "odd"`)
392
+ - `noWrap`: Prevents cell text wrapping
393
+ - `slots`, `slotProps`: Customize toolbar/footer/loading overlay/background
394
+
395
+ ```tsx
396
+ <DataTable
397
+ rows={rows}
398
+ columns={columns}
399
+ stickyHeader
400
+ stripe="even"
401
+ noWrap
402
+ slots={{
403
+ toolbar: CustomToolbar,
404
+ footer: CustomFooter,
405
+ loadingOverlay: CustomLoading,
406
+ }}
407
+ slotProps={{
408
+ background: { style: { height: '300px' } },
409
+ }}
410
+ />
411
+ ```
412
+
413
+ ### Loading State
414
+
415
+ ```tsx
416
+ <DataTable {...args} />
417
+ ```
418
+
419
+ ### Virtual Scrolling for Large Data
420
+
421
+ ```tsx
422
+ <Stack height="500px">
423
+ <Typography level="title-sm" textColor="text.secondary">
424
+ Virtualized DataTable
425
+ </Typography>
426
+ <DataTable checkboxSelection stripe="even" hoverRow selectionModel={selectedId} stickyHeader onSelectionModelChange={newSelection => setSelectedId(newSelection)} rows={[...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4].map((row, i) => ({
427
+ ...row,
428
+ id: i
429
+ }))} columns={columns} sortModel={[{
430
+ field: 'number',
431
+ sort: 'asc'
432
+ }]} />
433
+ </Stack>
434
+ ```
435
+
436
+ ## Slots & SlotProps Customization
437
+
438
+ DataTable allows customization of various parts through `slots` and `slotProps`.
439
+
440
+ ### Available Slots
441
+
442
+ | Slot | Description |
443
+ | ---------------- | ---------------------------- |
444
+ | `checkbox` | Customize checkbox component |
445
+ | `toolbar` | Table top toolbar |
446
+ | `footer` | Table bottom footer |
447
+ | `loadingOverlay` | Loading overlay |
448
+
449
+ ### Custom Toolbar
450
+
451
+ ```tsx
452
+ <DataTable rows={filteredRows} columns={columns} slots={{
453
+ toolbar: CustomToolbar
454
+ }} noWrap stickyHeader stripe="even" />
455
+ ```
456
+
457
+ ```tsx
458
+ const CustomToolbar = () => (
459
+ <Box sx={{ p: 2, display: 'flex', gap: 1, borderBottom: '1px solid', borderColor: 'neutral.200' }}>
460
+ <Button size="sm">Export</Button>
461
+ <Button size="sm">Filter</Button>
462
+ </Box>
463
+ );
464
+
465
+ <DataTable rows={rows} columns={columns} slots={{ toolbar: CustomToolbar }} />;
466
+ ```
467
+
468
+ ### Custom Footer
469
+
470
+ ```tsx
471
+ <DataTable rows={tableRows} columns={columns} slots={{
472
+ footer: CustomFooter
473
+ }} noWrap />
474
+ ```
475
+
476
+ ```tsx
477
+ const CustomFooter = () => (
478
+ <Box sx={{ p: 2, textAlign: 'center', borderTop: '1px solid', borderColor: 'neutral.200' }}>
479
+ <Typography level="body-sm">Total {rows.length} items</Typography>
480
+ </Box>
481
+ );
482
+
483
+ <DataTable rows={rows} columns={columns} slots={{ footer: CustomFooter }} />;
484
+ ```
485
+
486
+ ### Custom Loading Overlay
487
+
488
+ ```tsx
489
+ <Stack gap={2}>
490
+ <Button onClick={() => setLoading(!loading)}>{loading ? 'Stop Loading' : 'Start Loading'}</Button>
491
+ <DataTable rows={tableRows} columns={columns} loading={loading} slots={{
492
+ loadingOverlay: CustomLoadingOverlay
493
+ }} noWrap slotProps={{
494
+ background: {
495
+ style: {
496
+ height: '300px'
497
+ }
498
+ }
499
+ }} />
500
+ </Stack>
501
+ ```
502
+
503
+ ```tsx
504
+ const CustomLoadingOverlay = () => (
505
+ <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
506
+ <CircularProgress />
507
+ <Typography>Loading data...</Typography>
508
+ </Box>
509
+ );
510
+
511
+ <DataTable rows={rows} columns={columns} loading slots={{ loadingOverlay: CustomLoadingOverlay }} />;
512
+ ```
513
+
514
+ ### Custom Checkbox
515
+
516
+ ```tsx
517
+ <DataTable rows={tableRows} columns={columns} checkboxSelection selectionModel={selectionModel} onSelectionModelChange={newSelection => setSelectionModel(newSelection)} slots={{
518
+ checkbox: CustomCheckbox
519
+ }} noWrap />
520
+ ```
521
+
522
+ ```tsx
523
+ const CustomCheckbox = (props) => (
524
+ <Box
525
+ onClick={props.onChange}
526
+ sx={{
527
+ width: 20,
528
+ height: 20,
529
+ borderRadius: '4px',
530
+ border: '2px solid',
531
+ borderColor: props.checked ? 'primary.500' : 'neutral.400',
532
+ backgroundColor: props.checked ? 'primary.500' : 'transparent',
533
+ cursor: 'pointer',
534
+ }}
535
+ >
536
+ {props.checked && <span>✓</span>}
537
+ </Box>
538
+ );
539
+
540
+ <DataTable rows={rows} columns={columns} checkboxSelection slots={{ checkbox: CustomCheckbox }} />;
541
+ ```
542
+
543
+ ### Background Styling (slotProps.background)
544
+
545
+ You can style the table background area using `slotProps.background`. Height specification is required when used with `stickyHeader`.
546
+
547
+ ```tsx
548
+ <DataTable
549
+ rows={rows}
550
+ columns={columns}
551
+ stickyHeader
552
+ slotProps={{
553
+ background: {
554
+ style: { height: '400px' },
555
+ },
556
+ }}
557
+ />
558
+ ```
559
+
560
+ ## Column Configuration
561
+
562
+ ### Displaying id Column
563
+
564
+ ```tsx
565
+ <DataTable {...args} />
566
+ ```
567
+
568
+ ### Customizing Column Rendering
569
+
570
+ ```tsx
571
+ <DataTable rows={useMemo(() => [...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3].map((row, i) => ({
572
+ ...row,
573
+ id: i + 1
574
+ })), [])} columns={columns} checkboxSelection stickyHeader stripe="even" noWrap slotProps={{
575
+ background: {
576
+ style: {
577
+ height: '600px'
578
+ }
579
+ }
580
+ }} />
581
+ ```
582
+
583
+ ### Complex UI in Cells
584
+
585
+ ```tsx
586
+ <DataTable rows={rows4} columns={columns} />
587
+ ```
588
+
589
+ ### Description (tooltip)
590
+
591
+ ```tsx
592
+ <DataTable rows={rows4} columns={columns} />
593
+ ```
594
+
595
+ ### Customizing Cell/Header Classes
596
+
597
+ ```tsx
598
+ <DataTable
599
+ onPaginationModelChange={fn()}
600
+ onSelectionModelChange={fn()}
601
+ onRowClick={fn()}
602
+ columns={[{
603
+ field: 'id',
604
+ headerName: 'ID',
605
+ width: '150px',
606
+ cellClassName: ({
607
+ row
608
+ }) => row.number > 5 ? 'red' : 'blue'
609
+ }, {
610
+ field: 'number',
611
+ headerName: 'Number',
612
+ type: 'number',
613
+ width: '150px',
614
+ cellClassName: ({
615
+ value
616
+ }) => value > 5 ? 'red' : 'blue'
617
+ }, {
618
+ field: 'string',
619
+ headerName: 'String',
620
+ width: '600px',
621
+ cellClassName: ({
622
+ row
623
+ }) => row.number > 5 ? 'red' : 'blue'
624
+ }]}
625
+ rows={rows4}
626
+ />
627
+ ```
628
+
629
+ ```tsx
630
+ <DataTable
631
+ onPaginationModelChange={fn()}
632
+ onSelectionModelChange={fn()}
633
+ onRowClick={fn()}
634
+ columns={[{
635
+ field: 'id',
636
+ headerName: 'ID',
637
+ width: '150px',
638
+ headerClassName: 'red'
639
+ }, {
640
+ field: 'number',
641
+ headerName: 'Number',
642
+ type: 'number',
643
+ width: '150px',
644
+ headerClassName: 'red'
645
+ }, {
646
+ field: 'string',
647
+ headerName: 'String',
648
+ width: '600px',
649
+ headerClassName: 'red'
650
+ }, {
651
+ field: 'date',
652
+ headerName: 'Date Sort',
653
+ width: '150px',
654
+ renderCell: ({
655
+ value
656
+ }) => value.toLocaleDateString(),
657
+ headerClassName: 'blue'
658
+ }, {
659
+ field: 'object',
660
+ headerName: 'Object Sort',
661
+ width: '150px',
662
+ renderCell: ({
663
+ value
664
+ }) => value.age,
665
+ type: 'number',
666
+ sortComparator: ({
667
+ rowA,
668
+ rowB
669
+ }) => rowA.object.age - rowB.object.age,
670
+ headerClassName: 'blue'
671
+ }]}
672
+ rows={rows4}
673
+ checkboxSelection
674
+ hoverRow
675
+ noWrap
676
+ stripe="even"
677
+ stickyHeader
678
+ columnGroupingModel={[{
679
+ groupId: 'group1',
680
+ headerName: 'Group 1',
681
+ headerClassName: 'red',
682
+ children: [{
683
+ field: 'id'
684
+ }, {
685
+ field: 'number'
686
+ }, {
687
+ field: 'string'
688
+ }]
689
+ }, {
690
+ groupId: 'group2',
691
+ headerName: '',
692
+ children: [{
693
+ field: 'date'
694
+ }, {
695
+ field: 'object'
696
+ }]
697
+ }]}
698
+ />
699
+ ```
700
+
701
+ ### Column Groups
702
+
41
703
  ```tsx
42
- import { DataTable } from '@ceed/cds';
704
+ <DataTable
705
+ onPaginationModelChange={fn()}
706
+ onSelectionModelChange={fn()}
707
+ onRowClick={fn()}
708
+ columns={[{
709
+ field: 'id',
710
+ headerName: 'ID',
711
+ width: '150px'
712
+ }, {
713
+ field: 'number',
714
+ headerName: 'Number Sort',
715
+ type: 'number',
716
+ width: '900px',
717
+ cellClassName: () => 'warn'
718
+ }, {
719
+ field: 'string',
720
+ headerName: 'String Sort',
721
+ width: '150px'
722
+ }, {
723
+ field: 'date',
724
+ headerName: 'Date Sort',
725
+ width: '150px',
726
+ renderCell: ({
727
+ value
728
+ }) => value.toLocaleDateString(),
729
+ cellClassName: () => 'primary'
730
+ }, {
731
+ field: 'object',
732
+ headerName: 'Object Sort',
733
+ width: '150px',
734
+ renderCell: ({
735
+ value
736
+ }) => value.age,
737
+ type: 'number',
738
+ sortComparator: ({
739
+ rowA,
740
+ rowB
741
+ }) => rowA.object.age - rowB.object.age,
742
+ cellClassName: () => 'primary'
743
+ }]}
744
+ rows={[...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4, ...rows4].map((row, i) => ({
745
+ ...row,
746
+ id: i,
747
+ date: new Date(row.date),
748
+ object: {
749
+ age: row.object.age + i
750
+ }
751
+ }))}
752
+ checkboxSelection
753
+ hoverRow
754
+ noWrap
755
+ stripe="even"
756
+ stickyHeader
757
+ columnGroupingModel={[{
758
+ groupId: 'group1',
759
+ headerName: 'Group 1',
760
+ children: [{
761
+ field: 'id'
762
+ }, {
763
+ field: 'number'
764
+ }, {
765
+ field: 'string'
766
+ }]
767
+ }, {
768
+ groupId: 'group2',
769
+ headerName: '',
770
+ children: [{
771
+ field: 'date'
772
+ }, {
773
+ field: 'object'
774
+ }]
775
+ }]}
776
+ slotProps={{
777
+ background: {
778
+ style: {
779
+ height: '300px'
780
+ }
781
+ }
782
+ }}
783
+ />
784
+ ```
785
+
786
+ ```tsx
787
+ const columnGroupingModel = [
788
+ {
789
+ groupId: 'nutrition',
790
+ headerName: 'Nutrition',
791
+ children: [{ field: 'calories' }, { field: 'fat' }, { field: 'carbs' }],
792
+ },
793
+ ];
794
+
795
+ <DataTable rows={rows} columns={columns} columnGroupingModel={columnGroupingModel} />;
796
+ ```
797
+
798
+ ### Column Resizing
799
+
800
+ Setting `resizable: true` on a column allows you to adjust the column width by dragging.
801
+
802
+ ```tsx
803
+ <Stack gap={1}>
804
+ <Typography level="body-sm" textColor="text.secondary">
805
+ Drag the column borders to resize columns
806
+ </Typography>
807
+ <DataTable rows={tableRows} columns={columns} noWrap />
808
+ </Stack>
809
+ ```
810
+
811
+ ```tsx
812
+ const columns = [
813
+ {
814
+ field: 'name',
815
+ headerName: 'Name',
816
+ width: '200px',
817
+ resizable: true,
818
+ },
819
+ {
820
+ field: 'description',
821
+ headerName: 'Description',
822
+ width: '300px',
823
+ resizable: true,
824
+ },
825
+ ];
826
+ ```
827
+
828
+ ## Editing Features
829
+
830
+ ### Inline Editing
831
+
832
+ ```tsx
833
+ <div>
834
+ <DataTable rows={rows3} columns={columns} checkboxSelection={false} noWrap editMode />
835
+ <Button onClick={() => {
836
+ console.log(editedRows);
837
+ setIsCellEditable(true);
838
+ }}>
839
+ Submit
840
+ </Button>
841
+ </div>
842
+ ```
843
+
844
+ #### Edit Types
43
845
 
846
+ - `text`, `number`, `currency`, `select`, `autocomplete`, `date`, `longText`, `link`
847
+
848
+ #### Notes on Editing
849
+
850
+ - `editMode` must be enabled for the editing UI to be active.
851
+ - Use `onCellEditStop` or `onCellEditStart` to reflect changed values.
852
+ - Specifying `renderEditCell` allows you to customize the editing UI.
853
+
854
+ ```tsx
44
855
  const columns = [
45
- { field: 'dessert', headerName: 'Dessert (100g serving)', width: '40%' },
46
- { field: 'calories', headerName: 'Calories', type: 'number' },
47
- { field: 'fat', headerName: 'Fat (g)', type: 'number' },
48
- { field: 'carbs', headerName: 'Carbs (g)', type: 'number' },
49
- { field: 'protein', headerName: 'Protein (g)', type: 'number' },
856
+ {
857
+ field: 'status',
858
+ type: 'select',
859
+ componentProps: { options: ['Opened', 'Preparing', 'Closed'] },
860
+ onCellEditStop: ({ row }) => updateRow(row),
861
+ },
862
+ {
863
+ field: 'note',
864
+ type: 'longText',
865
+ renderEditCell: ({ value }) => value.slice(0, 100),
866
+ },
50
867
  ];
51
868
 
52
- const rows = [
53
- { id: 1, dessert: 'Frozen yoghurt', calories: 159, fat: 6.0, carbs: 24, protein: 4.0 },
54
- { id: 2, dessert: 'Ice cream sandwich', calories: 237, fat: 9.0, carbs: 37, protein: 4.3 },
55
- // ... more data
56
- ];
869
+ <DataTable rows={rows} columns={columns} editMode />;
870
+ ```
871
+
872
+ ### Conditional Editing
873
+
874
+ ```tsx
875
+ <div>
876
+ <Typography level="title-lg" textColor="text.secondary">
877
+ expectedSales 100초과만 수정 가능
878
+ </Typography>
879
+ <DataTable rows={rows} columns={columns} checkboxSelection={false} noWrap editMode />
880
+ <Button onClick={() => {
881
+ console.log(rows);
882
+ }}>
883
+ Submit
884
+ </Button>
885
+ </div>
886
+ ```
887
+
888
+ ### Real-time Data Updates
57
889
 
58
- <DataTable columns={columns} rows={rows} />;
890
+ ```tsx
891
+ <DataTable rows={editRows} columns={columns} checkboxSelection={false} noWrap />
59
892
  ```
60
893
 
61
- ## 주요 기능
894
+ ### Required Field Indicator
62
895
 
63
- ### 상호작용
896
+ Setting `required: true` on a column displays a required indicator (\*) in the header.
64
897
 
65
- #### 행 호버 효과
898
+ ```tsx
899
+ <Stack gap={1}>
900
+ <Typography level="body-sm" textColor="text.secondary">
901
+ Fields marked with * are required
902
+ </Typography>
903
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
904
+ </Stack>
905
+ ```
66
906
 
67
907
  ```tsx
68
- <DataTable {...args} />
908
+ const columns = [
909
+ {
910
+ field: 'name',
911
+ headerName: 'Name',
912
+ required: true,
913
+ },
914
+ {
915
+ field: 'email',
916
+ headerName: 'Email',
917
+ required: true,
918
+ },
919
+ ];
69
920
  ```
70
921
 
71
- 호버 효과를 통해 사용자가 현재 포커스하고 있는 행을 명확하게 표시할 수 있습니다.
922
+ ### Customizing renderEditCell
923
+
924
+ Using `renderEditCell` allows you to fully customize the editing UI.
925
+
926
+ ```tsx
927
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
928
+ ```
72
929
 
73
930
  ```tsx
74
- <DataTable columns={columns} rows={rows} hoverRow={true} />
931
+ {
932
+ field: 'rating',
933
+ headerName: 'Rating',
934
+ renderEditCell: ({ value, row }) => (
935
+ <Box sx={{ display: 'flex', gap: 0.5 }}>
936
+ {[1, 2, 3, 4, 5].map((star) => (
937
+ <IconButton
938
+ key={star}
939
+ size="sm"
940
+ onClick={() => updateRating(row.id, star)}
941
+ >
942
+ {star <= value ? '★' : '☆'}
943
+ </IconButton>
944
+ ))}
945
+ </Box>
946
+ ),
947
+ }
75
948
  ```
76
949
 
77
- #### 체크박스 선택
950
+ ### Handling Edit Events
951
+
952
+ You can use `onCellEditStart` and `onCellEditStop` to detect when editing starts and ends.
78
953
 
79
954
  ```tsx
80
- <DataTable {...args} />
955
+ <Stack gap={2}>
956
+ <DataTable rows={tableRows} columns={columns} editMode noWrap />
957
+ <Box sx={{
958
+ p: 2,
959
+ bgcolor: 'neutral.100',
960
+ borderRadius: '8px',
961
+ fontFamily: 'monospace',
962
+ fontSize: '12px'
963
+ }}>
964
+ <Typography level="title-sm" sx={{
965
+ mb: 1
966
+ }}>
967
+ Edit Log:
968
+ </Typography>
969
+ {editLog.length === 0 ? <Typography level="body-xs" textColor="text.secondary">
970
+ No edits yet. Click on a cell to edit.
971
+ </Typography> : editLog.map((log, i) => <Typography key={i} level="body-xs">
972
+ {log}
973
+ </Typography>)}
974
+ </Box>
975
+ </Stack>
976
+ ```
977
+
978
+ ```tsx
979
+ {
980
+ field: 'value',
981
+ headerName: 'Value',
982
+ onCellEditStart: ({ row, originalRow, value }) => {
983
+ console.log('Edit started:', { row, originalRow, value });
984
+ },
985
+ onCellEditStop: ({ row, originalRow, value }) => {
986
+ console.log('Edit stopped:', { row, originalRow, value });
987
+ // Save changes to server
988
+ saveChanges(row);
989
+ },
990
+ }
81
991
  ```
82
992
 
83
- 여러 행을 선택할 수 있는 체크박스 기능을 제공합니다.
993
+ ## Event Handlers
994
+
995
+ ### onRowClick
996
+
997
+ Handles row click events. Useful for implementing Inspector patterns or detail view features.
998
+
999
+ ```tsx
1000
+ <Stack gap={2}>
1001
+ <DataTable rows={tableRows} columns={columns} hoverRow onRowClick={({
1002
+ row
1003
+ }) => setSelectedRow(row)} noWrap />
1004
+ {selectedRow && <Box sx={{
1005
+ p: 2,
1006
+ border: '1px solid',
1007
+ borderColor: 'neutral.300',
1008
+ borderRadius: '8px'
1009
+ }}>
1010
+ <Typography level="title-md">Selected Employee Details</Typography>
1011
+ <Typography level="body-sm">Name: {selectedRow.name}</Typography>
1012
+ <Typography level="body-sm">Email: {selectedRow.email}</Typography>
1013
+ <Typography level="body-sm">Department: {selectedRow.department}</Typography>
1014
+ </Box>}
1015
+ </Stack>
1016
+ ```
84
1017
 
85
1018
  ```tsx
1019
+ const [selectedRow, setSelectedRow] = useState(null);
1020
+
86
1021
  <DataTable
87
- columns={columns}
88
1022
  rows={rows}
89
- checkboxSelection={true}
90
- selectionModel={selectedRows}
91
- onSelectionModelChange={setSelectedRows}
92
- />
1023
+ columns={columns}
1024
+ hoverRow
1025
+ onRowClick={({ row, rowId }, event) => {
1026
+ console.log('Clicked row:', row, 'ID:', rowId);
1027
+ setSelectedRow(row);
1028
+ }}
1029
+ />;
1030
+ ```
1031
+
1032
+ ## Sorting Features
1033
+
1034
+ ### Basic Sorting
1035
+
1036
+ ```tsx
1037
+ <DataTable rows={rows4} columns={columns} />
1038
+ ```
1039
+
1040
+ ### Custom Sort Order
1041
+
1042
+ ```tsx
1043
+ <DataTable rows={rows4} columns={columns} sortOrder={['desc', 'asc']} />
1044
+ ```
1045
+
1046
+ ### Column-specific Sort Order
1047
+
1048
+ ```tsx
1049
+ <DataTable rows={rows4} columns={columns} />
1050
+ ```
1051
+
1052
+ ### Disabling Sorting
1053
+
1054
+ ```tsx
1055
+ <DataTable rows={rows4} columns={columns} />
1056
+ ```
1057
+
1058
+ ### Multiple Sorting
1059
+
1060
+ ```tsx
1061
+ <DataTable rows={rows4} columns={columns} />
93
1062
  ```
94
1063
 
95
- ### 백오피스 스타일 테이블
1064
+ ### Custom Sort Logic
96
1065
 
97
1066
  ```tsx
98
- <DataTable {...args} selectionModel={selectionModel} onSelectionModelChange={useCallback((v: any, ...params: any[]) => {
99
- setSelectionModel(v);
100
- args.onSelectionModelChange?.(v, ...params);
101
- }, [args.onSelectionModelChange])} />
1067
+ <DataTable rows={rows4} columns={columns} />
102
1068
  ```
103
1069
 
104
- 관리 도구나 대시보드에서 사용하기 적합한 고급 스타일링과 기능을 제공합니다:
1070
+ ### Controlling Sort State
105
1071
 
106
- - 고정 헤더 (stickyHeader)
107
- - 스트라이프 패턴 (stripe="even")
108
- - 텍스트 줄바꿈 방지 (noWrap)
109
- - 커스텀 툴바
1072
+ #### Uncontrolled Initial Sort
110
1073
 
111
1074
  ```tsx
112
1075
  <DataTable
113
- columns={columns}
1076
+ onPaginationModelChange={fn()}
1077
+ onSelectionModelChange={fn()}
1078
+ onRowClick={fn()}
1079
+ columns={[{
1080
+ field: 'dessert',
1081
+ headerName: 'Dessert (100g serving)',
1082
+ width: '40%'
1083
+ }, {
1084
+ field: 'calories',
1085
+ headerName: 'Calories',
1086
+ type: 'number'
1087
+ }, {
1088
+ field: 'fat',
1089
+ headerName: 'Fat (g)',
1090
+ type: 'number'
1091
+ }, {
1092
+ field: 'carbs',
1093
+ headerName: 'Carbs (g)',
1094
+ type: 'number'
1095
+ }, {
1096
+ field: 'protein',
1097
+ headerName: 'Protein (g)',
1098
+ type: 'number'
1099
+ }]}
114
1100
  rows={rows}
115
- checkboxSelection
116
- hoverRow
117
- noWrap
118
- stripe="even"
119
- stickyHeader
120
- slots={{
121
- toolbar: () => (
122
- <Stack direction="row" gap={1}>
123
- <Button variant="plain" color="neutral" size="sm">
124
- Action
125
- </Button>
126
- <Button variant="plain" color="danger" size="sm">
127
- Delete
128
- </Button>
129
- </Stack>
130
- ),
131
- }}
132
- slotProps={{
133
- background: { style: { height: '300px' } },
134
- }}
1101
+ initialState={{
1102
+ sorting: {
1103
+ sortModel: [{
1104
+ field: 'calories',
1105
+ sort: 'asc'
1106
+ }]
1107
+ }
1108
+ }}
135
1109
  />
136
1110
  ```
137
1111
 
138
- ### 로딩 상태
1112
+ #### Controlled Sort
139
1113
 
140
1114
  ```tsx
141
- <DataTable {...args} />
1115
+ <DataTable rows={rows4} columns={columns} sortModel={sortModel} onSortModelChange={handleSortModelChange} />
142
1116
  ```
143
1117
 
144
- 데이터 로딩 중일 때의 상태를 표시합니다.
145
-
146
1118
  ```tsx
147
- <DataTable columns={columns} rows={rows} loading={true} />
1119
+ const [sortModel, setSortModel] = useState([{ field: 'calories', sort: 'asc' }]);
1120
+
1121
+ <DataTable rows={rows} columns={columns} sortModel={sortModel} onSortModelChange={setSortModel} />;
148
1122
  ```
149
1123
 
150
- ### 페이지네이션
1124
+ ## Pagination
1125
+
1126
+ ### Basic Pagination
151
1127
 
152
1128
  ```tsx
153
1129
  <DataTable
@@ -191,19 +1167,7 @@ const rows = [
191
1167
  />
192
1168
  ```
193
1169
 
194
- 대량의 데이터를 페이지 단위로 나누어 표시할 수 있습니다.
195
-
196
- ```tsx
197
- <DataTable
198
- columns={columns}
199
- rows={rows}
200
- pagination={true}
201
- paginationModel={{ page: 0, pageSize: 20 }}
202
- onPaginationModelChange={setPaginationModel}
203
- />
204
- ```
205
-
206
- ### 빈 데이터 상태
1170
+ ### Empty State
207
1171
 
208
1172
  ```tsx
209
1173
  <DataTable
@@ -244,209 +1208,149 @@ const rows = [
244
1208
  />
245
1209
  ```
246
1210
 
247
- 데이터가 없을 때의 상태를 적절하게 표시합니다.
248
-
249
- ## 고급 기능
250
-
251
- ### 커스텀 셀 렌더링
1211
+ ### Sorting + Pagination
252
1212
 
253
1213
  ```tsx
254
- <DataTable rows={useMemo(() => [...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3, ...rows3].map((row, i) => ({
255
- ...row,
256
- id: i + 1
257
- })), [])} columns={columns} checkboxSelection stickyHeader stripe="even" noWrap slotProps={{
1214
+ <DataTable rows={rows} columns={columns} pagination slotProps={{
258
1215
  background: {
259
1216
  style: {
260
- height: '600px'
1217
+ height: '300px'
261
1218
  }
262
1219
  }
263
- }} />
1220
+ }} getId={(row: RowForSort) => row.id} />
264
1221
  ```
265
1222
 
266
- 셀의 내용을 커스터마이징할 수 있습니다:
1223
+ ### Server Pagination
267
1224
 
268
1225
  ```tsx
269
- const columns = [
270
- { field: 'id', headerName: 'ID' },
271
- {
272
- field: 'dessert',
273
- renderCell: ({ value, row }) => <Link href={row.storeLink}>{value}</Link>,
274
- },
275
- {
276
- field: 'location',
277
- renderCell: ({ value }) => value.join(', '),
278
- },
279
- {
280
- field: 'expectedSales',
281
- renderCell: ({ value }) => (value ? `$${value.toFixed(2)}` : ''),
282
- },
283
- ];
1226
+ <DataTable rows={pagedRows} columns={columns as any} pagination paginationMode="server" rowCount={allRows.length} paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} loading={loading} />
284
1227
  ```
285
1228
 
286
- ### 편집 가능한 테이블
1229
+ You can specify the total row count using `paginationMode="server"` and `rowCount`.
287
1230
 
288
1231
  ```tsx
289
- <div>
290
- <DataTable rows={rows3} columns={columns} checkboxSelection={false} noWrap editMode />
291
- <Button onClick={() => {
292
- console.log(editedRows);
293
- setIsCellEditable(true);
294
- }}>
295
- Submit
296
- </Button>
297
- </div>
1232
+ <DataTable
1233
+ rows={rows}
1234
+ columns={columns}
1235
+ pagination
1236
+ paginationMode="server"
1237
+ rowCount={totalRowCount}
1238
+ paginationModel={{ page: 0, pageSize: 20 }}
1239
+ onPaginationModelChange={setPaginationModel}
1240
+ />
298
1241
  ```
299
1242
 
300
- 인라인 편집 기능을 제공하며, 다양한 입력 타입을 지원합니다:
301
-
302
- - **text**: 기본 텍스트 입력
303
- - **number/currency**: 숫자/통화 입력
304
- - **select**: 드롭다운 선택
305
- - **autocomplete**: 자동완성 입력
306
- - **date**: 날짜 선택
307
- - **longText**: 긴 텍스트 입력
308
- - **link**: 링크 렌더링
1243
+ ### Dynamic rowCount
309
1244
 
310
1245
  ```tsx
311
- const columns = [
312
- {
313
- field: 'dessert',
314
- type: 'autocomplete',
315
- componentProps: () => ({
316
- options: ['Frozen yoghurt', 'Ice cream sandwich', 'Eclair'],
317
- }),
318
- required: true,
319
- onCellEditStop: handleCellEdit,
320
- },
321
- {
322
- field: 'status',
323
- type: 'select',
324
- componentProps: {
325
- options: ['Opened', 'Preparing', 'Closed'],
326
- },
327
- onCellEditStop: handleCellEdit,
328
- },
329
- {
330
- field: 'expectedSales',
331
- type: 'currency',
332
- onCellEditStop: handleCellEdit,
333
- },
334
- ];
335
-
336
- <DataTable rows={rows} columns={columns} editMode={true} />;
1246
+ <Stack gap={4} height="500px">
1247
+ <Stack direction="row" gap={2}>
1248
+ <Button onClick={handleIncrease}>Increase</Button>
1249
+ <Button onClick={handleDecrease}>Decrease</Button>
1250
+ </Stack>
1251
+ <DataTable rows={rows} columns={columns} pagination getId={(row: RowForSort) => row.id} />
1252
+ </Stack>
337
1253
  ```
338
1254
 
339
- ### 조건부 편집
340
-
341
- ```tsx
342
- <div>
343
- <Typography level="title-lg" textColor="text.secondary">
344
- expectedSales 100초과만 수정 가능
345
- </Typography>
346
- <DataTable rows={rows} columns={columns} checkboxSelection={false} noWrap editMode />
347
- <Button onClick={() => {
348
- console.log(rows);
349
- }}>
350
- Submit
351
- </Button>
352
- </div>
353
- ```
1255
+ ## Advanced Layout
354
1256
 
355
- 특정 조건에 따라 셀의 편집 가능 여부를 결정할 수 있습니다:
1257
+ ### Pinned Columns
356
1258
 
357
1259
  ```tsx
358
- const columns = [
359
- {
360
- field: 'dessert',
361
- type: 'autocomplete',
362
- isCellEditable: ({ row }) => row.expectedSales > 100,
363
- onCellEditStop: handleRowChange,
364
- },
365
- {
366
- field: 'expectedSales',
367
- type: 'currency',
368
- isCellEditable: true, // 항상 편집 가능
369
- onCellEditStop: handleRowChange,
370
- },
371
- {
372
- field: 'note',
373
- type: 'longText',
374
- isCellEditable: ({ row }) => row.expectedSales > 100,
375
- },
376
- ];
1260
+ <DataTable rows={args.rows} columns={args.columns} pinnedColumns={args.pinnedColumns} stickyHeader={args.stickyHeader} stripe={args.stripe} noWrap={args.noWrap} hoverRow={args.hoverRow} selectionModel={selectionModel} onSelectionModelChange={setSelectionModel} />
377
1261
  ```
378
1262
 
379
- ## 정렬 기능
380
-
381
- ### 기본 정렬
382
-
383
1263
  ```tsx
384
- <DataTable rows={rows4} columns={columns} />
1264
+ <DataTable rows={rows} columns={columns} pinnedColumns={{ left: ['id', 'name'], right: ['actions'] }} />
385
1265
  ```
386
1266
 
387
- 헤더를 클릭하여 데이터를 정렬할 수 있습니다. 오름차순 → 내림차순 → 정렬 해제 순서로 동작합니다.
1267
+ ## API Usage
388
1268
 
389
- ```tsx
390
- const columns = [
391
- { field: 'id', headerName: 'ID' },
392
- { field: 'number', headerName: 'Number Sort', type: 'number' },
393
- { field: 'string', headerName: 'String Sort' },
394
- { field: 'date', headerName: 'Date Sort', renderCell: ({ value }) => value.toLocaleDateString() },
395
- {
396
- field: 'object',
397
- headerName: 'Object Sort',
398
- renderCell: ({ value }) => value.age,
399
- type: 'number',
400
- sortComparator: ({ rowA, rowB }) => rowA.object.age - rowB.object.age,
401
- },
402
- ];
403
- ```
1269
+ ### DataTableApi Interface
404
1270
 
405
- ### 커스텀 정렬 순서
1271
+ DataTable supports programmatic control through `apiRef`.
406
1272
 
407
1273
  ```tsx
408
- <DataTable rows={rows4} columns={columns} sortOrder={['desc', 'asc']} />
1274
+ interface DataTableApi {
1275
+ getRowIndexRelativeToVisibleRows(rowId: string): number;
1276
+ setCellFocus(rowId: string): void;
1277
+ }
409
1278
  ```
410
1279
 
411
- 기본 정렬 순서를 변경할 있습니다:
1280
+ ### Scrolling/Focusing to a Specific Row
412
1281
 
413
1282
  ```tsx
414
- <DataTable
415
- rows={rows}
416
- columns={columns}
417
- sortOrder={['desc', 'asc']} // 내림차순 먼저
418
- />
1283
+ <Box sx={{
1284
+ height: '400px'
1285
+ }}>
1286
+ <DataTable ref={ref} rows={args.rows} columns={args.columns} checkboxSelection={args.checkboxSelection} disableSelectionOnClick={args.disableSelectionOnClick} selectionModel={args.selectionModel} isTotalSelected={args.isTotalSelected} pagination={args.pagination} paginationMode={args.paginationMode} paginationModel={args.paginationModel} noWrap={args.noWrap} hoverRow={args.hoverRow} stripe={args.stripe} stickyHeader={args.stickyHeader} initialState={args.initialState} />
1287
+ </Box>
419
1288
  ```
420
1289
 
421
- ### 정렬 비활성화
1290
+ ### Inspector Pattern
422
1291
 
423
1292
  ```tsx
424
- <DataTable rows={rows4} columns={columns} />
1293
+ <Stack sx={{
1294
+ height: '100vh'
1295
+ }}>
1296
+ <Typography level="title-sm" textColor="text.secondary">
1297
+ Virtualized & Inspector DataTable
1298
+ </Typography>
1299
+ <Button onClick={() => apiRef.current?.setCellFocus('0')}>Row ID 0 Focus</Button>
1300
+ <Button onClick={() => apiRef.current?.setCellFocus('100')}>Row ID 100 Focus</Button>
1301
+ <div style={{
1302
+ flex: 1,
1303
+ maxHeight: '100%',
1304
+ overflow: 'auto'
1305
+ }}>
1306
+ <DataTable ref={apiRef} rows={args.rows} columns={args.columns} stripe={args.stripe} hoverRow={args.hoverRow} stickyHeader={args.stickyHeader} disableSelectionOnClick={args.disableSelectionOnClick} checkboxSelection={args.checkboxSelection} noWrap={args.noWrap} selectionModel={selectedRowId === -1 ? [] : [selectedRowId]} onRowClick={({
1307
+ rowId
1308
+ }) => {
1309
+ if (selectedRowId === rowId) {
1310
+ setSelectedRowId(-1);
1311
+ } else {
1312
+ console.log(rowId);
1313
+ setSelectedRowId(rowId);
1314
+ setTimeout(() => {
1315
+ apiRef.current?.setCellFocus(String(rowId));
1316
+ }, 0);
1317
+ }
1318
+ }} />
1319
+ </div>
1320
+ {selectedRowId !== -1 && <Box sx={{
1321
+ backgroundColor: 'aqua',
1322
+ border: 'black 1px solid',
1323
+ height: '300px'
1324
+ }}>
1325
+ This is inspector: {selectedRowId}
1326
+ </Box>}
1327
+ </Stack>
425
1328
  ```
426
1329
 
427
- 특정 열의 정렬을 비활성화할 수 있습니다:
1330
+ ## Constraints and Considerations
428
1331
 
429
- ```tsx
430
- const columns = [
431
- { field: 'id', headerName: 'ID' },
432
- { field: 'number', headerName: 'Number Sort', type: 'number', sortable: false },
433
- ];
434
- ```
1332
+ ### Required Conditions
435
1333
 
436
- ### 열별 정렬 순서
1334
+ - Using `useMemo` to maintain references when `rows` changes can reduce unnecessary re-renders.
1335
+ - When using custom `getId`, values must be unique; duplicates can cause selection/sorting/editing to malfunction.
1336
+ - Without height on `slotProps.background`, `stickyHeader` may not display properly.
437
1337
 
438
- ```tsx
439
- <DataTable rows={rows4} columns={columns} />
440
- ```
1338
+ ### Feature Combination Constraints
441
1339
 
442
- 열마다 다른 정렬 순서를 지정할 있습니다.
1340
+ - `pinnedColumns` and `columnGroupingModel` cannot be used simultaneously.
1341
+ - `editMode` must be `true` for editing-related UI to be active.
1342
+ - When `paginationMode="server"`, `rowCount` must be specified.
443
1343
 
444
- ## 실시간 데이터 업데이트
1344
+ ### Performance Optimization
445
1345
 
446
- ```tsx
447
- <DataTable rows={editRows} columns={columns} checkboxSelection={false} noWrap />
448
- ```
1346
+ - Use virtual scrolling for large data (1000+ rows).
1347
+ - Memoize the `columns` array with `useMemo`.
1348
+ - Be careful with `renderCell` and `componentProps` functions as they can cause unnecessary re-renders.
449
1349
 
450
- 테이블 데이터를 실시간으로 업데이트하고 변경 사항을 즉시 반영할 수 있습니다.
1350
+ ```tsx
1351
+ // Good
1352
+ const columns = useMemo(() => [...], [dependencies]);
451
1353
 
452
- DataTable은 현대적인 데이터 관리 애플리케이션에서 요구되는 모든 기능을 제공하는 강력한 컴포넌트입니다. 간단한 데이터 표시부터 복잡한 편집 가능한 테이블까지 다양한 용도로 활용할 수 있습니다.
1354
+ // Bad - creates a new array on every render
1355
+ const columns = [...];
1356
+ ```