11import {
2+ Box ,
23 Checkbox ,
34 Collapse ,
45 IconButton ,
@@ -10,44 +11,100 @@ import {
1011} from '@mui/material' ;
1112import * as React from 'react' ;
1213import { ExpandLess , ExpandMore } from '@mui/icons-material' ;
14+ import { useCallback , useRef } from 'react' ;
1315
1416interface NestedCheckboxListProps {
1517 checkboxData : CheckboxStructure [ ] ;
1618 onCheckboxChange : ( checkboxData : CheckboxStructure [ ] ) => void ;
1719 onExpandGroupChange ?: ( checkboxData : CheckboxStructure [ ] ) => void ;
20+ disableAll ?: boolean ;
21+ debounceTime ?: number ;
1822}
1923
20- // NOTE: Although the data structure allows for multiple levels of nesting, the current implementation only supports two levels.
21- // TODO: Implement support for multiple levels of nesting
2224export interface CheckboxStructure {
2325 title : string ;
2426 type : 'label' | 'checkbox' ;
2527 checked : boolean ;
2628 seeChildren ?: boolean ;
2729 children ?: CheckboxStructure [ ] ;
30+ disabled ?: boolean ;
2831}
2932
33+ function useDebouncedCallback ( callback : ( ) => void , delay : number ) : ( ) => void {
34+ const timeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
35+
36+ const debouncedFunction = useCallback ( ( ) => {
37+ if ( timeoutRef . current != null ) {
38+ clearTimeout ( timeoutRef . current ) ;
39+ }
40+
41+ timeoutRef . current = setTimeout ( ( ) => {
42+ callback ( ) ;
43+ } , delay ) ;
44+ } , [ callback , delay ] ) ;
45+
46+ return debouncedFunction ;
47+ }
48+
49+ export const checkboxCheckValue = (
50+ checkboxData : CheckboxStructure ,
51+ disableAll : boolean ,
52+ ) : boolean => {
53+ if ( ( checkboxData . disabled != null && checkboxData . disabled ) || disableAll ) {
54+ return false ;
55+ }
56+ return (
57+ checkboxData . checked ||
58+ ( checkboxData . children !== undefined &&
59+ checkboxData . children . length > 0 &&
60+ checkboxData . children . every ( ( child ) => child . checked ) )
61+ ) ;
62+ } ;
63+
64+ export const checkboxIndeterminateValue = (
65+ checkboxData : CheckboxStructure ,
66+ disableAll : boolean ,
67+ ) : boolean => {
68+ if ( disableAll || checkboxData . children == undefined ) {
69+ return false ;
70+ }
71+ const checkboxCheck =
72+ checkboxData . children . some ( ( child ) => child . checked ) &&
73+ ! checkboxData . children . every ( ( child ) => child . checked ) ;
74+ const areAllChildrenDisabled = checkboxData . children . every (
75+ ( child ) => child . disabled ,
76+ ) ;
77+ return ! areAllChildrenDisabled && checkboxCheck ;
78+ } ;
79+
3080export default function NestedCheckboxList ( {
3181 checkboxData,
3282 onCheckboxChange,
3383 onExpandGroupChange,
84+ disableAll = false ,
85+ debounceTime = 0 ,
3486} : NestedCheckboxListProps ) : JSX . Element {
35- const [ checkboxStructure , setCheckboxStructure ] =
36- React . useState < CheckboxStructure [ ] > ( checkboxData ) ;
87+ const [ checkboxStructure , setCheckboxStructure ] = React . useState <
88+ CheckboxStructure [ ]
89+ > ( [ ...checkboxData ] ) ;
3790 const [ hasChange , setHasChange ] = React . useState < boolean > ( false ) ;
3891 const theme = useTheme ( ) ;
3992
4093 React . useEffect ( ( ) => {
4194 if ( hasChange ) {
4295 setHasChange ( false ) ;
43- onCheckboxChange ( checkboxStructure ) ;
96+ debouncedSubmit ( ) ;
4497 }
4598 } , [ checkboxStructure ] ) ;
4699
47100 React . useEffect ( ( ) => {
48101 setCheckboxStructure ( checkboxData ) ;
49102 } , [ checkboxData ] ) ;
50103
104+ const debouncedSubmit = useDebouncedCallback ( ( ) => {
105+ onCheckboxChange ( checkboxStructure ) ;
106+ } , debounceTime ) ;
107+
51108 return (
52109 < List sx = { { width : '100%' } } dense >
53110 { checkboxStructure . map ( ( checkboxData , index ) => {
@@ -71,6 +128,7 @@ export default function NestedCheckboxList({
71128 { checkboxData . children !== undefined &&
72129 checkboxData . children ?. length > 0 && (
73130 < IconButton
131+ disabled = { disableAll || checkboxData . disabled }
74132 edge = { 'end' }
75133 aria-label = 'expand'
76134 onClick = { ( ) => {
@@ -85,7 +143,8 @@ export default function NestedCheckboxList({
85143 }
86144 } }
87145 >
88- { checkboxData . seeChildren !== undefined &&
146+ { ! disableAll &&
147+ checkboxData . seeChildren != undefined &&
89148 checkboxData . seeChildren ? (
90149 < ExpandLess />
91150 ) : (
@@ -99,6 +158,7 @@ export default function NestedCheckboxList({
99158 { checkboxData . type === 'checkbox' && (
100159 < ListItemButton
101160 role = { undefined }
161+ disabled = { disableAll || checkboxData . disabled }
102162 dense = { true }
103163 sx = { { p : 0 } }
104164 onClick = { ( ) => {
@@ -118,18 +178,11 @@ export default function NestedCheckboxList({
118178 tabIndex = { - 1 }
119179 disableRipple
120180 inputProps = { { 'aria-labelledby' : labelId } }
121- checked = {
122- checkboxData . checked ||
123- ( checkboxData . children !== undefined &&
124- checkboxData . children . length > 0 &&
125- checkboxData . children . every ( ( child ) => child . checked ) )
126- }
127- indeterminate = {
128- checkboxData . children !== undefined
129- ? checkboxData . children . some ( ( child ) => child . checked ) &&
130- ! checkboxData . children . every ( ( child ) => child . checked )
131- : false
132- }
181+ checked = { checkboxCheckValue ( checkboxData , disableAll ) }
182+ indeterminate = { checkboxIndeterminateValue (
183+ checkboxData ,
184+ disableAll ,
185+ ) }
133186 />
134187 < ListItemText
135188 id = { labelId }
@@ -151,62 +204,30 @@ export default function NestedCheckboxList({
151204 ) }
152205 { checkboxData . children !== undefined && (
153206 < Collapse
154- in = { checkboxData . seeChildren }
207+ in = {
208+ checkboxData . seeChildren != null &&
209+ checkboxData . seeChildren &&
210+ ! disableAll
211+ }
155212 timeout = 'auto'
156213 unmountOnExit
157214 >
158- < List
159- sx = { {
160- ml : 1 ,
161- display : { xs : 'flex' , md : 'block' } ,
162- flexWrap : 'wrap' ,
163- } }
164- dense
165- >
166- { checkboxData . children . map ( ( value ) => {
167- const labelId = `checkbox-list-label-${ value . title } ` ;
168-
169- return (
170- < ListItem
171- key = { value . title }
172- disablePadding
173- sx = { { width : { xs : '50%' , sm : '33%' , md : '100%' } } }
174- onClick = { ( ) => {
175- setCheckboxStructure ( ( prev ) => {
176- value . checked = ! value . checked ;
177- if ( ! value . checked ) {
178- checkboxData . checked = false ;
179- }
180- return [ ...prev ] ;
181- } ) ;
182- setHasChange ( true ) ;
183- } }
184- >
185- < ListItemButton
186- role = { undefined }
187- dense = { true }
188- sx = { { p : 0 , pl : 1 } }
189- >
190- < Checkbox
191- edge = 'start'
192- tabIndex = { - 1 }
193- disableRipple
194- checked = { value . checked || checkboxData . checked }
195- inputProps = { { 'aria-labelledby' : labelId } }
196- />
197-
198- < ListItemText
199- id = { labelId }
200- primary = { `${ value . title } ` }
201- primaryTypographyProps = { {
202- variant : 'body1' ,
203- } }
204- />
205- </ ListItemButton >
206- </ ListItem >
207- ) ;
208- } ) }
209- </ List >
215+ < Box sx = { { pl : 1 , width : '100%' } } >
216+ < NestedCheckboxList
217+ checkboxData = { checkboxData . children }
218+ onCheckboxChange = { ( children ) => {
219+ setCheckboxStructure ( ( prev ) => {
220+ checkboxData . children = children ;
221+ prev [ index ] = checkboxData ;
222+ return [ ...prev ] ;
223+ } ) ;
224+ setHasChange ( true ) ;
225+ } }
226+ onExpandGroupChange = { onExpandGroupChange }
227+ disableAll = { disableAll || checkboxData . disabled }
228+ debounceTime = { 0 }
229+ > </ NestedCheckboxList >
230+ </ Box >
210231 </ Collapse >
211232 ) }
212233 </ ListItem >
0 commit comments