PromucFlow_constructor/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx
Paul Li eb37416a2a
feat: Controls for labels in widgets to align the widgets in forms and other places (#10600)
* feat: When there are multiple input widgets with different label lengths then the input box looks misaligned

-- Create a new property control for a label position
-- Create a new property control for a label alignment
-- Prototype a label section for Input widget

* feat: When there are multiple input widgets with different label lengths then the input box looks misaligned

-- Add a property, labelWidth in the property pane

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Input widget: Implement all the requirements in case its type is Text

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Adapt the functionalty on other types of the input widget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add label functionalities into DropdownWidget
-- Clean up for the input widget and DRY

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add label functionalities into MultiSelectWidget
-- Eliminate unnecessary component prop, columns

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add label functionalties into Tree Select widget
-- Add styles for alignment between lable and input control over the widgets

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add label functionalities into MultiSelectTreeWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Introduce label functionalities into DatePickerWidget2
-- Use width instead of columns prop in InputWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Apply label functionalities into RichTextEditorWidget
-- Eliminate compactMode from StyledLabel

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Apply label functionalities into CheckboxGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Apply label functionalities into SwitchGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Reimplement switch group for the correct meaning of right alignment

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Apply label functionalities into RadioGroupWidget
-- Add new properties, alignment and inline for consistency

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Adjust cols and rows for RadioGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate unused StyledRadioProps

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Complete first MVP of enhanced SwitchGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Complete the first MVP of enhanced RadioGroupWidget
-- Eliminate unused StyledSwitch component for SwitchGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add min-height, align-self rules for LabelContainer

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Use original label property for RadioGroupWidget
-- Add a migration for adding isInline and alignment properties for RadioGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Update version to latest one in DSLMigrationsUtils.test.ts

* fix failing jest test

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Reimplement label functionalities on BaseInputWidget, InputWidgetV2, CurrencyInputWidget, PhoneInputWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate unused imports in DSLMigrationsUtils

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on the label related test case which is failed in Input_spec.js

* feat: Controls for labels in widgets to align the widgets in forms and other places
-- Fix on #10119: The label text truncates on resizing the input widget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix scroll issue when shrink with MultiSelectWidget and MultiSelectTreeWidget

* fix: Widget Popup test

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Reimplement width and alginment features on the level of label element
-- Prevent actual inputs from DropdownWidget, MultiSelectWidget, SingleSelectTreeWidget, MultiSelectTreeWidget from overflow when resizing
-- Enable label feature on a RadioGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Set label container's default width to 33% when width is not set

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix crash issue when labelWidth is filled by non-numeric value, eliminating passing NaN as its value

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Set flex-grow to zero on input types other than TEXT

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Implement label features on newly created MultiSelectWidgetV2

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate LabelPositionTypes, directly using enum LabelPosition
-- Add a comment for a constant LABEL_MAX_WIDTH_RATE
-- Directly import React for LabelAlignmentOptionsControl
-- Remove unnecessary constructor for LabelAlignmentOptionsControl
-- Define handleAlign instance method as a higher-order function
-- Only migrate alignment property for RadioGroupWidget
-- Use Object.hasOwnProperty instead of in operator

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Migrate alignment property of RadioGroupWidget in case of currentDSL.version is 52

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Revert currentDSL.version to 52

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add a Jest test case for RadioGroupWidget's alignment property migration

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Replace all nested ternary operators with if statements

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Implement label feature on new version of SelectWidget
-- Add Cypress tests for widgets' label section

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Refactor code for BaseInputWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Change CSS selector for step buttons for Numeric BaseInputWidget
-- Directly use migrateRadioGroupAlignmentProperty migration function without using transformDSL

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on typo about migrateRadioGroupAlignmentProperty

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add data-testid attributes for Cypress selectors

* feat: Deprecate form button widget

-- Assert flex-direction to row in CheckboxGroup_spec.js

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add a missing data-testid for SelectWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on failed test cases: CheckboxGroup_spec, DatePicker_2_spec, MultiSelectWidgetV2

* fix: Select popup DSL

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Create a new property control, NumericInputControl
-- Replace all the label properties with the newly created controls

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Create a new Cypress command, checkLabelWidth and apply to all related test cases
-- Increase width in checkboxgroupDsl.json
-- Rename className for label in MultiSelectWidgetV2

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Reimplement the tooltip feature for labels
-- Add missing props for labels in DateField, MultiSelectField, RadioGroupField, SelectField fields for JSONFormWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Refactor property controls, including LabelPositionOptionsControl, LabelAlignmentOptionsControl, NumericInputControl to keep consistency
-- Apply default values into label section

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Extract the label related parts from the various widgets as an independent component

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate TypeScript any type from BaseInputComponent

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Change labelPosition property type to DROP_DOWN
-- Modify LabelAlignmentOptionsControl to use ButtonTabComponent

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Define getLabelWidth method into BaseWidget
-- Extract the common CSS rules for the widget containers
-- Revert rows and columns for SwitchGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on the failed test case in DSLMigrationsUtils.test.ts

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on overflow issue on CheckboxGroupWidget
-- Create a distinctive spec file for label feature
-- Eliminate the redundant label specs with the relevant widgets

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Delete unnecessary files, including Select_spec.js, LabelButton.tsx and LabelPositionOptionsControl.tsx
-- Revise wrong comment for checkLabelForWidget Cypress command

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Do not set the label width only if its value is 0
-- Clean up the component for DatePickerWidget2

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate unused imports in DatePickerWidget2

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Make RadioGroupWidget's layout flexible in all modes

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on Cypress test case for RadioGroupWidget in Widgets_Labels_spec
-- Change Cypress commands, including addAction, addSuccessMessage, enterActionValue to accept parentSelector

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Change getLabelWidth method to not have any argument
-- Define some constants for label numbers
-- Extract the common styles for SwitchGroupWidget and RadioGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Refactor some constants

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate unused width prop from RadioGroupWidget
-- Get labelWidth from getLabelWidth

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Eliminate the min-height restriction on a label
-- Eliminate the scroll on the earlier InputWidgetV2 which was not in compact mode

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add one more condition checking if the current input type is text

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Extract common code base for MultiSelectTreeWidget and MultiSelectWidgetV2
-- Apply a few CSS fixes on the scrollbar issue select related widgets

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Apply some tweaks for earlier widgets with labels so as not to be broken UX

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Fix on the failed Cypress test case in Widget_Popup_spec.js

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Add constants, LABEL_DEFAULT_WIDTH_RATE, SELECT_DEFAULT_HEIGHT, LABEL_MARGIN_OLD_SELECT

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Increase the widths of CheckboxGroupWidget and SwitchGroupWidget

* feat: Controls for labels in widgets to align the widgets in forms and other places

-- Set the font size to 14px for NumericInputControl

Co-authored-by: ohansFavour <fohanekwu@gmail.com>
Co-authored-by: Tolulope Adetula <31691737+Tooluloope@users.noreply.github.com>
2022-04-14 16:47:25 +08:00

301 lines
8.1 KiB
TypeScript

import React, {
ChangeEvent,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import TreeSelect, { TreeSelectProps as SelectProps } from "rc-tree-select";
import {
TreeSelectContainer,
DropdownStyles,
StyledIcon,
InputContainer,
} from "./index.styled";
import "rc-tree-select/assets/index.less";
import { DefaultValueType } from "rc-tree-select/lib/interface";
import { TreeNodeProps } from "rc-tree-select/lib/TreeNode";
import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil";
import {
CANVAS_CLASSNAME,
MODAL_PORTAL_CLASSNAME,
TextSize,
} from "constants/WidgetConstants";
import { Alignment, Button, Classes, InputGroup } from "@blueprintjs/core";
import { labelMargin, WidgetContainerDiff } from "widgets/WidgetUtils";
import Icon from "components/ads/Icon";
import { Colors } from "constants/Colors";
import { LabelPosition } from "components/constants";
import LabelWithTooltip from "components/ads/LabelWithTooltip";
export interface TreeSelectProps
extends Required<
Pick<
SelectProps,
| "disabled"
| "placeholder"
| "loading"
| "dropdownStyle"
| "allowClear"
| "options"
>
> {
value?: DefaultValueType;
onChange: (value?: DefaultValueType, labelList?: ReactNode[]) => void;
expandAll: boolean;
mode: CheckedStrategy;
labelText: string;
labelAlignment?: Alignment;
labelPosition?: LabelPosition;
labelWidth?: number;
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
compactMode: boolean;
dropDownWidth: number;
width: number;
isValid: boolean;
filterText?: string;
widgetId: string;
isFilterable: boolean;
}
const getSvg = (expanded: boolean) => (
<i
style={{
cursor: "pointer",
backgroundColor: "transparent",
display: "inline-flex",
width: "14px",
height: "100%",
}}
>
<StyledIcon
className="switcher-icon"
expanded={expanded}
fillColor={Colors.GREY_10}
name="dropdown"
/>
</i>
);
const switcherIcon = (treeNode: TreeNodeProps) => {
if (treeNode.isLeaf) {
return (
<i
style={{
cursor: "pointer",
backgroundColor: "white",
display: "inline-flex",
width: "14px",
}}
/>
);
}
return getSvg(treeNode.expanded);
};
const FOCUS_TIMEOUT = 500;
function MultiTreeSelectComponent({
allowClear,
compactMode,
disabled,
dropdownStyle,
dropDownWidth,
expandAll,
filterText,
isFilterable,
isValid,
labelAlignment,
labelPosition,
labelStyle,
labelText,
labelTextColor,
labelTextSize,
labelWidth,
loading,
mode,
onChange,
options,
placeholder,
value,
widgetId,
width,
}: TreeSelectProps): JSX.Element {
const [key, setKey] = useState(Math.random());
const [filter, setFilter] = useState(filterText ?? "");
const _menu = useRef<HTMLElement | null>(null);
const labelRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [memoDropDownWidth, setMemoDropDownWidth] = useState(0);
// treeDefaultExpandAll is uncontrolled after first render,
// using this to force render to respond to changes in expandAll
useEffect(() => {
setKey(Math.random());
}, [expandAll]);
const clearButton = useMemo(
() =>
filter ? (
<Button icon="cross" minimal onClick={() => setFilter("")} />
) : null,
[filter],
);
const getDropdownPosition = useCallback(() => {
const node = _menu.current;
if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
return document.querySelector(
`.${MODAL_PORTAL_CLASSNAME}`,
) as HTMLElement;
}
return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
}, []);
const onQueryChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
event.stopPropagation();
setFilter(event.target.value);
}, []);
useEffect(() => {
const parentWidth = width - WidgetContainerDiff;
if (compactMode && labelRef.current) {
const labelWidth = labelRef.current.getBoundingClientRect().width;
const widthDiff = parentWidth - labelWidth - labelMargin;
setMemoDropDownWidth(
widthDiff > dropDownWidth ? widthDiff : dropDownWidth,
);
return;
}
setMemoDropDownWidth(
parentWidth > dropDownWidth ? parentWidth : dropDownWidth,
);
}, [compactMode, dropDownWidth, width, labelText]);
const dropdownRender = useCallback(
(
menu: React.ReactElement<any, string | React.JSXElementConstructor<any>>,
) => (
<>
{isFilterable ? (
<InputGroup
autoFocus
inputRef={inputRef}
leftIcon="search"
onChange={onQueryChange}
onKeyDown={(e) => e.stopPropagation()}
placeholder="Filter..."
rightElement={clearButton as JSX.Element}
small
type="text"
value={filter}
/>
) : null}
<div className={`${loading ? Classes.SKELETON : ""}`}>{menu}</div>
</>
),
[loading, isFilterable, filter, onQueryChange],
);
const onOpen = useCallback((open: boolean) => {
if (open) {
setTimeout(() => inputRef.current?.focus(), FOCUS_TIMEOUT);
}
}, []);
const onClear = useCallback(() => onChange([], []), []);
return (
<TreeSelectContainer
allowClear={allowClear}
compactMode={compactMode}
data-testid="multitreeselect-container"
isValid={isValid}
labelPosition={labelPosition}
ref={_menu as React.RefObject<HTMLDivElement>}
>
<DropdownStyles dropDownWidth={memoDropDownWidth} id={widgetId} />
{labelText && (
<LabelWithTooltip
alignment={labelAlignment}
className={`multitree-select-label`}
color={labelTextColor}
compact={compactMode}
disabled={disabled}
fontSize={labelTextSize}
fontStyle={labelStyle}
loading={loading}
position={labelPosition}
ref={labelRef}
text={labelText}
width={labelWidth}
/>
)}
<InputContainer compactMode={compactMode} labelPosition={labelPosition}>
<TreeSelect
allowClear={allowClear}
animation="slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
className="rc-tree-select"
clearIcon={
<Icon
className="clear-icon"
fillColor={Colors.GREY_10}
name="close-x"
/>
}
disabled={disabled}
dropdownClassName={`tree-multiselect-dropdown multiselecttree-popover-width-${widgetId}`}
dropdownRender={dropdownRender}
dropdownStyle={dropdownStyle}
filterTreeNode
getPopupContainer={getDropdownPosition}
inputIcon={
<Icon
className="dropdown-icon"
fillColor={disabled ? Colors.GREY_7 : Colors.GREY_10}
name="dropdown"
/>
}
key={key}
loading={loading}
maxTagCount={"responsive"}
maxTagPlaceholder={(e) => `+${e.length} more`}
multiple
notFoundContent="No Results Found"
onChange={onChange}
onClear={onClear}
onDropdownVisibleChange={onOpen}
placeholder={placeholder}
removeIcon={
<Icon
className="remove-icon"
fillColor={Colors.GREY_10}
name="close-x"
/>
}
searchValue={filter}
showArrow
showCheckedStrategy={mode}
showSearch={false}
style={{ width: "100%" }}
switcherIcon={switcherIcon}
transitionName="rc-tree-select-dropdown-slide-up"
treeCheckable={
<span className={`rc-tree-select-tree-checkbox-inner`} />
}
treeData={options}
treeDefaultExpandAll={expandAll}
treeIcon
treeNodeFilterProp="label"
value={value}
/>
</InputContainer>
</TreeSelectContainer>
);
}
export default MultiTreeSelectComponent;