Feature/dropdown

This commit is contained in:
Nikhil Nandagopal 2019-10-31 05:28:11 +00:00
parent 3b393a8089
commit 9d659ddff0
23 changed files with 496 additions and 227 deletions

View File

@ -25,6 +25,8 @@ export interface ComponentProps {
widgetId: string; widgetId: string;
widgetName?: string; widgetName?: string;
style: BaseStyle; style: BaseStyle;
isDisabled?: boolean;
isVisibile?: boolean;
} }
export default BaseComponent; export default BaseComponent;

View File

@ -1,33 +0,0 @@
import * as React from "react";
import { Text } from "@blueprintjs/core";
import styled from "styled-components";
import { ComponentProps } from "./BaseComponent";
import { Container } from "./ContainerComponent";
type TextStyleProps = {
styleName: "primary" | "secondary" | "error";
};
export const BaseText = styled(Text)<TextStyleProps>`
color: ${props => props.theme.colors[props.styleName]};
`;
export interface TextComponentProps extends ComponentProps {
text?: string;
ellipsize?: boolean;
tagName?: keyof JSX.IntrinsicElements;
}
class TextComponent extends React.Component<TextComponentProps> {
render() {
return (
<Container {...this.props}>
<Text ellipsize={this.props.ellipsize} tagName={this.props.tagName}>
{this.props.text}
</Text>
</Container>
);
}
}
export default TextComponent;

View File

@ -1,29 +0,0 @@
import * as React from "react";
import { ComponentProps } from "../appsmith/BaseComponent";
import { Boundary, Breadcrumbs, IBreadcrumbProps } from "@blueprintjs/core";
import { Container } from "../appsmith/ContainerComponent";
class BreadcrumbsComponent extends React.Component<BreadcrumbsComponentProps> {
render() {
return (
<Container {...this.props}>
<Breadcrumbs
collapseFrom={this.props.collapseFrom}
items={this.props.items}
minVisibleItems={this.props.minVisibleItems}
className={this.props.className}
/>
</Container>
);
}
}
export interface BreadcrumbsComponentProps extends ComponentProps {
width?: number;
collapseFrom?: Boundary;
className?: string;
minVisibleItems?: number;
items?: IBreadcrumbProps[];
}
export default BreadcrumbsComponent;

View File

@ -2,7 +2,8 @@ import React from "react";
import { AnchorButton, IButtonProps, MaybeElement } from "@blueprintjs/core"; import { AnchorButton, IButtonProps, MaybeElement } from "@blueprintjs/core";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { Container } from "../appsmith/ContainerComponent"; import { Container } from "../appsmith/ContainerComponent";
import { TextComponentProps } from "../appsmith/TextViewComponent"; import { TextComponentProps } from "./TextComponent";
import { ButtonStyle } from "../../widgets/ButtonWidget";
const ButtonColorStyles = css<ButtonStyleProps>` const ButtonColorStyles = css<ButtonStyleProps>`
color: ${props => { color: ${props => {
@ -87,8 +88,22 @@ interface ButtonContainerProps extends TextComponentProps {
icon?: MaybeElement; icon?: MaybeElement;
onClick?: (event: React.MouseEvent<HTMLElement>) => void; onClick?: (event: React.MouseEvent<HTMLElement>) => void;
disabled?: boolean; disabled?: boolean;
buttonStyle?: ButtonStyle;
} }
const mapButtonStyleToStyleName = (buttonStyle?: ButtonStyle) => {
switch (buttonStyle) {
case "PRIMARY_BUTTON":
return "primary";
case "SECONDARY_BUTTON":
return "secondary";
case "DANGER_BUTTON":
return "error";
default:
return undefined;
}
};
// To be used with the canvas // To be used with the canvas
const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => { const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => {
return ( return (
@ -96,7 +111,8 @@ const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => {
<BaseButton <BaseButton
icon={props.icon} icon={props.icon}
text={props.text} text={props.text}
styleName={props.styleName} filled={props.buttonStyle === "PRIMARY_BUTTON"}
styleName={mapButtonStyleToStyleName(props.buttonStyle)}
onClick={props.onClick} onClick={props.onClick}
disabled={props.disabled} disabled={props.disabled}
/> />

View File

@ -0,0 +1,125 @@
import * as React from "react";
import { ComponentProps } from "../appsmith/BaseComponent";
import { MenuItem, Button } from "@blueprintjs/core";
import { Container } from "../appsmith/ContainerComponent";
import { SelectionType, DropdownOption } from "../../widgets/DropdownWidget";
import {
Select,
MultiSelect,
IItemRendererProps,
ItemRenderer,
} from "@blueprintjs/select";
import _ from "lodash";
const SingleDropDown = Select.ofType<DropdownOption>();
const MultiDropDown = MultiSelect.ofType<DropdownOption>();
class DropDownComponent extends React.Component<DropDownComponentProps> {
constructor(props: DropDownComponentProps) {
super(props);
}
render() {
const selectedItems = this.props.selectedIndexArr
? _.map(this.props.selectedIndexArr, index => {
return this.props.options[index];
})
: [];
return (
<div style={{ textAlign: "center" }}>
{this.props.selectionType === "SINGLE_SELECT" ? (
<SingleDropDown
items={this.props.options}
filterable={false}
itemRenderer={this.renderItem}
onItemSelect={this.onItemSelect}
>
<Container {...this.props}>
<Button
intent={"primary"}
text={
!_.isEmpty(this.props.options)
? this.props.options[this.props.selectedIndex].label
: "Add options"
}
/>
</Container>
</SingleDropDown>
) : (
<Container {...this.props}>
<MultiDropDown
items={this.props.options}
placeholder={this.props.placeholder}
tagRenderer={this.renderTag}
itemRenderer={this.renderItem}
selectedItems={selectedItems}
tagInputProps={{ onRemove: this.onItemRemoved }}
onItemSelect={this.onItemSelect}
></MultiDropDown>
</Container>
)}
</div>
);
}
onItemSelect = (
item: DropdownOption,
event?: React.SyntheticEvent<HTMLElement>,
): void => {
this.props.onOptionSelected(item);
};
onItemRemoved = (_tag: string, index: number) => {
this.props.onOptionRemoved(index);
};
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
);
}
};
renderItem = (option: DropdownOption, itemProps: IItemRendererProps) => {
if (!itemProps.modifiers.matchesPredicate) {
return null;
}
const isSelected: boolean = this.isOptionSelected(option);
console.log("is selected " + isSelected);
return (
<MenuItem
icon={isSelected ? "tick" : "blank"}
active={itemProps.modifiers.active}
key={option.value}
onClick={itemProps.handleClick}
text={option.label}
/>
);
};
}
export interface DropDownComponentProps extends ComponentProps {
selectionType: SelectionType;
disabled?: boolean;
onOptionSelected: (optionSelected: DropdownOption) => void;
onOptionRemoved: (removedIndex: number) => void;
placeholder?: string;
label?: string;
selectedIndex: number;
selectedIndexArr: number[];
options: DropdownOption[];
}
export default DropDownComponent;

View File

@ -6,6 +6,9 @@ import {
IconName, IconName,
InputGroup, InputGroup,
Button, Button,
Label,
Classes,
Text,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import { Container } from "../appsmith/ContainerComponent"; import { Container } from "../appsmith/ContainerComponent";
import { InputType } from "../../widgets/InputWidget"; import { InputType } from "../../widgets/InputWidget";
@ -25,20 +28,64 @@ class InputComponent extends React.Component<
this.state = { showPassword: false }; this.state = { showPassword: false };
} }
onTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.props.onValueChange(event.target.value);
};
onNumberChange = (valueAsNum: number, valueAsString: string) => {
this.props.onValueChange(valueAsString);
};
isNumberInputType(inputType: InputType) {
return (
this.props.inputType === "INTEGER" ||
this.props.inputType === "PHONE_NUMBER" ||
this.props.inputType === "NUMBER" ||
this.props.inputType === "CURRENCY"
);
}
getIcon(inputType: InputType) {
switch (inputType) {
case "PHONE_NUMBER":
return "phone";
case "SEARCH":
return "search";
case "EMAIL":
return "envelope";
default:
return undefined;
}
}
getType(inputType: InputType) {
switch (inputType) {
case "PASSWORD":
return this.state.showPassword ? "password" : "text";
case "EMAIL":
return "email";
case "SEARCH":
return "search";
default:
return "text";
}
}
render() { render() {
return ( return (
<Container {...this.props}> <Container {...this.props}>
{this.props.inputType === "INTEGER" || <Label>
this.props.inputType === "PHONE_NUMBER" || {this.props.label}
this.props.inputType === "NUMBER" || {this.isNumberInputType(this.props.inputType) ? (
this.props.inputType === "CURRENCY" ? (
<NumericInput <NumericInput
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
min={this.props.minNum} min={this.props.minNum}
max={this.props.maxNum} max={this.props.maxNum}
maxLength={this.props.maxChars}
disabled={this.props.disabled} disabled={this.props.disabled}
intent={this.props.intent} intent={this.props.intent}
defaultValue={this.props.defaultValue} defaultValue={this.props.defaultValue}
onValueChange={this.onNumberChange}
leftIcon={ leftIcon={
this.props.inputType === "PHONE_NUMBER" this.props.inputType === "PHONE_NUMBER"
? "phone" ? "phone"
@ -48,14 +95,13 @@ class InputComponent extends React.Component<
allowNumericCharactersOnly={true} allowNumericCharactersOnly={true}
stepSize={this.props.stepSize} stepSize={this.props.stepSize}
/> />
) : this.props.inputType === "TEXT" || ) : (
this.props.inputType === "EMAIL" ||
this.props.inputType === "PASSWORD" ||
this.props.inputType === "SEARCH" ? (
<InputGroup <InputGroup
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
disabled={this.props.disabled} disabled={this.props.disabled}
maxLength={this.props.maxChars}
intent={this.props.intent} intent={this.props.intent}
onChange={this.onTextChange}
defaultValue={this.props.defaultValue} defaultValue={this.props.defaultValue}
rightElement={ rightElement={
this.props.inputType === "PASSWORD" ? ( this.props.inputType === "PASSWORD" ? (
@ -69,26 +115,12 @@ class InputComponent extends React.Component<
undefined undefined
) )
} }
type={ type={this.getType(this.props.inputType)}
this.props.inputType === "PASSWORD" && !this.state.showPassword leftIcon={this.getIcon(this.props.inputType)}
? "password"
: this.props.inputType === "EMAIL"
? "email"
: this.props.inputType === "SEARCH"
? "search"
: "text"
}
leftIcon={
this.props.inputType === "SEARCH"
? "search"
: this.props.inputType === "EMAIL"
? "envelope"
: this.props.leftIcon
}
/> />
) : (
undefined
)} )}
</Label>
<Text>{this.props.errorMessage}</Text>
</Container> </Container>
); );
} }
@ -99,16 +131,19 @@ export interface InputComponentState {
} }
export interface InputComponentProps extends ComponentProps { export interface InputComponentProps extends ComponentProps {
inputType?: InputType; inputType: InputType;
disabled?: boolean; disabled?: boolean;
intent?: Intent; intent?: Intent;
defaultValue?: string; defaultValue?: string;
label: string;
leftIcon?: IconName; leftIcon?: IconName;
allowNumericCharactersOnly?: boolean; allowNumericCharactersOnly?: boolean;
fill?: boolean; fill?: boolean;
errorMessage?: string;
maxChars?: number;
maxNum?: number; maxNum?: number;
minNum?: number; minNum?: number;
onValueChange?: (valueAsNumber: number, valueAsString: string) => void; onValueChange: (valueAsString: string) => void;
stepSize?: number; stepSize?: number;
placeholder?: string; placeholder?: string;
} }

View File

@ -1,35 +0,0 @@
import * as React from "react";
import { ComponentProps } from "../appsmith/BaseComponent";
import { Intent, ITagProps, TagInput, HTMLInputProps } from "@blueprintjs/core";
import { Container } from "../appsmith/ContainerComponent";
class TagInputComponent extends React.Component<TagInputComponentProps> {
render() {
return (
<Container {...this.props}>
<TagInput
placeholder={this.props.placeholder}
values={this.props.values}
/>
</Container>
);
}
}
export interface TagInputComponentProps extends ComponentProps {
addOnPaste?: boolean;
className?: string;
disabled?: boolean;
fill?: boolean;
inputProps?: HTMLInputProps;
inputValue?: string; //Controlled value of the <input> element.
intent?: Intent;
large?: boolean; //Whether the tag input should use a large size
onInputChange?: React.FormEventHandler<HTMLInputElement>;
placeholder?: string;
rightElement?: JSX.Element;
separator?: string | RegExp | false;
tagProps?: ITagProps;
values?: string[]; //Required field
}
export default TagInputComponent;

View File

@ -0,0 +1,48 @@
import * as React from "react";
import { Text, Classes } from "@blueprintjs/core";
import styled from "styled-components";
import { ComponentProps } from "../appsmith/BaseComponent";
import { Container } from "../appsmith/ContainerComponent";
import { TextStyle } from "../../widgets/TextWidget";
type TextStyleProps = {
styleName: "primary" | "secondary" | "error";
};
export const BaseText = styled(Text)<TextStyleProps>``;
export interface TextComponentProps extends ComponentProps {
text?: string;
ellipsize?: boolean;
textStyle?: TextStyle;
}
class TextComponent extends React.Component<TextComponentProps> {
getTextClass(textStyle?: TextStyle) {
switch (textStyle) {
case "HEADING":
return Classes.TEXT_LARGE;
case "LABEL":
return undefined;
case "BODY":
return Classes.TEXT_SMALL;
default:
return undefined;
}
}
render() {
return (
<Container {...this.props}>
<Text
className={this.getTextClass(this.props.textStyle)}
ellipsize={this.props.ellipsize}
>
{this.props.text}
</Text>
</Container>
);
}
}
export default TextComponent;

View File

@ -2,7 +2,7 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { withRouter, RouteComponentProps } from "react-router"; import { withRouter, RouteComponentProps } from "react-router";
import FormRow from "./FormRow"; import FormRow from "./FormRow";
import { BaseText } from "../appsmith/TextViewComponent"; import { BaseText } from "../blueprint/TextComponent";
import { BaseTabbedView } from "../appsmith/TabbedView"; import { BaseTabbedView } from "../appsmith/TabbedView";
import styled from "styled-components"; import styled from "styled-components";
import { AppState } from "../../reducers"; import { AppState } from "../../reducers";

View File

@ -187,7 +187,7 @@ const DraggableComponent = (props: DraggableComponentProps) => {
</Tooltip> </Tooltip>
</DeleteControl> </DeleteControl>
<EditControl className="control" onClick={togglePropertyEditor}> <EditControl className="control" onClick={togglePropertyEditor}>
<Tooltip content="Toggle properties pane" hoverOpenDelay={500}> <Tooltip content="Toggle props" hoverOpenDelay={500}>
{editControlIcon} {editControlIcon}
</Tooltip> </Tooltip>
</EditControl> </EditControl>

View File

@ -12,7 +12,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
propertyName: "text", propertyName: "text",
label: "Button Text", label: "Button Text",
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
placeholderText: "Enter button text here", placeholderText: "Enter button text",
}, },
{ {
id: "1.2", id: "1.2",
@ -22,6 +22,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
options: [ options: [
{ label: "Primary Button", value: "PRIMARY_BUTTON" }, { label: "Primary Button", value: "PRIMARY_BUTTON" },
{ label: "Secondary Button", value: "SECONDARY_BUTTON" }, { label: "Secondary Button", value: "SECONDARY_BUTTON" },
{ label: "Danger Button", value: "DANGER_BUTTON" },
], ],
}, },
{ {
@ -61,7 +62,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
propertyName: "text", propertyName: "text",
label: "Text", label: "Text",
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
placeholderText: "Enter your text here", placeholderText: "Enter your text",
}, },
{ {
id: "3.2", id: "3.2",
@ -72,7 +73,6 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
{ label: "Heading", value: "HEADING" }, { label: "Heading", value: "HEADING" },
{ label: "Label", value: "LABEL" }, { label: "Label", value: "LABEL" },
{ label: "Body", value: "BODY" }, { label: "Body", value: "BODY" },
{ label: "Sub text", value: "SUB_TEXT" },
], ],
}, },
{ {
@ -140,7 +140,6 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
{ label: "Password", value: "PASSWORD" }, { label: "Password", value: "PASSWORD" },
{ label: "Phone Number", value: "PHONE_NUMBER" }, { label: "Phone Number", value: "PHONE_NUMBER" },
{ label: "Email", value: "EMAIL" }, { label: "Email", value: "EMAIL" },
{ label: "Search", value: "SEARCH" },
], ],
}, },
{ {
@ -148,7 +147,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
propertyName: "placeholderText", propertyName: "placeholderText",
label: "Placeholder", label: "Placeholder",
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
placeholderText: "Enter your text here", placeholderText: "Enter your text",
}, },
{ {
id: "5.4", id: "5.4",
@ -156,41 +155,32 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
label: "Max Chars", label: "Max Chars",
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
inputType: "INTEGER", inputType: "INTEGER",
placeholderText: "Maximum character length", placeholderText: "Enter the max length",
}, },
{ {
id: "5.5", id: "5.5",
propertyName: "validators", propertyName: "regex",
label: "Validators", label: "Regex",
controlType: "VALIDATION_INPUT", controlType: "INPUT_TEXT",
inputType: "TEXT",
placeholderText: "Enter the regex",
}, },
{ {
id: "5.6", id: "5.6",
children: [ propertyName: "errorMessage",
{ label: "Error Message",
id: "5.6.1",
propertyName: "focusIndexx",
label: "Focus Index",
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
inputType: "INTEGER", inputType: "TEXT",
placeholderText: "Enter the order of tab focus", placeholderText: "Enter the message",
}, },
{ {
id: "5.6.2", id: "5.8",
propertyName: "autoFocus",
label: "Auto Focus",
controlType: "SWITCH",
},
],
},
{
id: "5.7",
propertyName: "isVisible", propertyName: "isVisible",
label: "Visibile", label: "Visibile",
controlType: "SWITCH", controlType: "SWITCH",
}, },
{ {
id: "5.8", id: "5.9",
propertyName: "isDisabled", propertyName: "isDisabled",
label: "Disabled", label: "Disabled",
controlType: "SWITCH", controlType: "SWITCH",
@ -407,7 +397,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
children: [ children: [
{ {
id: "13.1", id: "13.1",
propertyName: "type", propertyName: "selectionType",
label: "Selection Type", label: "Selection Type",
controlType: "DROP_DOWN", controlType: "DROP_DOWN",
options: [ options: [
@ -415,6 +405,12 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
{ label: "Multi Select", value: "MULTI_SELECT" }, { label: "Multi Select", value: "MULTI_SELECT" },
], ],
}, },
{
id: "13.4",
propertyName: "options",
label: "Options",
controlType: "INPUT_TEXT",
},
{ {
id: "13.2", id: "13.2",
propertyName: "label", propertyName: "label",
@ -429,12 +425,6 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
placeholderText: "Enter the placeholder", placeholderText: "Enter the placeholder",
}, },
{
id: "13.4",
propertyName: "options",
label: "Options",
controlType: "OPTION_INPUT",
},
{ {
id: "13.5", id: "13.5",
propertyName: "isVisible", propertyName: "isVisible",

View File

@ -12,10 +12,10 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isVisible: true, isVisible: true,
}, },
TEXT_WIDGET: { TEXT_WIDGET: {
text: "Not all labels are bad!", text: "Label me",
textStyle: "LABEL", textStyle: "LABEL",
rows: 1, rows: 1,
columns: 3, columns: 4,
widgetName: "Text", widgetName: "Text",
}, },
IMAGE_WIDGET: { IMAGE_WIDGET: {
@ -30,20 +30,20 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
inputType: "TEXT", inputType: "TEXT",
label: "Label me", label: "Label me",
rows: 1, rows: 1,
columns: 3, columns: 5,
widgetName: "Input", widgetName: "Input",
}, },
SWITCH_WIDGET: { SWITCH_WIDGET: {
isOn: false, isOn: false,
label: "Turn me on", label: "Switch",
rows: 1, rows: 1,
columns: 4, columns: 4,
widgetName: "Switch", widgetName: "Switch",
}, },
CONTAINER_WIDGET: { CONTAINER_WIDGET: {
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
rows: 1, rows: 8,
columns: 4, columns: 8,
widgetName: "Container", widgetName: "Container",
}, },
SPINNER_WIDGET: { SPINNER_WIDGET: {
@ -62,14 +62,19 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
TABLE_WIDGET: { TABLE_WIDGET: {
rows: 5, rows: 5,
columns: 7, columns: 7,
label: "Don't table me!", label: "Data",
widgetName: "Table", widgetName: "Table",
}, },
DROP_DOWN_WIDGET: { DROP_DOWN_WIDGET: {
rows: 1, rows: 1,
columns: 3, columns: 3,
selectionType: "SINGLE_SELECT", selectionType: "SINGLE_SELECT",
label: "Pick me!", label: "Select",
options: [
{ label: "Option 1", value: "1" },
{ label: "Option 2", value: "2" },
{ label: "Option 3", value: "3" },
],
widgetName: "Dropdown", widgetName: "Dropdown",
}, },
CHECKBOX_WIDGET: { CHECKBOX_WIDGET: {
@ -82,7 +87,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
RADIO_GROUP_WIDGET: { RADIO_GROUP_WIDGET: {
rows: 3, rows: 3,
columns: 3, columns: 3,
label: "Alpha - come in!", label: "Labels",
options: [ options: [
{ label: "Alpha", value: "1" }, { label: "Alpha", value: "1" },
{ label: "Bravo", value: "2" }, { label: "Bravo", value: "2" },

View File

@ -27,6 +27,7 @@ import {
fetchEditorConfigs, fetchEditorConfigs,
} from "../../actions/configsActions"; } from "../../actions/configsActions";
import { ReduxActionTypes } from "../../constants/ReduxActionConstants"; import { ReduxActionTypes } from "../../constants/ReduxActionConstants";
import { updateWidgetProperty } from "../../actions/controlActions";
const EditorWrapper = styled.div` const EditorWrapper = styled.div`
display: flex; display: flex;
@ -59,6 +60,11 @@ type EditorProps = {
fetchCanvasWidgets: Function; fetchCanvasWidgets: Function;
executeAction: (actionPayloads?: ActionPayload[]) => void; executeAction: (actionPayloads?: ActionPayload[]) => void;
updateWidget: Function; updateWidget: Function;
updateWidgetProperty: (
widgetId: string,
propertyName: string,
propertyValue: any,
) => void;
savePageLayout: Function; savePageLayout: Function;
currentPageName: string; currentPageName: string;
currentPageId: string; currentPageId: string;
@ -92,6 +98,7 @@ class WidgetsEditor extends React.Component<EditorProps> {
value={{ value={{
executeAction: this.props.executeAction, executeAction: this.props.executeAction,
updateWidget: this.props.updateWidget, updateWidget: this.props.updateWidget,
updateWidgetProperty: this.props.updateWidgetProperty,
}} }}
> >
<EditorWrapper> <EditorWrapper>
@ -123,6 +130,11 @@ const mapStateToProps = (state: AppState) => {
const mapDispatchToProps = (dispatch: any) => { const mapDispatchToProps = (dispatch: any) => {
return { return {
updateWidgetProperty: (
widgetId: string,
propertyName: string,
propertyValue: any,
) => dispatch(updateWidgetProperty(widgetId, propertyName, propertyValue)),
executeAction: (actionPayloads?: ActionPayload[]) => executeAction: (actionPayloads?: ActionPayload[]) =>
dispatch(executeAction(actionPayloads)), dispatch(executeAction(actionPayloads)),
fetchCanvasWidgets: (pageId: string) => fetchCanvasWidgets: (pageId: string) =>

View File

@ -8,7 +8,6 @@ import { ControlWrapper, StyledDropDown } from "./StyledControls";
class DropDownControl extends BaseControl<DropDownControlProps> { class DropDownControl extends BaseControl<DropDownControlProps> {
constructor(props: DropDownControlProps) { constructor(props: DropDownControlProps) {
super(props); super(props);
this.onItemSelect = this.onItemSelect.bind(this);
} }
render() { render() {
@ -21,6 +20,7 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
<StyledDropDown <StyledDropDown
items={this.props.options} items={this.props.options}
itemPredicate={this.filterOption} itemPredicate={this.filterOption}
filterable={false}
itemRenderer={this.renderItem} itemRenderer={this.renderItem}
onItemSelect={this.onItemSelect} onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text="No results." />} noResults={<MenuItem disabled={true} text="No results." />}
@ -34,14 +34,14 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
); );
} }
onItemSelect( onItemSelect = (
item: DropdownOption, item: DropdownOption,
event?: SyntheticEvent<HTMLElement>, event?: SyntheticEvent<HTMLElement>,
): void { ): void => {
this.updateProperty(this.props.propertyName, item.value); this.updateProperty(this.props.propertyName, item.value);
} };
renderItem(option: DropdownOption, itemProps: IItemRendererProps) { renderItem = (option: DropdownOption, itemProps: IItemRendererProps) => {
if (!itemProps.modifiers.matchesPredicate) { if (!itemProps.modifiers.matchesPredicate) {
return null; return null;
} }
@ -49,12 +49,11 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
<MenuItem <MenuItem
active={itemProps.modifiers.active} active={itemProps.modifiers.active}
key={option.value} key={option.value}
label={option.label}
onClick={itemProps.handleClick} onClick={itemProps.handleClick}
text={option.label} text={option.label}
/> />
); );
} };
filterOption(query: string, option: DropdownOption): boolean { filterOption(query: string, option: DropdownOption): boolean {
return ( return (

View File

@ -10,13 +10,27 @@ class InputTextControl extends BaseControl<InputControlProps> {
<ControlWrapper> <ControlWrapper>
<label>{this.props.label}</label> <label>{this.props.label}</label>
<StyledInputGroup <StyledInputGroup
type={this.isNumberType(this.props.inputType) ? "number" : "text"}
onChange={this.onTextChange} onChange={this.onTextChange}
placeholder={this.props.placeholderText}
defaultValue={this.props.propertyValue} defaultValue={this.props.propertyValue}
/> />
</ControlWrapper> </ControlWrapper>
); );
} }
isNumberType(inputType: InputType): boolean {
switch (inputType) {
case "CURRENCY":
case "INTEGER":
case "NUMBER":
case "PHONE_NUMBER":
return true;
default:
return false;
}
}
onTextChange = (event: React.ChangeEvent<HTMLInputElement>) => { onTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.updateProperty(this.props.propertyName, event.target.value); this.updateProperty(this.props.propertyName, event.target.value);
}; };

View File

@ -34,6 +34,7 @@ export const StyledSwitch = styled(Switch)`
export const StyledInputGroup = styled(InputGroup)` export const StyledInputGroup = styled(InputGroup)`
& > input { & > input {
placeholderText: ${props => props.placeholder}
color: ${props => props.theme.colors.textOnDarkBG}; color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG}; background: ${props => props.theme.colors.paneInputBG};
} }

View File

@ -43,12 +43,16 @@ export interface PropertyPaneConfigState {
config: PropertyConfig; config: PropertyConfig;
configVersion: number; configVersion: number;
} }
/**
* Todo: Remove hardcoding of config response
*/
const propertyPaneConfigReducer = createReducer(initialState, { const propertyPaneConfigReducer = createReducer(initialState, {
[ReduxActionTypes.FETCH_PROPERTY_PANE_CONFIGS_SUCCESS]: ( [ReduxActionTypes.FETCH_PROPERTY_PANE_CONFIGS_SUCCESS]: (
state: PropertyPaneConfigState, state: PropertyPaneConfigState,
action: ReduxAction<PropertyPaneConfigState>, action: ReduxAction<PropertyPaneConfigState>,
) => { ) => {
return { ...PropertyPaneConfigResponse };
return { ...action.payload }; return { ...action.payload };
}, },
}); });

View File

@ -12,6 +12,7 @@ import RadioGroupWidget, {
import WidgetFactory from "./WidgetFactory"; import WidgetFactory from "./WidgetFactory";
import React from "react"; import React from "react";
import ButtonWidget, { ButtonWidgetProps } from "../widgets/ButtonWidget"; import ButtonWidget, { ButtonWidgetProps } from "../widgets/ButtonWidget";
import DropdownWidget, { DropdownWidgetProps } from "../widgets/DropdownWidget";
class WidgetBuilderRegistry { class WidgetBuilderRegistry {
static registerWidgetBuilders() { static registerWidgetBuilders() {
@ -51,6 +52,12 @@ class WidgetBuilderRegistry {
}, },
}); });
WidgetFactory.registerWidgetBuilder("DROP_DOWN_WIDGET", {
buildWidget(widgetData: DropdownWidgetProps): JSX.Element {
return <DropdownWidget {...widgetData} />;
},
});
WidgetFactory.registerWidgetBuilder("RADIO_GROUP_WIDGET", { WidgetFactory.registerWidgetBuilder("RADIO_GROUP_WIDGET", {
buildWidget(widgetData: RadioGroupWidgetProps): JSX.Element { buildWidget(widgetData: RadioGroupWidgetProps): JSX.Element {
return <RadioGroupWidget {...widgetData} />; return <RadioGroupWidget {...widgetData} />;

View File

@ -173,6 +173,11 @@ export interface WidgetDataProps {
export interface WidgetFunctions { export interface WidgetFunctions {
executeAction?: (actionPayloads?: ActionPayload[]) => void; executeAction?: (actionPayloads?: ActionPayload[]) => void;
updateWidget?: Function; updateWidget?: Function;
updateWidgetProperty?: (
widgetId: string,
propertyName: string,
propertyValue: any,
) => void;
} }
export interface WidgetCardProps { export interface WidgetCardProps {

View File

@ -1,9 +1,7 @@
import React from "react"; import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "../constants/WidgetConstants"; import { WidgetType } from "../constants/WidgetConstants";
import ButtonComponent, { import ButtonComponent from "../components/blueprint/ButtonComponent";
ButtonStyleName,
} from "../components/blueprint/ButtonComponent";
import { ActionPayload } from "../constants/ActionConstants"; import { ActionPayload } from "../constants/ActionConstants";
class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> { class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
@ -19,14 +17,10 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
} }
getPageView() { getPageView() {
// TODO(abhinav): This is a hack. Need to standardize the style names
const translatedButtonStyleName: ButtonStyleName | undefined =
this.props.buttonStyle &&
(this.props.buttonStyle.split("_")[0].toLowerCase() as ButtonStyleName);
return ( return (
<ButtonComponent <ButtonComponent
style={this.getPositionStyle()} style={this.getPositionStyle()}
styleName={translatedButtonStyleName} buttonStyle={this.props.buttonStyle}
widgetId={this.props.widgetId} widgetId={this.props.widgetId}
widgetName={this.props.widgetName} widgetName={this.props.widgetName}
key={this.props.widgetId} key={this.props.widgetId}

View File

@ -2,12 +2,73 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "../constants/WidgetConstants"; import { WidgetType } from "../constants/WidgetConstants";
import { ActionPayload } from "../constants/ActionConstants"; import { ActionPayload } from "../constants/ActionConstants";
import DropDownComponent from "../components/blueprint/DropdownComponent";
import _ from "lodash";
class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> { class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
getPageView() { constructor(props: DropdownWidgetProps) {
return <div />; super(props);
} }
getPageView() {
return (
<DropDownComponent
onOptionSelected={this.onOptionSelected}
onOptionRemoved={this.onOptionRemoved}
widgetId={this.props.widgetId}
style={this.getPositionStyle()}
placeholder={this.props.placeholderText}
options={this.props.options || []}
selectionType={this.props.selectionType}
selectedIndex={this.props.selectedIndex || 0}
selectedIndexArr={this.props.selectedIndexArr || []}
label={this.props.label}
/>
);
}
onOptionSelected = (selectedOption: DropdownOption) => {
const selectedIndex = _.findIndex(this.props.options, option => {
return option.value === selectedOption.value;
});
if (this.props.selectionType === "SINGLE_SELECT") {
this.context.updateWidgetProperty(
this.props.widgetId,
"selectedIndex",
selectedIndex,
);
} else if (this.props.selectionType === "MULTI_SELECT") {
const selectedIndexArr = this.props.selectedIndexArr || [];
const isAlreadySelected =
_.find(selectedIndexArr, index => {
return index === selectedIndex;
}) !== undefined;
if (isAlreadySelected) {
this.onOptionRemoved(selectedIndex);
} else {
selectedIndexArr.push(selectedIndex);
this.context.updateWidgetProperty(
this.props.widgetId,
"selectedIndexArr",
selectedIndexArr,
);
}
}
};
onOptionRemoved = (removedIndex: number) => {
const updateIndexArr = this.props.selectedIndexArr
? this.props.selectedIndexArr.filter(index => {
return removedIndex !== index;
})
: [];
this.context.updateWidgetProperty(
this.props.widgetId,
"selectedIndexArr",
updateIndexArr,
);
};
getWidgetType(): WidgetType { getWidgetType(): WidgetType {
return "DROP_DOWN_WIDGET"; return "DROP_DOWN_WIDGET";
} }
@ -22,6 +83,8 @@ export interface DropdownOption {
export interface DropdownWidgetProps extends WidgetProps { export interface DropdownWidgetProps extends WidgetProps {
placeholderText?: string; placeholderText?: string;
label?: string; label?: string;
selectedIndex?: number;
selectedIndexArr?: number[];
selectionType: SelectionType; selectionType: SelectionType;
options?: DropdownOption[]; options?: DropdownOption[];
onOptionSelected?: ActionPayload[]; onOptionSelected?: ActionPayload[];

View File

@ -4,13 +4,55 @@ import { WidgetType } from "../constants/WidgetConstants";
import InputComponent from "../components/blueprint/InputComponent"; import InputComponent from "../components/blueprint/InputComponent";
class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> { class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
regex = new RegExp("");
constructor(props: InputWidgetProps) {
super(props);
}
componentDidMount() {
super.componentDidMount();
if (this.props.regex) {
try {
this.regex = new RegExp(this.props.regex);
} catch (e) {
console.log("invalid regex");
}
}
}
componentDidUpdate(prevProps: InputWidgetProps) {
super.componentDidUpdate(prevProps);
if (this.props.regex !== prevProps.regex && this.props.regex) {
try {
this.regex = new RegExp(this.props.regex);
} catch (e) {
console.log("invalid regex");
}
}
}
onValueChange = (value: string) => {
this.context.updateWidgetProperty(this.props.widgetId, "text", value);
};
getPageView() { getPageView() {
return ( return (
<InputComponent <InputComponent
onValueChange={this.onValueChange}
style={this.getPositionStyle()} style={this.getPositionStyle()}
widgetId={this.props.widgetId} widgetId={this.props.widgetId}
errorMessage={
this.props.regex &&
this.props.text &&
!this.regex.test(this.props.text)
? this.props.errorMessage
: undefined
}
inputType={this.props.inputType} inputType={this.props.inputType}
disabled={this.props.isDisabled} disabled={this.props.isDisabled}
maxChars={this.props.maxChars}
label={this.props.label}
defaultValue={this.props.defaultText} defaultValue={this.props.defaultText}
maxNum={this.props.maxNum} maxNum={this.props.maxNum}
minNum={this.props.minNum} minNum={this.props.minNum}
@ -43,6 +85,9 @@ export interface InputWidgetProps extends WidgetProps {
inputType: InputType; inputType: InputType;
defaultText?: string; defaultText?: string;
isDisabled?: boolean; isDisabled?: boolean;
text?: string;
regex?: string;
errorMessage?: string;
placeholderText?: string; placeholderText?: string;
maxChars?: number; maxChars?: number;
minNum?: number; minNum?: number;

View File

@ -1,15 +1,16 @@
import React from "react"; import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "../constants/WidgetConstants"; import { WidgetType } from "../constants/WidgetConstants";
import TextViewComponent from "../components/appsmith/TextViewComponent"; import TextComponent from "../components/blueprint/TextComponent";
class TextWidget extends BaseWidget<TextWidgetProps, WidgetState> { class TextWidget extends BaseWidget<TextWidgetProps, WidgetState> {
getPageView() { getPageView() {
return ( return (
<TextViewComponent <TextComponent
style={this.getPositionStyle()} style={this.getPositionStyle()}
widgetId={this.props.widgetId} widgetId={this.props.widgetId}
key={this.props.widgetId} key={this.props.widgetId}
textStyle={this.props.textStyle}
text={this.props.text} text={this.props.text}
/> />
); );
@ -24,7 +25,7 @@ export type TextStyle = "BODY" | "HEADING" | "LABEL" | "SUB_TEXT";
export interface TextWidgetProps extends WidgetProps { export interface TextWidgetProps extends WidgetProps {
text?: string; text?: string;
textStyle?: TextStyle; textStyle: TextStyle;
} }
export default TextWidget; export default TextWidget;