mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-12 23:37:00 +08:00
feat: CB-1890 CB-1848 indeterminate state for checkbox
This commit is contained in:
+2
@@ -34,6 +34,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
|
||||
control,
|
||||
disabled,
|
||||
selected,
|
||||
indeterminateSelected,
|
||||
loading,
|
||||
showInFilter,
|
||||
expanded,
|
||||
@@ -70,6 +71,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
selected={selected}
|
||||
indeterminateSelected={indeterminateSelected}
|
||||
expanded={expanded}
|
||||
showInFilter={showInFilter}
|
||||
externalExpanded={expandedExternal}
|
||||
|
||||
+5
@@ -26,6 +26,7 @@ interface INavigationNode {
|
||||
group: boolean;
|
||||
showInFilter: boolean;
|
||||
selected: boolean;
|
||||
indeterminateSelected: boolean;
|
||||
loading: boolean;
|
||||
expanded: boolean;
|
||||
leaf: boolean;
|
||||
@@ -54,6 +55,9 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod
|
||||
const control = getComputed(() => contextRef.context?.control);
|
||||
const disabled = getComputed(() => contextRef.context?.tree.disabled || false);
|
||||
const selected = getComputed(() => contextRef.context?.tree.isNodeSelected(node.id) || false);
|
||||
const indeterminateSelected = getComputed(
|
||||
() => contextRef.context?.tree.isNodeIndeterminateSelected(node.id) || false
|
||||
);
|
||||
|
||||
const handleClick = async (leaf: boolean) => await contextRef.context?.onClick?.(node, path, leaf);
|
||||
const handleOpen = async (leaf: boolean) => await contextRef.context?.onOpen?.(node, path, leaf);
|
||||
@@ -94,6 +98,7 @@ export function useNavigationNode(node: NavNode, path: string[]): INavigationNod
|
||||
control,
|
||||
disabled,
|
||||
selected,
|
||||
indeterminateSelected,
|
||||
loading,
|
||||
expanded,
|
||||
leaf,
|
||||
|
||||
@@ -89,6 +89,7 @@ export interface IElementsTree {
|
||||
getNodeState: (nodeId: string) => ITreeNodeState;
|
||||
isNodeExpanded: (nodeId: string, ignoreFilter?: boolean) => boolean;
|
||||
isNodeSelected: (nodeId: string) => boolean;
|
||||
isNodeIndeterminateSelected: (nodeId: string) => boolean;
|
||||
getNodeChildren: (nodeId: string) => string[];
|
||||
isGroup?: (node: NavNode) => boolean;
|
||||
setFilter: (value: string) => Promise<void>;
|
||||
@@ -196,7 +197,6 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async setSelection(nodeId: string, selected: boolean): Promise<void> {
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
@@ -303,6 +303,25 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
|
||||
return this.getNodeState(nodeId).selected;
|
||||
},
|
||||
isNodeIndeterminateSelected(nodeId: string): boolean {
|
||||
if (this.isNodeSelected(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (node && elementsTree.isGroup?.(node)) {
|
||||
const children = options.getChildren(nodeId) || [];
|
||||
|
||||
if (children.length > 0) {
|
||||
return children.some(child => this.isNodeSelected(child) || this.isNodeIndeterminateSelected(child));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getNodeChildren(nodeId: string): string[] {
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
@@ -330,6 +349,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
collapse() {
|
||||
for (const state of this.state.values()) {
|
||||
state.expanded = false;
|
||||
state.showInFilter = false;
|
||||
}
|
||||
},
|
||||
async show(nodeId: string, path: string[]): Promise<void> {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
@@ -96,6 +97,14 @@ interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputE
|
||||
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
|
||||
id, label, className, title, mod = ['primary'], ripple = true, style, ...rest
|
||||
}) {
|
||||
const checkboxRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (checkboxRef.current) {
|
||||
checkboxRef.current.indeterminate = rest.indeterminate || false;
|
||||
}
|
||||
});
|
||||
|
||||
return styled(
|
||||
useStyles(
|
||||
checkboxStyles,
|
||||
@@ -107,7 +116,7 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
|
||||
)(
|
||||
<checkbox-container className={className} title={title}>
|
||||
<checkbox>
|
||||
<checkbox-input as='input' type='checkbox' {...rest} id={id || rest.name} />
|
||||
<checkbox-input ref={checkboxRef} as='input' type='checkbox' {...rest} id={id || rest.name} />
|
||||
<checkbox-background>
|
||||
<checkbox-checkmark as='svg' viewBox='0 0 24 24'>
|
||||
<checkbox-checkmark-path as='path' fill='none' d='M1.73,12.91 8.1,19.28 22.79,4.59' />
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface ITreeNodeState {
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
selected?: boolean;
|
||||
indeterminateSelected?: boolean;
|
||||
externalExpanded?: boolean;
|
||||
expanded?: boolean;
|
||||
showInFilter?: boolean;
|
||||
|
||||
@@ -34,6 +34,7 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(function TreeNode
|
||||
group = false,
|
||||
loading = false,
|
||||
selected = false,
|
||||
indeterminateSelected = false,
|
||||
disabled = false,
|
||||
expanded = false,
|
||||
showInFilter = false,
|
||||
@@ -87,14 +88,17 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(function TreeNode
|
||||
inProgress: observable.ref,
|
||||
loading: observable.ref,
|
||||
selected: observable.ref,
|
||||
indeterminateSelected: observable.ref,
|
||||
expanded: observable.ref,
|
||||
externalExpanded: observable.ref,
|
||||
showInFilter: observable.ref,
|
||||
leaf: observable.ref,
|
||||
}, {
|
||||
group,
|
||||
disabled,
|
||||
loading,
|
||||
selected,
|
||||
indeterminateSelected,
|
||||
expanded,
|
||||
showInFilter,
|
||||
externalExpanded: externalExpanded || false,
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ITreeNodeContext {
|
||||
showInFilter: boolean;
|
||||
loading: boolean;
|
||||
selected: boolean;
|
||||
indeterminateSelected: boolean;
|
||||
leaf: boolean;
|
||||
group: boolean;
|
||||
readonly select: (multiple?: boolean, nested?: boolean) => Promise<void>;
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Props {
|
||||
group?: boolean;
|
||||
onSelect?: () => Promise<boolean | void> | boolean | void;
|
||||
selected?: boolean;
|
||||
indeterminate?: boolean;
|
||||
disabled?: boolean;
|
||||
loadIndicator?: boolean;
|
||||
className?: string;
|
||||
@@ -37,6 +38,7 @@ export const TreeNodeSelect = observer<Props>(function TreeNodeSelect({
|
||||
onSelect,
|
||||
group,
|
||||
selected,
|
||||
indeterminate,
|
||||
disabled,
|
||||
loadIndicator,
|
||||
className,
|
||||
@@ -51,6 +53,7 @@ export const TreeNodeSelect = observer<Props>(function TreeNodeSelect({
|
||||
group = group ?? context.group;
|
||||
const loading = loadIndicator && context.loading;
|
||||
selected = selected ?? context.selected;
|
||||
indeterminate = indeterminate ?? context.indeterminateSelected;
|
||||
|
||||
async function handleSelect() {
|
||||
await onSelect?.();
|
||||
@@ -72,6 +75,7 @@ export const TreeNodeSelect = observer<Props>(function TreeNodeSelect({
|
||||
: (
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
indeterminate={indeterminate}
|
||||
disabled={disabled}
|
||||
onChange={handleSelect}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user