ToolJet/frontend/src/AppBuilder/Widgets/TreeSelect.jsx
KJyang-0114 17a6901ca4 fix: improve dark mode visibility for TreeSelect widget
- Add theme-dark class to TreeSelect when darkMode is enabled
- Add CSS rules for dark mode text and checkbox visibility
- Fixes #11798: Treeview items barely visible in dark mode
2026-03-07 13:00:22 +08:00

139 lines
4.2 KiB
JavaScript

import React, { useState, useEffect, useMemo } from 'react';
// eslint-disable-next-line import/no-unresolved
import CheckboxTree from 'react-checkbox-tree';
// eslint-disable-next-line import/no-unresolved
import 'react-checkbox-tree/lib/react-checkbox-tree.css';
import { isExpectedDataType } from '@/_helpers/utils.js';
export const TreeSelect = ({
height,
properties,
styles,
setExposedVariable,
setExposedVariables,
fireEvent,
darkMode,
dataCy,
id,
}) => {
const { label } = properties;
const { visibility, disabledState, checkboxColor, boxShadow } = styles;
const textColor = darkMode && styles.textColor === '#000' ? '#fff' : styles.textColor;
const darkModeClass = darkMode ? 'theme-dark' : '';
const [checked, setChecked] = useState(checkedData);
const [expanded, setExpanded] = useState(expandedData);
const data = isExpectedDataType(properties.data, 'array');
const checkedData = isExpectedDataType(properties.checkedData, 'array');
const expandedData = isExpectedDataType(properties.expandedData, 'array');
let pathObj = {};
useEffect(() => {
const checkedArr = [],
checkedPathArray = [],
checkedPathString = [];
const updateCheckedArr = (array = [], selected, isSelected = false) => {
array.forEach((node) => {
if (isSelected || selected.includes(node.value)) {
checkedArr.push(node.value);
updateCheckedArr(node.children, selected, true);
} else {
updateCheckedArr(node.children, selected);
}
});
};
updateCheckedArr(data, checkedData);
setChecked(checkedArr);
checkedArr.forEach((item) => {
checkedPathArray.push(pathObj[item]);
checkedPathString.push(pathObj[item].join('-'));
});
const exposedVariables = {
checkedPathArray: checkedPathArray,
checkedPathStrings: checkedPathString,
checked: checkedArr,
};
setExposedVariables(exposedVariables);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(checkedData), JSON.stringify(data)]);
useEffect(() => {
setExposedVariable('expanded', expandedData);
setExpanded(expandedData);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(expandedData)]);
pathObj = useMemo(() => {
let nodePath = {};
function checkedPath(nodes, arr = []) {
for (const node of nodes) {
nodePath[node.value] = [...arr, node.value];
if (node?.children?.length > 0) {
checkedPath(node.children, [...arr, node.value]);
}
}
}
checkedPath(data, []);
return nodePath;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(data)]);
const onCheck = (checked, updatedNode) => {
const checkedPathArray = [],
checkedPathString = [];
checked.forEach((item) => {
checkedPathArray.push(pathObj[item]);
checkedPathString.push(pathObj[item].join('-'));
});
const exposedVariables = {
checkedPathArray: checkedPathArray,
checkedPathStrings: checkedPathString,
checked: checked,
};
setExposedVariables(exposedVariables);
updatedNode.checked ? fireEvent('onCheck') : fireEvent('onUnCheck');
fireEvent('onChange');
setChecked(checked);
};
const onExpand = (expanded) => {
setExposedVariable('expanded', expanded);
setExpanded(expanded);
};
return (
<div
className={`custom-checkbox-tree ${darkModeClass}`}
data-disabled={disabledState}
style={{
maxHeight: height,
display: visibility ? '' : 'none',
color: textColor,
accentColor: checkboxColor,
boxShadow,
}}
data-cy={dataCy}
aria-hidden={!visibility}
aria-disabled={disabledState}
>
<div className="card-title" style={{ marginBottom: '0.5rem' }}>
<label htmlFor={`component-${id}`}>{label}</label>
</div>
<CheckboxTree
nodes={data}
checked={checked}
expanded={expanded}
showNodeIcon={false}
onCheck={onCheck}
onExpand={onExpand}
nativeCheckboxes
checkModel="all"
disabled={disabledState}
id={`component-${id}`}
/>
</div>
);
};