Feat: Table Widget: icon button as a new column type (#6598)

* feat: add icon button properties to property pane
* feat: add icon button styles to property pane
This commit is contained in:
Tolulope Adetula 2021-09-01 10:50:23 +01:00 committed by GitHub
parent 277cb7fcca
commit 45fad5e094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 457 additions and 29 deletions

View File

@ -45,4 +45,22 @@ describe("Table Widget property pane feature validation", function() {
expect(someText).to.equal("Successful tobias.funke@reqres.in");
});
});
it("Table widget add new icon button column", function() {
cy.openPropertyPane("tablewidget");
// click on Add new Column.
cy.get(".t--add-column-btn").click();
//Open New Custom Column
cy.editColumn("customColumn1");
// Change Column type to icon Button
cy.changeColumnType("Icon Button");
// Select Icon from Icon Control
cy.get(".t--property-control-icon .bp3-icon-caret-down").click({
force: true,
});
cy.get(".bp3-icon-add")
.first()
.click({ force: true });
cy.get(".t--widget-tablewidget .tbody .bp3-icon-add").should("exist");
});
});

View File

@ -30,13 +30,13 @@ export interface ButtonStyleProps {
boxShadowColor?: string;
buttonStyle?: ButtonStyle;
buttonVariant?: ButtonVariant;
dimension: number;
dimension?: number;
}
const StyledButton = styled(Button)<ThemeProp & ButtonStyleProps>`
export const StyledButton = styled(Button)<ThemeProp & ButtonStyleProps>`
background-image: none !important;
height: ${({ dimension }) => `${dimension}px`};
width: ${({ dimension }) => `${dimension}px`};
height: ${({ dimension }) => (dimension ? `${dimension}px` : "auto")};
width: ${({ dimension }) => (dimension ? `${dimension}px` : "auto")};
${({ buttonStyle, buttonVariant, theme }) => `
&:enabled {
background: ${

View File

@ -1,6 +1,10 @@
import { isString } from "lodash";
import moment from "moment";
import { TextSize } from "constants/WidgetConstants";
import { IconName } from "@blueprintjs/icons";
import { ButtonBorderRadius } from "../../../propertyControls/ButtonBorderRadiusControl";
import { ButtonBoxShadow } from "../../../propertyControls/BoxShadowOptionsControl";
import { ButtonStyle, ButtonVariant } from "../IconButtonComponent";
export type TableSizes = {
COLUMN_HEADER_HEIGHT: number;
@ -102,6 +106,12 @@ export interface CellLayoutProperties {
isVisible?: boolean;
isDisabled?: boolean;
displayText?: string;
iconName?: IconName;
buttonVariant: ButtonVariant;
borderRadius: ButtonBorderRadius;
boxShadow: ButtonBoxShadow;
boxShadowColor: string;
iconButtonStyle: ButtonStyle;
isCellVisible: boolean;
}
@ -130,7 +140,7 @@ export interface ReactTableColumnProps extends TableColumnProps {
export interface ColumnProperties {
id: string;
label: string;
label?: string;
columnType: string;
isVisible: boolean;
isDisabled?: boolean;
@ -155,6 +165,12 @@ export interface ColumnProperties {
dropdownOptions?: string;
onOptionChange?: string;
displayText?: string;
iconName?: IconName;
buttonVariant?: ButtonVariant;
borderRadius?: ButtonBorderRadius;
boxShadow?: ButtonBoxShadow;
boxShadowColor?: string;
iconButtonStyle?: ButtonStyle;
isCellVisible?: boolean;
}

View File

@ -137,7 +137,9 @@ function TableFilterPaneContent(props: TableFilterProps) {
};
})
.filter((column: { label: string; value: string; type: string }) => {
return !["video", "button", "image"].includes(column.type as string);
return !["video", "button", "image", "iconButton"].includes(
column.type as string,
);
});
const hasAnyFilters = !!(
filters.length >= 1 &&

View File

@ -27,10 +27,17 @@ import { AnyStyledComponent } from "styled-components";
import styled from "constants/DefaultTheme";
import { Colors } from "constants/Colors";
import { DropdownOption } from "widgets/DropdownWidget";
import { IconNames } from "@blueprintjs/icons";
import { IconName, IconNames } from "@blueprintjs/icons";
import { Select, IItemRendererProps } from "@blueprintjs/select";
import { FontStyleTypes, TextSizes } from "constants/WidgetConstants";
import { noop } from "utils/AppsmithUtils";
import { ButtonBorderRadius } from "../../../propertyControls/ButtonBorderRadiusControl";
import { ButtonBoxShadow } from "../../../propertyControls/BoxShadowOptionsControl";
import {
ButtonStyle,
ButtonVariant,
StyledButton,
} from "../IconButtonComponent";
export const renderCell = (
value: any,
@ -157,6 +164,95 @@ export const renderCell = (
}
};
interface RenderIconButtonProps {
isSelected: boolean;
columnActions?: ColumnAction[];
iconName?: IconName;
buttonVariant: ButtonVariant;
buttonStyle: ButtonStyle;
borderRadius: ButtonBorderRadius;
boxShadow: ButtonBoxShadow;
boxShadowColor: string;
onCommandClick: (dynamicTrigger: string, onComplete: () => void) => void;
isCellVisible: boolean;
}
export const renderIconButton = (
props: RenderIconButtonProps,
isHidden: boolean,
cellProperties: CellLayoutProperties,
) => {
if (!props.columnActions)
return <CellWrapper cellProperties={cellProperties} isHidden={isHidden} />;
return (
<CellWrapper
cellProperties={cellProperties}
isCellVisible={props.isCellVisible}
isHidden={isHidden}
>
{props.columnActions.map((action: ColumnAction, index: number) => {
return (
<IconButton
action={action}
borderRadius={props.borderRadius}
boxShadow={props.boxShadow}
boxShadowColor={props.boxShadowColor}
buttonStyle={props.buttonStyle}
buttonVariant={props.buttonVariant}
iconName={props.iconName}
isSelected={props.isSelected}
key={index}
onCommandClick={props.onCommandClick}
/>
);
})}
</CellWrapper>
);
};
function IconButton(props: {
iconName?: IconName;
onCommandClick: (dynamicTrigger: string, onComplete: () => void) => void;
isSelected: boolean;
action: ColumnAction;
buttonStyle: ButtonStyle;
buttonVariant: ButtonVariant;
borderRadius: ButtonBorderRadius;
boxShadow: ButtonBoxShadow;
boxShadowColor: string;
}): JSX.Element {
const [loading, setLoading] = useState(false);
const onComplete = () => {
setLoading(false);
};
const handlePropagation = (
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
if (props.isSelected) {
e.stopPropagation();
}
};
const handleClick = () => {
if (props.action.dynamicTrigger) {
setLoading(true);
props.onCommandClick(props.action.dynamicTrigger, onComplete);
}
};
return (
<div onClick={handlePropagation}>
<StyledButton
borderRadius={props.borderRadius}
boxShadow={props.boxShadow}
boxShadowColor={props.boxShadowColor}
buttonStyle={props.buttonStyle}
buttonVariant={props.buttonVariant}
icon={props.iconName}
loading={loading}
onClick={handleClick}
/>
</div>
);
}
interface RenderActionProps {
isSelected: boolean;
columnActions?: ColumnAction[];

View File

@ -10,7 +10,7 @@ import { InputText } from "components/propertyControls/InputTextControl";
import { ActionCreator } from "components/editorComponents/ActionCreator";
import { Size, Category } from "components/ads/Button";
export interface ColumnAction {
label: string;
label?: string;
id: string;
dynamicTrigger: string;
}
@ -52,13 +52,13 @@ class ColumnActionSelectorControl extends BaseControl<
<InputTextWrapper>
<InputText
evaluatedValue={columnAction.label}
label={columnAction.label}
label={columnAction.label || ""}
onChange={this.updateColumnActionLabel.bind(
this,
columnAction,
)}
theme={this.props.theme}
value={columnAction.label}
value={columnAction.label as string}
/>
</InputTextWrapper>
<Wrapper>

View File

@ -146,7 +146,6 @@ describe("getAllPathsFromPropertyConfig", () => {
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.isCellVisible":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.horizontalAlignment":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.verticalAlignment":
@ -166,8 +165,6 @@ describe("getAllPathsFromPropertyConfig", () => {
"primaryColumns.status.isDisabled": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonLabelColor":
EvaluationSubstitutionType.TEMPLATE,
// "primaryColumns.createdAt.isVisible":
// EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.isCellVisible":
EvaluationSubstitutionType.TEMPLATE,
},

View File

@ -5,6 +5,19 @@ import { TableWidgetProps } from "./TableWidgetConstants";
import { ValidationTypes } from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
import { ButtonBorderRadiusTypes } from "components/propertyControls/BorderRadiusOptionsControl";
enum ColumnTypes {
TEXT = "text",
URL = "url",
NUMBER = "number",
IMAGE = "image",
VIDEO = "video",
DATE = "date",
BUTTON = "button",
ICON_BUTTON = "iconButton",
}
function defaultSelectedRowValidation(
value: unknown,
@ -244,6 +257,20 @@ const getBasePropertyPath = (propertyPath: string): string | undefined => {
}
};
// Hide column which are not included in the array params
const hideByColumnType = (
props: TableWidgetProps,
propertyPath: string,
columnTypes: ColumnTypes[],
shouldUsePropertyPath?: boolean,
) => {
const baseProperty = shouldUsePropertyPath
? propertyPath
: getBasePropertyPath(propertyPath);
const columnType = get(props, `${baseProperty}.columnType`, "");
return !columnTypes.includes(columnType);
};
export default [
{
sectionName: "General",
@ -319,6 +346,10 @@ export default [
label: "Button",
value: "button",
},
{
label: "Icon Button",
value: "iconButton",
},
],
updateHook: updateDerivedColumnsHook,
dependencies: [
@ -358,13 +389,14 @@ export default [
controlType: "COMPUTE_VALUE",
updateHook: updateDerivedColumnsHook,
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
const columnType = get(
props,
`${baseProperty}.columnType`,
"",
);
return columnType === "button";
return hideByColumnType(props, propertyPath, [
ColumnTypes.DATE,
ColumnTypes.IMAGE,
ColumnTypes.NUMBER,
ColumnTypes.TEXT,
ColumnTypes.VIDEO,
ColumnTypes.URL,
]);
},
dependencies: [
"primaryColumns",
@ -633,12 +665,16 @@ export default [
{
sectionName: "Styles",
hidden: (props: TableWidgetProps, propertyPath: string) => {
const columnType = get(props, `${propertyPath}.columnType`, "");
return (
columnType === "button" ||
columnType === "image" ||
columnType === "video"
return hideByColumnType(
props,
propertyPath,
[
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.URL,
],
true,
);
},
dependencies: ["primaryColumns", "derivedColumns"],
@ -811,9 +847,85 @@ export default [
sectionName: "Button Properties",
hidden: (props: TableWidgetProps, propertyPath: string) => {
const columnType = get(props, `${propertyPath}.columnType`, "");
return columnType !== "button";
return columnType !== "button" && columnType !== "iconButton";
},
children: [
{
propertyName: "iconName",
label: "Icon",
helpText: "Sets the icon to be used for the icon button",
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
controlType: "ICON_SELECT",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
default: "plus",
},
},
},
{
propertyName: "iconButtonStyle",
label: "Icon Color",
controlType: "DROP_DOWN",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
helpText: "Sets the style of the icon button",
options: [
{
label: "Primary",
value: "PRIMARY",
},
{
label: "Warning",
value: "WARNING",
},
{
label: "Danger",
value: "DANGER",
},
{
label: "Info",
value: "INFO",
},
{
label: "Secondary",
value: "SECONDARY",
},
],
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
default: "plus",
},
},
},
{
propertyName: "isDisabled",
label: "Disabled",
@ -831,6 +943,11 @@ export default [
controlType: "COMPUTE_VALUE",
defaultValue: "Action",
updateHook: updateDerivedColumnsHook,
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
@ -848,6 +965,11 @@ export default [
customJSControl: "COMPUTE_VALUE",
defaultColor: Colors.GREEN,
updateHook: updateDerivedColumnsHook,
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
@ -856,6 +978,126 @@ export default [
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "buttonVariant",
label: "Button Variant",
controlType: "DROP_DOWN",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
helpText: "Sets the variant of the icon button",
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
options: [
{
label: "Solid",
value: "SOLID",
},
{
label: "Outline",
value: "OUTLINE",
},
{
label: "Ghost",
value: "GHOST",
},
],
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "borderRadius",
label: "Border Radius",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
helpText:
"Rounds the corners of the icon button's outer border edge",
controlType: "BORDER_RADIUS_OPTIONS",
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
options: [
ButtonBorderRadiusTypes.SHARP,
ButtonBorderRadiusTypes.ROUNDED,
ButtonBorderRadiusTypes.CIRCLE,
],
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: ["CIRCLE", "SHARP", "ROUNDED"],
},
},
},
{
propertyName: "boxShadow",
label: "Box Shadow",
helpText:
"Enables you to cast a drop shadow from the frame of the widget",
controlType: "BOX_SHADOW_OPTIONS",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: [
"NONE",
"VARIANT1",
"VARIANT2",
"VARIANT3",
"VARIANT4",
"VARIANT5",
],
},
},
},
{
propertyName: "boxShadowColor",
helpText: "Sets the shadow color of the widget",
label: "Shadow Color",
controlType: "COLOR_PICKER",
customJSControl: "COMPUTE_VALUE",
isJSConvertible: true,
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.ICON_BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
isBindProperty: false,
isTriggerProperty: false,
},
{
propertyName: "buttonLabelColor",
label: "Label Color",
@ -863,12 +1105,17 @@ export default [
isJSConvertible: true,
customJSControl: "COMPUTE_VALUE",
defaultColor: Colors.WHITE,
updateHook: updateDerivedColumnsHook,
hidden: (props: TableWidgetProps, propertyPath: string) => {
return hideByColumnType(props, propertyPath, [
ColumnTypes.BUTTON,
]);
},
dependencies: [
"primaryColumns",
"derivedColumns",
"columnOrder",
],
updateHook: updateDerivedColumnsHook,
isBindProperty: true,
isTriggerProperty: false,
},
@ -1222,4 +1469,4 @@ export default [
},
],
},
];
] as PropertyPaneConfig[];

View File

@ -21,6 +21,7 @@ import {
renderCell,
renderDropdown,
renderActions,
renderIconButton,
} from "components/designSystems/appsmith/TableComponent/TableUtilities";
import { getAllTableColumnKeys } from "components/designSystems/appsmith/TableComponent/TableHelpers";
import Skeleton from "components/utils/Skeleton";
@ -45,6 +46,7 @@ import {
} from "components/designSystems/appsmith/TableComponent/Constants";
import tablePropertyPaneConfig from "./TablePropertyPaneConfig";
import { BatchPropertyUpdatePayload } from "actions/controlActions";
import { IconName } from "@blueprintjs/icons";
import { isArray } from "lodash";
const ReactTableComponent = lazy(() =>
@ -141,6 +143,36 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
rowIndex,
true,
),
iconName: this.getPropertyValue(
columnProperties.iconName,
rowIndex,
true,
),
buttonVariant: this.getPropertyValue(
columnProperties.buttonVariant,
rowIndex,
true,
),
borderRadius: this.getPropertyValue(
columnProperties.borderRadius,
rowIndex,
true,
),
boxShadow: this.getPropertyValue(
columnProperties.boxShadow,
rowIndex,
true,
),
boxShadowColor: this.getPropertyValue(
columnProperties.boxShadowColor,
rowIndex,
true,
),
iconButtonStyle: this.getPropertyValue(
columnProperties.iconButtonStyle,
rowIndex,
true,
),
textSize: this.getPropertyValue(columnProperties.textSize, rowIndex),
textColor: this.getPropertyValue(columnProperties.textColor, rowIndex),
fontStyle: this.getPropertyValue(columnProperties.fontStyle, rowIndex), //Fix this
@ -251,6 +283,26 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
onClick,
isSelected,
);
} else if (columnProperties.columnType === "iconButton") {
const iconButtonProps = {
isSelected: !!props.row.isSelected,
onCommandClick: (action: string, onComplete: () => void) =>
this.onCommandClick(rowIndex, action, onComplete),
columnActions: [
{
id: columnProperties.id,
dynamicTrigger: columnProperties.onClick || "",
},
],
iconName: cellProperties.iconName as IconName,
buttonStyle: cellProperties.iconButtonStyle,
buttonVariant: cellProperties.buttonVariant,
borderRadius: cellProperties.borderRadius,
boxShadow: cellProperties.boxShadow,
boxShadowColor: cellProperties.boxShadowColor,
isCellVisible: cellProperties.isCellVisible ?? true,
};
return renderIconButton(iconButtonProps, isHidden, cellProperties);
} else {
const isCellVisible = cellProperties.isCellVisible ?? true;