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

415 lines
11 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;
min-height: 32px;
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}
`;
const StyledCheckbox = styled(Checkbox)`
&&.${Classes.CHECKBOX}.${Classes.CONTROL} {
margin: 0;
}
`;
const StyledMultiDropDown = styled(MultiDropDown)<{
hideCloseButtonIndex: number;
height: number;
width: number;
}>`
2020-02-06 07:01:25 +00:00
div {
flex: 1 1 auto;
height: ${props => props.height - WIDGET_PADDING * 2}px;
2020-02-06 07:01:25 +00:00
}
.${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;
min-height: 32px;
2020-02-06 07:01:25 +00:00
.${Classes.TAG_INPUT_VALUES} {
margin-top: 0;
overflow: hidden;
display: flex;
height: ${props => props.height - WIDGET_PADDING * 2 - 2}px;
2020-02-06 07:01:25 +00:00
}
2020-02-06 07:01:25 +00:00
.${Classes.TAG} {
background: none;
border: 1px solid #D0D7DD;
border-radius: 2px;
margin: 3px 2px;
max-width: ${props => props.width * 0.85}px;
height: 24px;
}
${props =>
props.hideCloseButtonIndex >= 0 &&
`
.${Classes.TAG}:nth-child(${props.hideCloseButtonIndex}) {
.${Classes.ICON} {
align-self: center;
margin-right: 0px;
color: ${Colors.SLATE_GRAY};
}
button {
display: none;
}
2020-02-06 07:01:25 +00:00
}
`}
2020-02-06 07:01:25 +00:00
& > .${Classes.ICON} {
align-self: center;
margin-right: 10px;
color: ${Colors.SLATE_GRAY};
}
.${Classes.INPUT_GHOST} {
flex: 0 0 auto;
margin: 0;
display: flex;
height: 26px;
flex: 1;
2020-02-06 07:01:25 +00:00
}
}
}
`;
2019-10-31 05:28:11 +00:00
class DropDownComponent extends React.Component<DropDownComponentProps> {
render() {
const { selectedIndexArr, options } = this.props;
const selectedItems = selectedIndexArr
? _.map(selectedIndexArr, index => options[index])
2019-10-31 05:28:11 +00:00
: [];
const hideCloseButtonIndex = -1;
2019-10-31 05:28:11 +00:00
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
: "-- Select --"
}
/>
</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}
height={this.props.height}
2020-02-06 07:01:25 +00:00
tagInputProps={{
onRemove: this.onItemRemoved,
tagProps: (value, index) => ({
minimal: true,
interactive:
hideCloseButtonIndex - 1 === index ? true : false,
rightIcon:
hideCloseButtonIndex - 1 === index
? IconNames.CHEVRON_DOWN
: undefined,
}),
disabled: this.props.disabled,
fill: true,
2020-02-06 07:01:25 +00:00
rightElement: <Icon icon={IconNames.CHEVRON_DOWN} />,
}}
hideCloseButtonIndex={hideCloseButtonIndex}
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
}}
width={this.props.width}
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;
width: number;
height: number;
2019-10-31 05:28:11 +00:00
}
export default DropDownComponent;