Skip to content

Commit

Permalink
#2192 Added icon support for dropdown menus (#2120)
Browse files Browse the repository at this point in the history
Signed-off-by: Prince Sanchez <[email protected]>
Co-authored-by: Prince Sanchez <[email protected]>
  • Loading branch information
PRINCESANCHEZ and Prince Sanchez authored Oct 3, 2024
1 parent 1709d3e commit 196215b
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ import { expect } from "chai";
import sinon from "sinon";
import editStyleResource from "../test_resources/json/form-editstyle-test.json";
import expressionTestResource from "../test_resources/json/expression-one-category.json";

import numberfieldResource from "../test_resources/paramDefs/numberfield_paramDef.json";
import textfieldResource from "../test_resources/paramDefs/textfield_paramDef.json";
import textAreaResource from "../test_resources/paramDefs/textarea_paramDef.json";
import emptyParamDef from "../test_resources/paramDefs/empty_paramDef.json";
import oneofselectParamDef from "../test_resources/paramDefs/oneofselect_paramDef.json";
import structureListEditorParamDef from "../test_resources/paramDefs/structurelisteditor_paramDef.json";
import structureEditorParamDef from "../test_resources/paramDefs/structureeditor_paramDef.json";
import { IntlProvider } from "react-intl";
import { AiGenerate, Password } from "@carbon/icons-react";

import { CARBON_MODAL_SIZE_XSMALL, CARBON_MODAL_SIZE_SMALL, CARBON_MODAL_SIZE_LARGE } from "./../../src/common-properties/constants/constants";

const applyPropertyChanges = sinon.spy();
const closePropertiesDialog = sinon.spy();
const propertyIconHandler = sinon.spy();

const locale = "en";
const localMessages = {
Expand Down Expand Up @@ -127,7 +129,8 @@ propertiesInfo.messages = [

const callbacks = {
applyPropertyChanges: applyPropertyChanges,
closePropertiesDialog: closePropertiesDialog
closePropertiesDialog: closePropertiesDialog,
propertyIconHandler: propertyIconHandler
};

describe("CommonProperties renders correctly", () => {
Expand Down Expand Up @@ -289,6 +292,63 @@ describe("CommonProperties works correctly in flyout", () => {
expect(updatedResizeBtn.props()).to.have.property("aria-label", "Contract");
});

it("When iconSwitch=false no icon should be rendered in oneofselect dropdown menu", () => {
const renderedObject = propertyUtils.flyoutEditorForm(oneofselectParamDef); // default is iconSwitch=false
wrapper = renderedObject.wrapper;
const customIcon = wrapper.find("svg.custom-icon");
expect(customIcon.exists()).to.be.false;
});

it("When iconSwitch=true icon should be rendered in oneofselect dropdown menu", () => {
const renderedObject = propertyUtils.flyoutEditorForm(oneofselectParamDef, { iconSwitch: true },
{
propertyIconHandler: (data, callbackIcon) => {
callbackIcon(<Password className="custom-icon" />);
}
});
wrapper = renderedObject.wrapper;
wrapper.update();
const customIcon = wrapper.find("svg.custom-icon");
expect(customIcon.exists()).to.be.true;
});

it("Should render the correct icon for a specific propertyId", () => {
const renderedObject = propertyUtils.flyoutEditorForm(oneofselectParamDef, { iconSwitch: true }, {
propertyIconHandler: (data, callbackIcon) => {
if (data.propertyId.name === "oneofselect") {
callbackIcon(<Password className="custom-icon-1" />);
} else if (data.propertyId === "") {
callbackIcon(<AiGenerate className="custom-icon-2" />);
}
}
});
wrapper = renderedObject.wrapper;
wrapper.update();
const customIcon1 = wrapper.find("svg.custom-icon-1");
expect(customIcon1.exists()).to.be.true;
const customIcon2 = wrapper.find("svg.custom-icon-2");
expect(customIcon2.exists()).to.be.false;
});

it("Should render the correct icon for a specific enum value", () => {
const renderedObject = propertyUtils.flyoutEditorForm(oneofselectParamDef, { iconSwitch: true }, {
propertyIconHandler: (data, callbackIcon) => {
if (data.enumValue === "blue") {
callbackIcon(<Password className="custom-icon-1" />);
} else if (data.propertyId === "") {
callbackIcon(<AiGenerate className="custom-icon-2" />);
}
}
});
wrapper = renderedObject.wrapper;
wrapper.update();
const customIcon1 = wrapper.find("svg.custom-icon-1");
expect(customIcon1.exists()).to.be.true;
const customIcon2 = wrapper.find("svg.custom-icon-2");
expect(customIcon2.exists()).to.be.false;
});


it("When enableResize=true resize button should be rendered", () => {
const renderedObject = propertyUtils.flyoutEditorForm(propertiesInfo.parameterDef, { enableResize: true });
wrapper = renderedObject.wrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ CommonProperties.propTypes = {
validationHandler: PropTypes.func,
titleChangeHandler: PropTypes.func,
propertiesActionLabelHandler: PropTypes.func,
tooltipLinkHandler: PropTypes.func
tooltipLinkHandler: PropTypes.func,
propertyIconHandler: PropTypes.func
}),
customPanels: PropTypes.array,
customControls: PropTypes.array,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class DropDown extends React.Component {
this.genSelectOptions = this.genSelectOptions.bind(this);
this.genFieldSelectOptions = this.genFieldSelectOptions.bind(this);
this.updateValueFromFilterEnum = this.updateValueFromFilterEnum.bind(this);
this.getItemIcon = this.getItemIcon.bind(this);
this.renderItem = this.renderItem.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -87,6 +89,20 @@ class DropDown extends React.Component {
return selectedOption;
}

getItemIcon(enumValue) {
const propertyIconHandler = this.props.controller.getHandlers().propertyIconHandler;
let callbackIcon;
if (propertyIconHandler) {
propertyIconHandler({
propertyId: this.props.propertyId,
enumValue: enumValue
}, (appIcon) => {
callbackIcon = appIcon;
});
}
return callbackIcon;
}

genSchemaSelectOptions(selectedValue) {
const options = [];
// allow for user to not select a schema
Expand Down Expand Up @@ -196,6 +212,15 @@ class DropDown extends React.Component {
return list?.item?.label?.toLowerCase().includes(list?.inputValue?.toLowerCase());
}

renderItem(item) {
return item ? (
<div className="properties-dropdown-label">
<div className="custom-icon-label">{ item.label }</div>
{ this.getItemIcon(item.value) }
</div>
) : "";
}

render() {
let dropDown;
if (this.props.control.controlType === ControlType.SELECTSCHEMA) {
Expand Down Expand Up @@ -265,6 +290,8 @@ class DropDown extends React.Component {
disabled={this.props.state === STATES.DISABLED || this.disableEmptyListDropdown}
type="default"
items={dropDown.options}
itemToElement={this.renderItem}
renderSelectedItem={this.renderItem}
onChange={this.handleChange}
selectedItem={dropDown.selectedOption}
label={this.emptyLabel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/

.properties-dropdown {
.cds--list-box__label {
width: 100%
}
.cds--list-box--expanded .cds--list-box__menu {
margin-bottom: 1px; // Show dropdown menu border above Save/Cancel buttons
}
Expand All @@ -23,8 +26,10 @@
}

}

.properties-table-cell-control {
.properties-dropdown {
width: 100;
.cds--select.cds--select--inline {
.cds--select-input--inline__wrapper {
width: 100%;
Expand Down Expand Up @@ -55,3 +60,8 @@
}
}
}

.properties-dropdown-label {
display: flex;
justify-content: space-between;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default class PropertiesController {
buttonHandler: null,
buttonIconHandler: null,
titleChangeHandler: null,
tooltipLinkHandler: null
tooltipLinkHandler: null,
propertyIconHandler: null
};
this.propertiesConfig = {};
this.visibleDefinitions = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class PropertiesMain extends React.Component {
buttonIconHandler: props.callbacks.buttonIconHandler,
validationHandler: props.callbacks.validationHandler,
titleChangeHandler: props.callbacks.titleChangeHandler,
tooltipLinkHandler: props.callbacks.tooltipLinkHandler
tooltipLinkHandler: props.callbacks.tooltipLinkHandler,
propertyIconHandler: props.callbacks.propertyIconHandler,
});
this.setForm(props.propertiesInfo, false);
this.previousErrorMessages = {};
Expand Down Expand Up @@ -650,7 +651,8 @@ PropertiesMain.propTypes = {
validationHandler: PropTypes.func,
titleChangeHandler: PropTypes.func,
propertiesActionLabelHandler: PropTypes.func,
tooltipLinkHandler: PropTypes.func
tooltipLinkHandler: PropTypes.func,
propertyIconHandler: PropTypes.func,
}),
customPanels: PropTypes.array, // array of custom panels
customControls: PropTypes.array, // array of custom controls
Expand Down
19 changes: 17 additions & 2 deletions canvas_modules/harness/src/client/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { FormattedMessage, IntlProvider } from "react-intl";
import { forIn, get, has, isEmpty, isEqual } from "lodash";
import classNames from "classnames";
import { v4 as uuid4 } from "uuid";
import { Password } from "@carbon/icons-react";

import { jsPDF } from "jspdf";
import * as htmlToImage from "html-to-image";
Expand Down Expand Up @@ -283,6 +284,7 @@ class App extends React.Component {
showRequiredIndicator: true,
showAlertsTab: true,
enableResize: true,
iconSwitch: false,
initialEditorSize: "small",
conditionHiddenPropertyHandling: "null",
conditionDisabledPropertyHandling: "null",
Expand Down Expand Up @@ -422,6 +424,7 @@ class App extends React.Component {
this.applyPropertyChanges = this.applyPropertyChanges.bind(this);
this.buttonHandler = this.buttonHandler.bind(this);
this.buttonIconHandler = this.buttonIconHandler.bind(this);
this.propertyIconHandler = this.propertyIconHandler.bind(this);
this.validationHandler = this.validationHandler.bind(this);
this.titleChangeHandler = this.titleChangeHandler.bind(this);
this.propertyListener = this.propertyListener.bind(this);
Expand Down Expand Up @@ -1401,6 +1404,15 @@ class App extends React.Component {
}
}

propertyIconHandler(data, callbackIcon) {
const { iconSwitch } = this.state;
if (iconSwitch === true && data.propertyId.name === "oneofselect" && data.enumValue === "orange") {
callbackIcon(<Password />);
} else {
callbackIcon(null);
}
}

validationHandler(controller, propertyId, value, appData, callback) {
const response = {
type: "error",
Expand Down Expand Up @@ -1971,7 +1983,8 @@ class App extends React.Component {
buttonIconHandler: this.buttonIconHandler,
titleChangeHandler: this.titleChangeHandler,
propertiesActionLabelHandler: this.propertiesActionLabelHandler,
tooltipLinkHandler: this.tooltipLinkHandler
tooltipLinkHandler: this.tooltipLinkHandler,
propertyIconHandler: this.propertyIconHandler
};
if (this.state.propertiesValidationHandler) {
callbacks.validationHandler = this.validationHandler;
Expand Down Expand Up @@ -2057,7 +2070,8 @@ class App extends React.Component {
returnValueFiltering: returnValueFilters,
maxLengthForMultiLineControls: this.state.maxLengthForMultiLineControls,
maxLengthForSingleLineControls: this.state.maxLengthForSingleLineControls,
locale: this.locale
locale: this.locale,
iconSwitch: this.state.iconSwitch
};
}

Expand Down Expand Up @@ -2814,6 +2828,7 @@ class App extends React.Component {
categoryView: this.state.categoryView,
applyOnBlur: this.state.applyOnBlur,
trimSpaces: this.state.trimSpaces,
iconSwitch: this.state.iconSwitch,
disableSaveOnRequiredErrors: this.state.disableSaveOnRequiredErrors,
expressionBuilder: this.state.expressionBuilder,
displayAdditionalComponents: this.state.displayAdditionalComponents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ export default class FlowsProperties extends React.Component {
returnValueFiltering: [],
maxLengthForMultiLineControls: 1024,
maxLengthForSingleLineControls: 128,
locale: "en"
locale: "en",
iconSwitch: false
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"showRequiredIndicator": true,
"showAlertsTab": true,
"enableResize": true,
"iconSwitch": false,
"initialEditorSize": "small",
"conditionHiddenPropertyHandling": "null",
"conditionDisabledPropertyHandling": "null",
Expand Down Expand Up @@ -124,6 +125,11 @@
"default": true,
"type": "boolean"
},
{
"id": "iconSwitch",
"default": false,
"type": "boolean"
},
{
"id": "initialEditorSize",
"enum": [
Expand Down Expand Up @@ -374,6 +380,16 @@
},
"control": "toggle"
},
{
"parameter_ref": "iconSwitch",
"label": {
"default": "iconSwitch"
},
"description": {
"default": "Allows for icon to be displayed or hidden"
},
"control": "toggle"
},
{
"parameter_ref": "initialEditorSize",
"label": {
Expand Down Expand Up @@ -700,6 +716,7 @@
"showRequiredIndicator",
"showAlertsTab",
"enableResize",
"iconSwitch",
"conditionHiddenPropertyHandling",
"conditionDisabledPropertyHandling",
"returnValueFiltering",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default class SidePanelProperties extends React.Component {
"propertiesContainerType": this.props.propertiesConfig.propertiesContainerType,
"categoryView": this.props.propertiesConfig.categoryView,
"propertiesSchemaValidation": this.props.propertiesConfig.propertiesSchemaValidation,
"iconSwitch": this.props.propertiesConfig.iconSwitch,
"applyPropertiesWithoutEdit": this.props.propertiesConfig.applyPropertiesWithoutEdit,
"applyOnBlur": this.props.propertiesConfig.applyOnBlur,
"convertValueDataTypes": this.props.propertiesConfig.convertValueDataTypes,
Expand Down Expand Up @@ -367,6 +368,7 @@ SidePanelProperties.propTypes = {
disableWideFlyoutPrimaryButtonForPanelId: PropTypes.string,
disableWideFlyoutPrimaryButton: PropTypes.func,
setWideFlyoutPrimaryButtonDisabled: PropTypes.func, // action
convertValueDataTypes: PropTypes.bool
convertValueDataTypes: PropTypes.bool,
iconSwitch: PropTypes.bool
})
};
16 changes: 16 additions & 0 deletions docs/pages/04.02-callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ buttonIconHandler(data, callbackIcon) {
}
```

### propertyIconHandler()
Called when a user wants to pass in a specific object to a dropdown menu. The propertyIconHandler expects a jsx object as the return value from the callback. This is used to display the jsx object in the dropdown menu. The propertyIconHandler provides the following data:

-data: an object that consists of
- propertyId: of the dropdown that was selected
- enumValue: of the dropdown that was selected

```js
propertyIconHandler(data, callbackIcon) {
const { iconSwitch } = this.state;
if (iconSwitch === true && data.propertyId.name === "oneofselect" && data.enumValue === "red") {
callbackIcon(<Icon />);
}
}
```

### helpClickHandler()
Executes when user clicks the help icon in the property editor dialog. The callback provides the following data:

Expand Down

0 comments on commit 196215b

Please sign in to comment.