JSFiddle - React, Tailwind, and code Playground
HTML
<div id="cb-root">
<div class="checkbox-container">
<label for="canada">Canada</label>
<input id="canada" type="checkbox">
<div class="checkbox-list">
<div class="checkbox-container">
<label for="ontario">Ontario</label>
<input id="ontario" type="checkbox">
<div class="checkbox-list">
<div class="checkbox-container">
<label for="ott">Ottawa</label>
<input id="ott" type="checkbox">
<div class="checkbox-list">
</div>
</div>
<div class="checkbox-container">
<label for="tor">Toronto</label>
<input id="tor" type="checkbox">
<div class="checkbox-list">
</div>
</div>
</div>
</div>
<div class="checkbox-container">
<label for="quebec">Quebec</label>
<input id="quebec" type="checkbox">
<div class="checkbox-list">
<div class="checkbox-container">
<label for="mor">Montreal</label>
<input id="mor" type="checkbox">
<div class="checkbox-list">
</div>
</div>
<div class="checkbox-container">
<label for="qc">Quebec City</label>
<input id="qc" type="checkbox">
<div class="checkbox-list">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
CSS
.checkbox-list {
margin: 5px 0 0 20px;
}
.checkbox-container {
margin-top: 5px;
}
JavaScript
const UNCHECKED = 0;
const CHECKED = 1;
const IND = 2;
function Checkbox(root, onChange) {
this.children = [];
this.childrenChecked = 0;
this.childrenInd = 0;
this.currState = UNCHECKED;
this.onChange = onChange;
this.checkbox = root.querySelector("input");
const list = root.querySelector(".checkbox-list");
const onChildChange = (prevState, currState) => {
if (prevState == CHECKED) {
this.childrenChecked--;
} else if (prevState == IND) {
this.childrenInd--;
}
if (currState == CHECKED) {
this.childrenChecked++;
} else if (currState == IND) {
this.childrenInd++;
}
if (this.childrenChecked == this.children.length) {
this.checkbox.checked = true;
this.checkbox.indeterminate = false;
this.onChange(this.currState, CHECKED);
this.currState = CHECKED;
} else if (this.childrenInd != 0 || this.childrenChecked != 0) {
this.checkbox.checked = false;
this.checkbox.indeterminate = true;
this.onChange(this.currState, IND);
this.currState = IND;
} else {
this.checkbox.checked = false;
this.checkbox.indeterminate = false;
this.onChange(this.currState, UNCHECKED);
this.currState = UNCHECKED;
}
}
list.childNodes.forEach(node => {
if(node.className == 'checkbox-container') {
this.children.push(new Checkbox(node, onChildChange));
}
});
this.checkbox.addEventListener("click", (e) => {
let newState;
if(this.checkbox.checked) {
newState = CHECKED;
this.childrenInd = 0;
this.childrenChecked = this.children.length;
} else {
newState = UNCHECKED;
this.childrenInd = 0;
this.childrenChecked = 0;
}
this.onChange(this.currState, newState);
this.currState = newState;
this.children.forEach(child => child.onParentUpdate(newState));
});
}
Checkbox.prototype.onParentUpdate = function(parentState) {
if(parentState == CHECKED) {
this.checkbox.checked =...