diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js index 1152173a15..c9bf0685db 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js @@ -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"); + }); }); diff --git a/app/client/src/components/designSystems/appsmith/IconButtonComponent.tsx b/app/client/src/components/designSystems/appsmith/IconButtonComponent.tsx index 2d10e82a3a..69858cfc98 100644 --- a/app/client/src/components/designSystems/appsmith/IconButtonComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/IconButtonComponent.tsx @@ -30,13 +30,13 @@ export interface ButtonStyleProps { boxShadowColor?: string; buttonStyle?: ButtonStyle; buttonVariant?: ButtonVariant; - dimension: number; + dimension?: number; } -const StyledButton = styled(Button)` +export const StyledButton = styled(Button)` 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: ${ diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts index b065168689..62d4cd9e2e 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts @@ -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; } diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx index b00501b208..bc69dd8154 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx @@ -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 && diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx index a919fedf07..94f96a3e5c 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx @@ -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 ; + + return ( + + {props.columnActions.map((action: ColumnAction, index: number) => { + return ( + + ); + })} + + ); +}; +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, + ) => { + if (props.isSelected) { + e.stopPropagation(); + } + }; + const handleClick = () => { + if (props.action.dynamicTrigger) { + setLoading(true); + props.onCommandClick(props.action.dynamicTrigger, onComplete); + } + }; + return ( +
+ +
+ ); +} + interface RenderActionProps { isSelected: boolean; columnActions?: ColumnAction[]; diff --git a/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx b/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx index 65c3c82276..5dff46f1a8 100644 --- a/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx +++ b/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx @@ -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< diff --git a/app/client/src/entities/Widget/utils.test.ts b/app/client/src/entities/Widget/utils.test.ts index 156505fd20..e6cd9ef653 100644 --- a/app/client/src/entities/Widget/utils.test.ts +++ b/app/client/src/entities/Widget/utils.test.ts @@ -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, }, diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 6ce3831bf7..ff104dfd12 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -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[]; diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index f5e1813153..b0c76c06af 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -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 { 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 { 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;