1
+ import { FC , HTMLAttributes , MouseEvent , ReactNode , forwardRef , useMemo } from "react" ;
2
+ import { styled } from "styled-components" ;
3
+
4
+ import { CheckedState } from "@radix-ui/react-checkbox" ;
5
+
1
6
import {
2
7
Checkbox ,
8
+ CheckboxProps ,
3
9
EllipsisContent ,
4
10
HorizontalDirection ,
5
11
Icon ,
6
12
IconButton ,
7
13
Text ,
8
14
} from "@/components" ;
9
- import { HTMLAttributes , MouseEvent , ReactNode , forwardRef } from "react" ;
10
- import { styled } from "styled-components" ;
15
+
11
16
type SortDir = "asc" | "desc" ;
12
17
type SortFn = ( sortDir : SortDir , header : TableHeaderType , index : number ) => void ;
13
18
type TableSize = "sm" | "md" ;
19
+
14
20
export interface TableHeaderType extends HTMLAttributes < HTMLTableCellElement > {
15
21
label : ReactNode ;
16
22
isSortable ?: boolean ;
@@ -87,11 +93,12 @@ const TableHeader = ({
87
93
interface TheadProps {
88
94
headers : Array < TableHeaderType > ;
89
95
isSelectable ?: boolean ;
90
- onSelectAll : ( checked : boolean ) => void ;
96
+ onSelectAll ? : ( selectedValues : SelectReturnValue [ ] ) => void ;
91
97
actionsList : Array < string > ;
92
98
onSort ?: SortFn ;
93
- hasRows : boolean ;
94
99
size : TableSize ;
100
+ rows : TableRowType [ ] ;
101
+ selectedIds : ( number | string ) [ ] ;
95
102
}
96
103
97
104
const Thead = ( {
@@ -100,8 +107,9 @@ const Thead = ({
100
107
onSelectAll,
101
108
actionsList,
102
109
onSort : onSortProp ,
103
- hasRows,
104
110
size,
111
+ rows,
112
+ selectedIds,
105
113
} : TheadProps ) => {
106
114
const onSort = ( header : TableHeaderType , headerIndex : number ) => ( ) => {
107
115
if ( typeof onSortProp === "function" && header . isSortable ) {
@@ -127,9 +135,10 @@ const Thead = ({
127
135
$size = { size }
128
136
aria-label = "Select column"
129
137
>
130
- < Checkbox
138
+ < SelectAllCheckbox
131
139
onCheckedChange = { onSelectAll }
132
- disabled = { ! hasRows }
140
+ rows = { rows }
141
+ selectedIds = { selectedIds }
133
142
/>
134
143
</ StyledHeader >
135
144
) }
@@ -451,6 +460,7 @@ const TableBodyRow = ({
451
460
< Checkbox
452
461
checked = { isSelected }
453
462
onCheckedChange = { onSelect }
463
+ disabled = { isDisabled || isDeleted }
454
464
/>
455
465
</ SelectData >
456
466
) }
@@ -561,17 +571,6 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
561
571
) => {
562
572
const isDeletable = typeof onDelete === "function" ;
563
573
const isEditable = typeof onEdit === "function" ;
564
- const onSelectAll = ( checked : boolean ) : void => {
565
- if ( typeof onSelect === "function" ) {
566
- const ids = checked
567
- ? rows . map ( ( row , index ) => ( {
568
- item : row ,
569
- index,
570
- } ) )
571
- : [ ] ;
572
- onSelect ( ids ) ;
573
- }
574
- } ;
575
574
576
575
const onRowSelect =
577
576
( id : number | string ) =>
@@ -580,7 +579,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
580
579
const selectedItems = rows . flatMap ( ( row , index ) => {
581
580
if (
582
581
( id === row . id && checked ) ||
583
- ( selectedIds . includes ( id ) && id !== row . id )
582
+ ( selectedIds . includes ( row . id ) && id !== row . id )
584
583
) {
585
584
return {
586
585
item : row ,
@@ -607,10 +606,11 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
607
606
{ hasRows && showHeader && (
608
607
< MobileActions >
609
608
{ isSelectable && (
610
- < Checkbox
609
+ < SelectAllCheckbox
611
610
label = "Select All"
612
- checked = { selectedIds . length === rows . length }
613
- onCheckedChange = { onSelectAll }
611
+ onCheckedChange = { onSelect }
612
+ rows = { rows }
613
+ selectedIds = { selectedIds }
614
614
/>
615
615
) }
616
616
</ MobileActions >
@@ -624,11 +624,12 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
624
624
< Thead
625
625
headers = { headers }
626
626
isSelectable = { isSelectable }
627
- onSelectAll = { onSelectAll }
627
+ onSelectAll = { onSelect }
628
628
actionsList = { actionsList }
629
629
onSort = { onSort }
630
- hasRows = { hasRows }
631
630
size = { size }
631
+ rows = { rows }
632
+ selectedIds = { selectedIds }
632
633
/>
633
634
) }
634
635
< Tbody >
@@ -677,6 +678,82 @@ const Table = forwardRef<HTMLTableElement, TableProps>(
677
678
}
678
679
) ;
679
680
681
+ interface SelectAllCheckboxProps extends Omit < CheckboxProps , "onCheckedChange" > {
682
+ onCheckedChange ?: ( selectedValues : Array < SelectReturnValue > ) => void ;
683
+ selectedIds : ( number | string ) [ ] ;
684
+ rows : TableRowType [ ] ;
685
+ }
686
+
687
+ const SelectAllCheckbox : FC < SelectAllCheckboxProps > = ( {
688
+ rows,
689
+ selectedIds,
690
+ onCheckedChange,
691
+ ...checkboxProps
692
+ } ) => {
693
+ const selectedIdSet = useMemo ( ( ) => new Set ( selectedIds ) , [ selectedIds ] ) ;
694
+
695
+ const { checked, disabled } = useMemo ( ( ) => {
696
+ let areAllChecked = true ;
697
+ let maybeIndeterminate : CheckedState = false ;
698
+ let disabled = true ;
699
+
700
+ for ( const row of rows ) {
701
+ if ( row . isDisabled || row . isDeleted ) {
702
+ continue ;
703
+ } else {
704
+ disabled = false ;
705
+ }
706
+
707
+ if ( selectedIdSet . has ( row . id ) ) {
708
+ maybeIndeterminate = "indeterminate" ;
709
+ } else {
710
+ areAllChecked = false ;
711
+ }
712
+ }
713
+
714
+ return {
715
+ checked : disabled ? false : areAllChecked || maybeIndeterminate ,
716
+ disabled,
717
+ } ;
718
+ } , [ rows , selectedIdSet ] ) ;
719
+
720
+ const handleCheckedChange = ( checked : boolean ) => {
721
+ if ( typeof onCheckedChange !== "function" ) {
722
+ return ;
723
+ }
724
+
725
+ // disabled items should not change their selected state because of user interaction
726
+
727
+ const newSelectedRows = rows . reduce ( ( acc : SelectReturnValue [ ] , row , index ) => {
728
+ const isDisabled = row . isDisabled || row . isDeleted ;
729
+
730
+ const shouldBeSelected = checked
731
+ ? ! isDisabled || selectedIdSet . has ( row . id )
732
+ : isDisabled && selectedIdSet . has ( row . id ) ;
733
+
734
+ if ( shouldBeSelected ) {
735
+ acc . push ( {
736
+ item : row ,
737
+ index,
738
+ } ) ;
739
+ }
740
+
741
+ return acc ;
742
+ } , [ ] ) ;
743
+
744
+ onCheckedChange ( newSelectedRows ) ;
745
+ } ;
746
+
747
+ return (
748
+ < Checkbox
749
+ checked = { checked }
750
+ disabled = { disabled }
751
+ onCheckedChange = { handleCheckedChange }
752
+ { ...checkboxProps }
753
+ />
754
+ ) ;
755
+ } ;
756
+
680
757
const StyledTable = styled . table `
681
758
width: 100%;
682
759
border-collapse: collapse;
0 commit comments