diff --git a/apps/react/src/challenges/nested-checkbox/App.jsx b/apps/react/src/challenges/nested-checkbox/App.jsx new file mode 100644 index 000000000..52d5c0062 --- /dev/null +++ b/apps/react/src/challenges/nested-checkbox/App.jsx @@ -0,0 +1,110 @@ +import { useState } from 'react'; +import CheckBoxList from './CheckBoxList'; +import { checkboxList } from './constants'; +import styles from './styles.module.css'; + +const NestedCheckbox = () => { + const [checkList, setCheckList] = useState(checkboxList); + + const toggleAllChildNodes = (list, value) => { + for (let i = 0; i < list.length; i++) { + list[i].checked = value; + toggleAllChildNodes(list[i].children, value); + } + }; + + const dfs = (list, id, value, isFound) => { + if (list.length === 0) return isFound; + for (let i = 0; i < list.length; i++) { + if (list[i].id === id) { + list[i].checked = value; + isFound = true; + toggleAllChildNodes(list[i].children, value); + break; + } + isFound = dfs(list[i].children, id, value, isFound); + if (isFound) break; + } + return isFound; + }; + + const getActiveChildCount = (list) => { + if (list.length === 0) return 0; + let count = 0; + for (let i = 0; i < list.length; i++) { + // If it's checked then increase the count. + if (list[i].checked) { + count += 1; + } + + // If there is no children for the node then we skip the rest of the code. + if (list[i].children.length === 0) continue; + + // we need to get the active child count. + let childCount = getActiveChildCount(list[i].children); + + // If count is not equal then we need to un-check the parent node else we need to check the parent node. + if (childCount !== list[i].children.length) { + count = list[i].checked ? count - 1 : count; + list[i].checked = false; + } else { + count = list[i].checked ? count : count + 1; + list[i].checked = true; + } + } + return count; + }; + + const handleChange = (id, value) => { + let clone = JSON.parse(JSON.stringify(checkList)); + let isFound = false; + let parentIndex = 0; + /** + * Iterate the root node and the child node until we find the target node. + * If we found the target node we need to make all the children of the target flag as the selected value ( true | false ). + */ + for (let i = 0; i < clone.length; i++) { + if (clone[i].id === id) { + clone[i].checked = value; + isFound = true; + parentIndex = i; + toggleAllChildNodes(clone[i].children, value); + break; + } + + // If the target node is not a root. We need to check all the child nodes. + isFound = dfs(clone[i].children, id, value, false); + + // If we found the targetNode we no need to check the rest of the node. + if (isFound) { + parentIndex = i; + break; + } + } + + /** + * Now we want to check/unCheck the parent node based on the child node values. + * If all the child nodes are check we need to make the parent node check. + * if any one of the child nodes are unChecked we need to make the parent node un-check. + */ + const childCount = getActiveChildCount(clone[parentIndex].children); + if (clone[parentIndex].children.length > 0) { + if (childCount !== clone[parentIndex].children.length) { + clone[parentIndex].checked = false; + } else { + clone[parentIndex].checked = true; + } + } + + // Finally set the updated list. + setCheckList(clone); + }; + + return ( +
+ {} +
+ ); +}; + +export default NestedCheckbox; diff --git a/apps/react/src/challenges/nested-checkbox/CheckBox.jsx b/apps/react/src/challenges/nested-checkbox/CheckBox.jsx new file mode 100644 index 000000000..81b349f44 --- /dev/null +++ b/apps/react/src/challenges/nested-checkbox/CheckBox.jsx @@ -0,0 +1,19 @@ +/* eslint-disable react/prop-types */ +import styles from './styles.module.css'; + +const Checkbox = (props) => { + const { id, isChecked, label, onCheckBoxChange } = props; + + return ( + + ); +}; + +export default Checkbox; diff --git a/apps/react/src/challenges/nested-checkbox/CheckBoxList.jsx b/apps/react/src/challenges/nested-checkbox/CheckBoxList.jsx new file mode 100644 index 000000000..c854e306d --- /dev/null +++ b/apps/react/src/challenges/nested-checkbox/CheckBoxList.jsx @@ -0,0 +1,31 @@ +/* eslint-disable react/prop-types */ +import { Fragment } from 'react'; +import Checkbox from './CheckBox'; +import styles from './styles.module.css'; + +const CheckBoxList = (props) => { + const { childCheckBoxList, onCheckBoxChange } = props; + + return ( + + {childCheckBoxList.map((childBox) => ( +
+ + {childBox.children.length > 0 && ( + + )} +
+ ))} +
+ ); +}; + +export default CheckBoxList; diff --git a/apps/react/src/challenges/nested-checkbox/constants.js b/apps/react/src/challenges/nested-checkbox/constants.js new file mode 100644 index 000000000..02dc9b5e4 --- /dev/null +++ b/apps/react/src/challenges/nested-checkbox/constants.js @@ -0,0 +1,110 @@ +export const checkboxList = [ + { + label: 'p1', + id: 1, + checked: false, + children: [ + { + label: 'p1-c1', + id: 2, + checked: false, + children: [ + { + label: 'p1-c1-c1', + id: 3, + checked: false, + children: [], + }, + { + label: 'p1-c1-c2', + id: 4, + checked: false, + children: [ + { + label: 'p1-c1-c2-c1', + id: 5, + checked: false, + children: [], + }, + { + label: 'p1-c1-c2-c2', + id: 6, + checked: false, + children: [ + { + label: 'p1-c1-c2-c2-c1', + id: 7, + checked: false, + children: [], + }, + { + label: 'p1-c1-c2-c2-c2', + id: 8, + checked: false, + children: [], + }, + ], + }, + { + label: 'p1-c1-c2-c3', + id: 9, + checked: false, + children: [], + }, + ], + }, + ], + }, + { + label: 'p1-c2', + id: 10, + checked: false, + children: [], + }, + { + label: 'p1-c3', + id: 11, + checked: false, + children: [], + }, + ], + }, + { + label: 'p2', + id: 12, + checked: false, + children: [ + { + label: 'p2-c1', + id: 13, + checked: false, + children: [], + }, + { + label: 'p2-c2', + id: 14, + checked: false, + children: [], + }, + ], + }, + { + label: 'p3', + id: 15, + checked: false, + children: [ + { + label: 'p3-c1', + id: 16, + checked: false, + children: [], + }, + ], + }, + { + label: 'p4', + id: 17, + checked: false, + children: [], + }, +]; diff --git a/apps/react/src/challenges/nested-checkbox/styles.module.css b/apps/react/src/challenges/nested-checkbox/styles.module.css new file mode 100644 index 000000000..203f14c7f --- /dev/null +++ b/apps/react/src/challenges/nested-checkbox/styles.module.css @@ -0,0 +1,15 @@ +.nested_checkbox_wrapper { + margin: 20px 0px; + width: 300px; +} + +.nested_checkbox_label { + display: flex; + align-items: center; + gap: 4px; +} + +.nested_checkbox_child { + margin: 10px 0px; + padding: 0px 0px 0px 25px; +} diff --git a/apps/react/src/pages/Challenge.tsx b/apps/react/src/pages/Challenge.tsx index de780d6a1..203e6abed 100644 --- a/apps/react/src/pages/Challenge.tsx +++ b/apps/react/src/pages/Challenge.tsx @@ -64,6 +64,7 @@ import DraggableList from '@/challenges/drag-drop/DraggableList'; import Circles from '@/challenges/circles/circles'; import AnalogClock from '@/challenges/analog-clock/analog-clock'; import AdvancedCounter from '@/challenges/advanced-counter/advanced-counter'; +import NestedCheckbox from '@/challenges/nested-checkbox/App'; const reactChallengesMap = { 'transfer-list': , @@ -129,6 +130,7 @@ const reactChallengesMap = { 'drag-drop': , circles: , 'analog-clock': , + 'nested-checkbox': , }; function Challenge() { diff --git a/shared/data/content/contributors.ts b/shared/data/content/contributors.ts index 935ea6609..2a804d5c8 100644 --- a/shared/data/content/contributors.ts +++ b/shared/data/content/contributors.ts @@ -217,4 +217,11 @@ export const contributors = new Map([ 'officialbidisha', { name: 'Bidisha Das', pic: 'https://avatars.githubusercontent.com/u/49115207' }, ], + [ + 'SujithGunasekaran', + { + name: 'Sujith Gunasekaran', + pic: 'https://avatars.githubusercontent.com/u/68234378?s=96&v=4', + }, + ], ]); diff --git a/shared/data/content/react-challenges.ts b/shared/data/content/react-challenges.ts index 8574a6e4c..941ac2316 100644 --- a/shared/data/content/react-challenges.ts +++ b/shared/data/content/react-challenges.ts @@ -646,6 +646,16 @@ const challenges = new Map([ tags: [ETag.interview], }, ], + [ + 'nested-checkbox', + { + title: 'Nested Checkbox', + link: 'nested-checkbox', + difficulty: EDifficulty.Hard, + developer: 'SujithGunasekaran', + tags: [ETag.interview], + }, + ], ]); export const reactChallenges = sortChallengesByDifficulty(challenges);