PromucFlow_constructor/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx

373 lines
9.9 KiB
TypeScript
Raw Normal View History

2020-02-06 07:01:25 +00:00
import React, { ReactNode } from "react";
2019-11-25 05:07:27 +00:00
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import {
MenuItem,
Button,
ControlGroup,
Label,
Classes,
2020-02-06 07:01:25 +00:00
Checkbox,
Icon,
} from "@blueprintjs/core";
2020-02-06 07:01:25 +00:00
import { IconNames } from "@blueprintjs/icons";
2019-11-25 05:07:27 +00:00
import { SelectionType, DropdownOption } from "widgets/DropdownWidget";
2020-02-06 07:01:25 +00:00
import {
Select,
MultiSelect,
IItemRendererProps,
Classes as MultiSelectClasses,
} from "@blueprintjs/select";
2019-10-31 05:28:11 +00:00
import _ from "lodash";
import { WIDGET_PADDING } from "constants/WidgetConstants";
2019-11-06 12:12:41 +00:00
import "../../../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
2020-02-06 07:01:25 +00:00
import styled, {
2020-03-13 07:24:03 +00:00
createGlobalStyle,
2020-02-06 07:01:25 +00:00
labelStyle,
BlueprintCSSTransform,
BlueprintInputTransform,
} from "constants/DefaultTheme";
import { Colors } from "constants/Colors";
import Fuse from "fuse.js";
const FUSE_OPTIONS = {
shouldSort: true,
threshold: 0.5,
location: 0,
minMatchCharLength: 3,
findAllMatches: true,
keys: ["label", "value"],
};
2019-10-31 05:28:11 +00:00
const SingleDropDown = Select.ofType<DropdownOption>();
const MultiDropDown = MultiSelect.ofType<DropdownOption>();
2019-12-18 07:23:28 +00:00
const StyledSingleDropDown = styled(SingleDropDown)`
div {
flex: 1 1 auto;
2019-12-18 07:23:28 +00:00
}
span {
width: 100%;
2020-02-06 07:01:25 +00:00
position: relative;
2019-12-18 07:23:28 +00:00
}
2020-02-06 07:01:25 +00:00
.${Classes.BUTTON} {
2019-12-18 07:23:28 +00:00
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
box-shadow: none;
background: white;
2019-12-18 07:23:28 +00:00
}
2020-02-06 07:01:25 +00:00
.${Classes.BUTTON_TEXT} {
2019-12-18 07:23:28 +00:00
text-overflow: ellipsis;
2020-02-06 07:01:25 +00:00
text-align: left;
2019-12-18 07:23:28 +00:00
overflow: hidden;
2020-02-06 07:01:25 +00:00
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
2019-12-18 07:23:28 +00:00
}
2020-02-06 07:01:25 +00:00
&& {
.${Classes.ICON} {
width: fit-content;
color: ${Colors.SLATE_GRAY};
}
2019-12-18 07:23:28 +00:00
}
`;
2019-12-18 17:05:28 +00:00
2020-02-27 07:14:32 +00:00
const StyledControlGroup = styled(ControlGroup)<{ haslabel: string }>`
2020-02-06 07:01:25 +00:00
&&& > {
label {
2020-01-28 08:21:22 +00:00
${labelStyle}
2020-02-06 07:01:25 +00:00
margin: 7px ${WIDGET_PADDING * 2}px 0 0;
align-self: flex-start;
flex: 0 1 30%;
2020-02-06 07:01:25 +00:00
max-width: calc(30% - ${WIDGET_PADDING}px);
text-align: right;
}
2020-02-06 07:01:25 +00:00
span {
max-width: ${props =>
2020-02-27 07:14:32 +00:00
props.haslabel === "true" ? `calc(70% - ${WIDGET_PADDING}px)` : "100%"};
2020-02-06 07:01:25 +00:00
}
}
`;
2020-03-13 07:24:03 +00:00
const DropdownStyles = createGlobalStyle`
.select-popover-wrapper {
2020-02-06 07:01:25 +00:00
width: 100%;
border-radius: ${props => props.theme.radii[1]}px;
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
padding: ${props => props.theme.spaces[3]}px;
2020-02-18 10:41:52 +00:00
background: white;
2020-03-13 07:24:03 +00:00
&& .${Classes.MENU} {
max-width: 100%;
max-height: auto;
}
&&&& .${Classes.MENU_ITEM} {
border-radius: ${props => props.theme.radii[1]}px;
&:hover{
background: ${Colors.POLAR};
}
&.${Classes.ACTIVE} {
background: ${Colors.POLAR};
color: ${props => props.theme.colors.textDefault};
position:relative;
&.single-select{
&:before{
left: 0;
top: -2px;
position: absolute;
content: "";
background: ${props => props.theme.colors.primaryOld};
2020-03-13 07:24:03 +00:00
border-radius: 4px 0 0 4px;
width: 4px;
height:100%;
}
}
}
.${Classes.CONTROL} .${Classes.CONTROL_INDICATOR} {
background: white;
box-shadow: none;
border-width: 2px;
border-style: solid;
border-color: ${Colors.GEYSER};
&::before {
width: auto;
height: 1em;
}&
}
.${Classes.CONTROL} input:checked ~ .${Classes.CONTROL_INDICATOR} {
background: ${props => props.theme.colors.primaryOld};
2020-03-13 07:24:03 +00:00
color: ${props => props.theme.colors.textOnDarkBG};
border-color: ${props => props.theme.colors.primaryOld};
2020-02-06 07:01:25 +00:00
}
}
}
2020-03-13 07:24:03 +00:00
`;
const DropdownContainer = styled.div`
${BlueprintCSSTransform}
`;
2019-12-18 17:05:28 +00:00
const StyledMultiDropDown = styled(MultiDropDown)`
2020-02-06 07:01:25 +00:00
div {
flex: 1 1 auto;
}
.${MultiSelectClasses.MULTISELECT} {
position: relative;
2019-12-18 17:05:28 +00:00
min-width: 0;
}
2020-02-06 07:01:25 +00:00
&& {
${BlueprintInputTransform}
.${Classes.TAG_INPUT} {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
text-overflow: ellipsis;
overflow: hidden;
.${Classes.TAG_INPUT_VALUES} {
margin-top: 0;
overflow: hidden;
padding: 2px 0;
}
.${Classes.TAG} {
background: none;
border: 1px solid #D0D7DD;
border-radius: 2px;
margin-bottom: 0;
max-width: 100px;
}
& > .${Classes.ICON} {
align-self: center;
margin-right: 10px;
color: ${Colors.SLATE_GRAY};
}
.${Classes.INPUT_GHOST} {
flex: 0 0 auto;
margin: 0;
2020-02-06 07:01:25 +00:00
}
}
}
`;
const StyledCheckbox = styled(Checkbox)`
&&.${Classes.CHECKBOX}.${Classes.CONTROL} {
margin: 0;
}
2019-12-18 17:05:28 +00:00
`;
2020-02-06 07:01:25 +00:00
2019-10-31 05:28:11 +00:00
class DropDownComponent extends React.Component<DropDownComponentProps> {
render() {
const selectedItems = this.props.selectedIndexArr
? _.map(this.props.selectedIndexArr, index => {
return this.props.options[index];
})
: [];
return (
<DropdownContainer>
2020-03-13 07:24:03 +00:00
<DropdownStyles />
2020-02-27 07:14:32 +00:00
<StyledControlGroup
fill
haslabel={!!this.props.label ? "true" : "false"}
>
2020-01-28 11:46:04 +00:00
{this.props.label && (
2020-01-31 11:13:16 +00:00
<Label
className={
this.props.isLoading
? Classes.SKELETON
: Classes.TEXT_OVERFLOW_ELLIPSIS
}
>
2020-01-28 11:46:04 +00:00
{this.props.label}
</Label>
)}
{this.props.selectionType === "SINGLE_SELECT" ? (
<StyledSingleDropDown
2020-02-06 07:01:25 +00:00
className={this.props.isLoading ? Classes.SKELETON : ""}
items={this.props.options}
filterable={true}
2020-02-06 07:01:25 +00:00
itemRenderer={this.renderSingleSelectItem}
onItemSelect={this.onItemSelect}
disabled={this.props.disabled}
2020-02-06 07:01:25 +00:00
popoverProps={{
minimal: true,
2020-03-13 07:24:03 +00:00
usePortal: true,
popoverClassName: "select-popover-wrapper",
2020-02-06 07:01:25 +00:00
}}
itemListPredicate={this.itemListPredicate}
>
<Button
2020-02-06 07:01:25 +00:00
rightIcon={IconNames.CHEVRON_DOWN}
text={
2020-02-11 11:03:10 +00:00
!_.isEmpty(this.props.options) &&
2020-03-16 15:42:39 +00:00
this.props.selectedIndex !== undefined &&
this.props.selectedIndex > -1
? this.props.options[this.props.selectedIndex].label
2020-01-28 11:46:04 +00:00
: "-- Empty --"
}
/>
</StyledSingleDropDown>
) : (
<StyledMultiDropDown
2020-02-06 07:01:25 +00:00
className={this.props.isLoading ? Classes.SKELETON : ""}
items={this.props.options}
itemListPredicate={this.itemListPredicate}
placeholder={this.props.placeholder}
tagRenderer={this.renderTag}
2020-02-06 07:01:25 +00:00
itemRenderer={this.renderMultiSelectItem}
selectedItems={selectedItems}
2020-02-06 07:01:25 +00:00
tagInputProps={{
onRemove: this.onItemRemoved,
tagProps: { minimal: true },
// inputProps: { readOnly: true },
disabled: this.props.disabled,
2020-02-06 07:01:25 +00:00
rightElement: <Icon icon={IconNames.CHEVRON_DOWN} />,
}}
onItemSelect={this.onItemSelect}
2020-02-06 07:01:25 +00:00
popoverProps={{
minimal: true,
2020-03-13 07:24:03 +00:00
usePortal: true,
popoverClassName: "select-popover-wrapper",
2020-02-06 07:01:25 +00:00
}}
2020-03-13 07:24:03 +00:00
/>
)}
</StyledControlGroup>
</DropdownContainer>
2019-10-31 05:28:11 +00:00
);
}
itemListPredicate(query: string, items: DropdownOption[]) {
const fuse = new Fuse(items, FUSE_OPTIONS);
return query ? fuse.search(query) : items;
}
onItemSelect = (item: DropdownOption): void => {
2019-10-31 05:28:11 +00:00
this.props.onOptionSelected(item);
};
onItemRemoved = (_tag: string, index: number) => {
this.props.onOptionRemoved(this.props.selectedIndexArr[index]);
2019-10-31 05:28:11 +00:00
};
renderTag = (option: DropdownOption) => {
return option.label;
};
isOptionSelected = (selectedOption: DropdownOption) => {
const optionIndex = _.findIndex(this.props.options, option => {
return option.value === selectedOption.value;
});
if (this.props.selectionType === "SINGLE_SELECT") {
return optionIndex === this.props.selectedIndex;
} else {
return (
_.findIndex(this.props.selectedIndexArr, index => {
return index === optionIndex;
}) !== -1
);
}
};
2020-02-06 07:01:25 +00:00
renderSingleSelectItem = (
option: DropdownOption,
itemProps: IItemRendererProps,
) => {
2019-10-31 05:28:11 +00:00
if (!itemProps.modifiers.matchesPredicate) {
return null;
}
const isSelected: boolean = this.isOptionSelected(option);
return (
<MenuItem
2020-02-06 07:01:25 +00:00
className="single-select"
active={isSelected}
2019-10-31 05:28:11 +00:00
key={option.value}
onClick={itemProps.handleClick}
text={option.label}
/>
);
};
2020-02-06 07:01:25 +00:00
renderMultiSelectItem = (
option: DropdownOption,
itemProps: IItemRendererProps,
) => {
if (!itemProps.modifiers.matchesPredicate) {
return null;
}
const isSelected: boolean = this.isOptionSelected(option);
const content: ReactNode = (
<React.Fragment>
<StyledCheckbox
checked={isSelected}
label={option.label}
alignIndicator="left"
onChange={(e: any) => itemProps.handleClick(e)}
/>
</React.Fragment>
);
return (
<MenuItem
className="multi-select"
active={isSelected}
key={option.value}
text={content}
/>
);
};
2019-10-31 05:28:11 +00:00
}
export interface DropDownComponentProps extends ComponentProps {
selectionType: SelectionType;
disabled?: boolean;
onOptionSelected: (optionSelected: DropdownOption) => void;
onOptionRemoved: (removedIndex: number) => void;
placeholder?: string;
label?: string;
2020-02-11 11:03:10 +00:00
selectedIndex?: number;
2019-10-31 05:28:11 +00:00
selectedIndexArr: number[];
options: DropdownOption[];
2019-12-03 04:41:10 +00:00
isLoading: boolean;
2019-10-31 05:28:11 +00:00
}
export default DropDownComponent;