projection selector component (#10294)

This commit is contained in:
Ayangade Adeoluwa 2022-01-14 14:53:33 +01:00 committed by GitHub
parent f100b5918d
commit 3d8e605245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 11 deletions

View File

@ -21,7 +21,7 @@ import Tooltip from "components/ads/Tooltip";
import { isEllipsisActive } from "utils/helpers";
import SegmentHeader from "components/ads/ListSegmentHeader";
import { useTheme } from "styled-components";
import { findIndex } from "lodash";
import { findIndex, isArray } from "lodash";
export type DropdownOnSelect = (value?: string, dropdownOption?: any) => void;
@ -252,7 +252,7 @@ const Selected = styled.div<{
export const DropdownContainer = styled.div<{ width: string; height?: string }>`
width: ${(props) => props.width};
height: ${(props) => props.height || `38px`};
height: ${(props) => props.height || `auto`};
position: relative;
span.bp3-popover-target {
display: inline-block;
@ -272,6 +272,7 @@ const DropdownSelect = styled.div``;
export const DropdownWrapper = styled.div<{
width: string;
isOpen: boolean;
}>`
width: ${(props) => props.width};
height: fit-content;
@ -279,6 +280,8 @@ export const DropdownWrapper = styled.div<{
background-color: ${(props) => props.theme.colors.dropdown.menu.bg};
border: 1px solid ${(props) => props.theme.colors.dropdown.menu.border};
padding: ${(props) => props.theme.spaces[3]}px 0;
overflow: hidden;
display: ${(props) => (props.isOpen ? "inline-block" : "none")};
.dropdown-search {
margin: 4px 12px 8px;
width: calc(100% - 24px);
@ -633,11 +636,13 @@ function DefaultDropDownValueNode({
interface DropdownOptionsProps extends DropdownProps, DropdownSearchProps {
optionClickHandler: (option: DropdownOption) => void;
selectedOptionClickHandler: (option: DropdownOption) => void;
renderOption?: RenderOption;
headerLabel?: string;
selected: DropdownOption | DropdownOption[];
optionWidth: string;
isMultiSelect?: boolean;
isOpen: boolean; // dropdown popover options flashes when closed, this prop helps to make sure it never happens again.
}
export function RenderDropdownOptions(props: DropdownOptionsProps) {
@ -663,6 +668,7 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
return (
<DropdownWrapper
className="ads-dropdown-options-wrapper"
isOpen={props.isOpen}
width={optionWidth}
>
{props.enableSearch && (
@ -706,7 +712,12 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
aria-selected={isSelected}
className="t--dropdown-option"
key={index}
onClick={() => props.optionClickHandler(option)}
onClick={
// users should be able to unselect a selected option by clicking the option again.
isSelected
? () => props.selectedOptionClickHandler(option)
: () => props.optionClickHandler(option)
}
role="option"
selected={isSelected}
>
@ -803,7 +814,7 @@ export default function Dropdown(props: DropdownProps) {
(option: DropdownOption) => {
if (props.isMultiSelect) {
// Multi select -> typeof selected is array of objects
if (!selected) {
if (isArray(selected) && selected.length < 1) {
setSelected([option]);
} else {
const newOptions: DropdownOption[] = [
@ -942,7 +953,7 @@ export default function Dropdown(props: DropdownProps) {
className={props.className}
disabled={props.disabled}
hasError={errorFlag}
height={props.height || getMinHeight(props.isMultiSelect)}
height={props.height || "38px"}
isMultiSelect={props.isMultiSelect}
isOpen={isOpen}
onClick={() => setIsOpen(!isOpen)}
@ -986,7 +997,7 @@ export default function Dropdown(props: DropdownProps) {
<DropdownContainer
className={props.containerClassName + " " + replayHighlightClass}
data-cy={props.cypressSelector}
height={getMinHeight(props.isMultiSelect)}
height={"38px"}
onKeyDown={handleKeydown}
role="listbox"
tabIndex={0}
@ -1006,16 +1017,13 @@ export default function Dropdown(props: DropdownProps) {
<RenderDropdownOptions
{...props}
isMultiSelect={props.isMultiSelect}
isOpen={isOpen}
optionClickHandler={optionClickHandler}
optionWidth={dropdownOptionWidth}
selected={selected ? selected : { id: undefined, value: undefined }}
selectedOptionClickHandler={selectedOptionClickHandler}
/>
</Popover>
</DropdownContainer>
);
}
function getMinHeight(isMultiSelect?: boolean): string {
if (isMultiSelect) return "44px";
return "38px";
}

View File

@ -0,0 +1,138 @@
import React from "react";
import BaseControl, { ControlProps } from "./BaseControl";
import styled from "styled-components";
import Dropdown, { DropdownOption } from "components/ads/Dropdown";
import { ControlType } from "constants/PropertyControlConstants";
import _ from "lodash";
import { FieldArray, getFormValues } from "redux-form";
import { connect } from "react-redux";
import { AppState } from "reducers";
import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
import { QueryAction } from "entities/Action";
const DropdownSelect = styled.div`
font-size: 14px;
width: 50vh;
`;
class ProjectionSelectorControl extends BaseControl<
ProjectionSelectorControlProps
> {
render() {
let width = "50vh";
if (this.props.customStyles && this.props?.customStyles?.width) {
width = this.props?.customStyles?.width;
}
return (
<DropdownSelect data-cy={this.props.configProperty} style={{ width }}>
<FieldArray
component={renderDropdown}
name={this.props.configProperty}
options={this.props.options}
props={{ ...this.props, width }}
rerenderOnEveryChange={false}
/>
</DropdownSelect>
);
}
getControlType(): ControlType {
return "PROJECTION";
}
}
function renderDropdown(props: any): JSX.Element {
// values that have been selected and stored in redux form state.
let selectedValues = props?.fields.getAll();
if (_.isUndefined(selectedValues)) {
selectedValues = props?.initialValue || [];
}
// options that correspond to the selected values kept in redux form state.
// the dropdown component requires the entire option object and not just the value.
const selectedOptions = props.dropDownOptions.filter(
(option: DropdownOption) => selectedValues.includes(option.value),
);
// select option
const onSelectOptions = (option: string | undefined) => {
if (option) {
props.fields.push(option);
}
};
// remove options
const removeOption = (option: string | undefined) => {
const options = selectedValues.map((field: DropdownOption) => field);
const optionIndex = options.indexOf(option);
props.fields.remove(optionIndex);
};
return (
<Dropdown
boundary="window"
disabled={props.disabled}
dontUsePortal={false}
dropdownMaxHeight="250px"
errorMsg={props.props?.errorText}
helperText={props.props?.info}
isMultiSelect
onSelect={onSelectOptions}
optionWidth="50vh"
options={props.dropDownOptions}
placeholder={props.props?.placeholderText || "Select options"}
removeSelectedOption={removeOption}
selected={selectedOptions}
showLabelOnly
width={props?.props?.width ? props?.props?.width : "50vh"}
/>
);
}
export interface ProjectionSelectorControlProps extends ControlProps {
options: DropdownOption[];
placeholderText: string;
propertyValue: string;
subtitle?: string;
isMultiSelect?: boolean;
isSearchable?: boolean;
fetchOptionsCondtionally?: boolean;
}
const mapStateToProps = (
state: AppState,
ownProps: ProjectionSelectorControlProps,
) => {
let dropDownOptions: DropdownOption[] = [];
// if the component has an option enabled to fetch the options dynamically,
if (ownProps.fetchOptionsCondtionally) {
// TODO: this is just a test, will be updated once the fetchDynamicFormData is implemented
dropDownOptions = [
{ label: "Test1", value: "SINGLE" },
{ label: "Test2", value: "ALL" },
];
const dynamicFormDataString = _.get(
getFormValues(QUERY_EDITOR_FORM_NAME)(state) as QueryAction,
"actionConfiguration.formData.updateMany.query",
);
// ownProps.configProperty will be used to filter from the array of data
// const dynamicFormDataString = getFormEvaluationState(state);
try {
dropDownOptions = JSON.parse(dynamicFormDataString);
} catch (e) {
dropDownOptions = [];
}
} else {
dropDownOptions = ownProps.options;
}
return {
dropDownOptions,
};
};
export default connect(mapStateToProps)(ProjectionSelectorControl);

View File

@ -42,6 +42,9 @@ import PaginationControl, {
import SortingControl, {
SortingControlProps,
} from "components/formControls/SortingControl";
import ProjectionSelectorControl, {
ProjectionSelectorControlProps,
} from "components/formControls/ProjectionSelectorControl";
class FormControlRegistry {
static registerFormControlBuilders() {
@ -132,6 +135,13 @@ class FormControlRegistry {
return <SortingControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("PROJECTION", {
buildPropertyControl(
controlProps: ProjectionSelectorControlProps,
): JSX.Element {
return <ProjectionSelectorControl {...controlProps} />;
},
});
}
}