diff --git a/app/client/packages/design-system/widgets/src/components/Button/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Button/src/styles.module.css index 821a3799a7..5d796a4ea1 100644 --- a/app/client/packages/design-system/widgets/src/components/Button/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/Button/src/styles.module.css @@ -119,7 +119,7 @@ *----------------------------------------------------------------------------- */ &[aria-disabled] { - cursor: default; + cursor: not-allowed; opacity: var(--opacity-disabled); } diff --git a/app/client/packages/design-system/widgets/src/components/ButtonGroup/src/types.ts b/app/client/packages/design-system/widgets/src/components/ButtonGroup/src/types.ts index 32a4ad3a6a..5cfd2a7c43 100644 --- a/app/client/packages/design-system/widgets/src/components/ButtonGroup/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/ButtonGroup/src/types.ts @@ -1,10 +1,15 @@ import type React from "react"; import type { ButtonProps } from "../../Button"; +export const BUTTON_GROUP_ORIENTATIONS = { + vertical: "vertical", + horizontal: "horizontal", +}; + export interface ButtonGroupProps extends Pick { children?: React.ReactNode; - orientation?: "vertical" | "horizontal"; + orientation?: keyof typeof BUTTON_GROUP_ORIENTATIONS; } export type ButtonGroupItemProps = Omit; diff --git a/app/client/src/components/propertyControls/ButtonListControl.tsx b/app/client/src/components/propertyControls/ButtonListControl.tsx index bd9d258eee..6d51213178 100644 --- a/app/client/src/components/propertyControls/ButtonListControl.tsx +++ b/app/client/src/components/propertyControls/ButtonListControl.tsx @@ -166,17 +166,31 @@ class ButtonListControl extends BaseControl { id: newGroupButtonId, index: groupButtonsArray.length, label: newGroupButtonLabel, - menuItems: {}, - buttonType: "SIMPLE", - placement: ButtonPlacementTypes.CENTER, widgetId: generateReactKey(), isDisabled: false, isVisible: true, - buttonColor: - this.props.widgetProperties.childStylesheet.button.buttonColor, }, }; + if (this.props.widgetProperties.type === "BUTTON_GROUP_WIDGET") { + /** + * These properties are required for "BUTTON_GROUP_WIDGET" but not for + * "WDS_BUTTON_GROUP_WIDGET" + */ + const optionalButtonGroupItemProperties = { + menuItems: {}, + buttonType: "SIMPLE", + placement: ButtonPlacementTypes.CENTER, + buttonColor: + this.props.widgetProperties.childStylesheet.button.buttonColor, + }; + + groupButtons[newGroupButtonId] = { + ...groupButtons[newGroupButtonId], + ...optionalButtonGroupItemProperties, + }; + } + this.updateProperty(this.props.propertyName, groupButtons); }; diff --git a/app/client/src/components/wds/constants.ts b/app/client/src/components/wds/constants.ts index ec7a5a8afb..c2b66497b0 100644 --- a/app/client/src/components/wds/constants.ts +++ b/app/client/src/components/wds/constants.ts @@ -5,4 +5,5 @@ export const WDS_V2_WIDGET_MAP = { ICON_BUTTON_WIDGET: "WDS_ICON_BUTTON_WIDGET", TEXT_WIDGET: "WDS_TEXT_WIDGET", TABLE_WIDGET_V2: "WDS_TABLE_WIDGET", + BUTTON_GROUP_WIDGET: "WDS_BUTTON_GROUP_WIDGET", }; diff --git a/app/client/src/widgets/index.ts b/app/client/src/widgets/index.ts index caf4172148..e5c98808d7 100644 --- a/app/client/src/widgets/index.ts +++ b/app/client/src/widgets/index.ts @@ -64,6 +64,7 @@ import { WDSIconButtonWidget } from "./wds/WDSIconButtonWidget"; import { WDSTextWidget } from "./wds/WDSTextWidget"; import type BaseWidget from "./BaseWidget"; import { WDSTableWidget } from "./wds/WDSTableWidget"; +import { WDSButtonGroupWidget } from "./wds/WDSButtonGroupWidget"; const Widgets = [ CanvasWidget, @@ -121,6 +122,7 @@ const Widgets = [ WDSIconButtonWidget, WDSTextWidget, WDSTableWidget, + WDSButtonGroupWidget, //Deprecated Widgets InputWidget, diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/component/index.tsx b/app/client/src/widgets/wds/WDSButtonGroupWidget/component/index.tsx new file mode 100644 index 0000000000..2564f6bb8b --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/component/index.tsx @@ -0,0 +1,71 @@ +import React, { useState } from "react"; +import { ButtonGroup, ButtonGroupItem } from "@design-system/widgets"; +import type { + ButtonGroupComponentProps, + ButtonGroupItemComponentProps, +} from "./types"; +import { Icon as BIcon } from "@blueprintjs/core"; +import { sortBy } from "lodash"; + +export const ButtonGroupComponent = (props: ButtonGroupComponentProps) => { + const [loadingButtonIds, setLoadingButtonIds] = useState< + Array + >([]); + + const sortedButtons = sortBy( + Object.keys(props.buttonsList) + .map((key) => props.buttonsList[key]) + .filter((button) => { + return button.isVisible === true; + }), + ["index"], + ); + + return ( + + {sortedButtons.map((button: ButtonGroupItemComponentProps) => { + const icon = + button.iconName && + (() => { + return ; + }); + + const handleActionComplete = () => { + const newLoadingButtonIds = [...loadingButtonIds]; + const index = newLoadingButtonIds.indexOf(button.id); + + if (index > -1) { + newLoadingButtonIds.splice(index, 1); + } + + setLoadingButtonIds(newLoadingButtonIds); + }; + + const onButtonClick = () => { + if (button.onClick) { + setLoadingButtonIds([...loadingButtonIds, button.id]); + + props.onButtonClick(button.onClick, handleActionComplete); + } + }; + + return ( + + {button.label} + + ); + })} + + ); +}; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/component/types.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/component/types.ts new file mode 100644 index 0000000000..1f3a39526a --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/component/types.ts @@ -0,0 +1,28 @@ +import type { IconName } from "@blueprintjs/icons"; +import type { ButtonGroupProps, ButtonProps } from "@design-system/widgets"; +import type { ButtonsList } from "../widget/types"; +import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; + +export interface ButtonGroupComponentProps { + color?: ButtonGroupProps["color"]; + variant?: ButtonGroupProps["variant"]; + orientation: ButtonGroupProps["orientation"]; + buttonsList: ButtonsList; + onButtonClick: ( + onClick: string, + callback: (result: ExecutionResult) => void, + ) => void; +} + +export interface ButtonGroupItemComponentProps { + label?: string; + isVisible?: boolean; + isLoading?: boolean; + isDisabled?: boolean; + widgetId: string; + id: string; + index: number; + iconName?: IconName; + iconAlign?: ButtonProps["iconPosition"]; + onClick?: string; +} diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/autocompleteConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/autocompleteConfig.ts new file mode 100644 index 0000000000..ebe6af4396 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/autocompleteConfig.ts @@ -0,0 +1,8 @@ +import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; + +export const autocompleteConfig = { + "!doc": + "The Button group widget represents a set of buttons in a group. Group can have simple buttons or menu buttons with drop-down items.", + "!url": "https://docs.appsmith.com/widget-reference/button-group", + isVisible: DefaultAutocompleteDefinitions.isVisible, +}; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/defaultsConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/defaultsConfig.ts new file mode 100644 index 0000000000..8816b6753a --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/defaultsConfig.ts @@ -0,0 +1,61 @@ +import { + BUTTON_GROUP_ORIENTATIONS, + BUTTON_VARIANTS, + COLORS, +} from "@design-system/widgets"; +import { IconNames } from "@blueprintjs/icons"; +import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; +import { + BUTTON_MIN_WIDTH, + FILL_WIDGET_MIN_WIDTH, +} from "constants/minWidthConstants"; + +export const defaultsConfig = { + rows: 4, + columns: 24, + widgetName: "ButtonGroup", + orientation: BUTTON_GROUP_ORIENTATIONS.horizontal, + buttonVariant: BUTTON_VARIANTS.filled, + buttonColor: COLORS.accent, + isDisabled: false, + isVisible: true, + version: 1, + animateLoading: true, + responsiveBehavior: ResponsiveBehavior.Fill, + minWidth: FILL_WIDGET_MIN_WIDTH, + buttonsList: { + button1: { + label: "Favorite", + isVisible: true, + isDisabled: false, + widgetId: "", + id: "button1", + index: 0, + iconName: IconNames.HEART, + iconAlign: "start", + minWidth: BUTTON_MIN_WIDTH, + }, + button2: { + label: "Add", + isVisible: true, + isDisabled: false, + widgetId: "", + id: "button2", + index: 1, + iconName: IconNames.ADD, + iconAlign: "start", + minWidth: BUTTON_MIN_WIDTH, + }, + button3: { + label: "More", + isVisible: true, + isDisabled: false, + widgetId: "", + id: "button3", + index: 2, + iconName: IconNames.MORE, + iconAlign: "start", + minWidth: BUTTON_MIN_WIDTH, + }, + }, +}; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/index.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/index.ts new file mode 100644 index 0000000000..3233e112d9 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/index.ts @@ -0,0 +1,5 @@ +export * from "./propertyPaneConfig"; +export { autocompleteConfig } from "./autocompleteConfig"; +export { defaultsConfig } from "./defaultsConfig"; +export { metaConfig } from "./metaConfig"; +export { settersConfig } from "./settersConfig"; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/metaConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/metaConfig.ts new file mode 100644 index 0000000000..299c5692fa --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/metaConfig.ts @@ -0,0 +1,11 @@ +import IconSVG from "../icon.svg"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; + +export const metaConfig = { + name: "Button Group", + iconSVG: IconSVG, + needsMeta: false, + isCanvas: false, + searchTags: ["click", "submit"], + tags: [WIDGET_TAGS.BUTTONS], +}; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/contentConfig.ts new file mode 100644 index 0000000000..f56d6d4fc7 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/contentConfig.ts @@ -0,0 +1,159 @@ +import { ValidationTypes } from "constants/WidgetValidation"; + +export const propertyPaneContentConfig = [ + { + sectionName: "Data", + children: [ + { + helpText: "Group Buttons", + propertyName: "buttonsList", + controlType: "GROUP_BUTTONS", + label: "Buttons", + isBindProperty: false, + isTriggerProperty: false, + dependencies: ["childStylesheet"], + panelConfig: { + editableTitle: true, + titlePropertyName: "label", + panelIdPropertyName: "id", + updateHook: ( + props: any, + propertyPath: string, + propertyValue: string, + ) => { + return [ + { + propertyPath, + propertyValue, + }, + ]; + }, + contentChildren: [ + { + sectionName: "Label", + children: [ + { + propertyName: "label", + helpText: "Sets the label of the button", + label: "Text", + controlType: "INPUT_TEXT", + placeholderText: "Enter label", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + ], + }, + { + sectionName: "General", + children: [ + { + propertyName: "isVisible", + helpText: "Controls the visibility of the widget", + label: "Visible", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + { + propertyName: "isDisabled", + helpText: "Disables input to the widget", + label: "Disabled", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + ], + }, + { + sectionName: "Events", + children: [ + { + helpText: "when the button is clicked", + propertyName: "onClick", + label: "onClick", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + ], + }, + ], + styleChildren: [ + { + sectionName: "Icon", + children: [ + { + propertyName: "iconName", + label: "Icon", + helpText: "Sets the icon to be used for a button", + controlType: "ICON_SELECT", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + { + propertyName: "iconAlign", + label: "Position", + helpText: "Sets the icon alignment of the button", + controlType: "ICON_TABS", + fullWidth: false, + options: [ + { + startIcon: "skip-left-line", + value: "start", + }, + { + startIcon: "skip-right-line", + value: "end", + }, + ], + isBindProperty: false, + isTriggerProperty: false, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: ["start", "end"], + }, + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + sectionName: "General", + children: [ + { + helpText: "Controls the visibility of the widget", + propertyName: "isVisible", + label: "Visible", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + { + propertyName: "animateLoading", + label: "Animate loading", + controlType: "SWITCH", + helpText: "Controls the loading of the widget", + defaultValue: true, + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, + ], + }, +]; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/index.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/index.ts new file mode 100644 index 0000000000..4273f74125 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/index.ts @@ -0,0 +1,2 @@ +export { propertyPaneContentConfig } from "./contentConfig"; +export { propertyPaneStyleConfig } from "./styleConfig"; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/styleConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/styleConfig.ts new file mode 100644 index 0000000000..00ed9bc991 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/propertyPaneConfig/styleConfig.ts @@ -0,0 +1,78 @@ +import { + BUTTON_GROUP_ORIENTATIONS, + BUTTON_VARIANTS, + COLORS, +} from "@design-system/widgets"; +import { ValidationTypes } from "constants/WidgetValidation"; +import { capitalize } from "lodash"; + +export const propertyPaneStyleConfig = [ + { + sectionName: "General", + children: [ + { + propertyName: "buttonVariant", + label: "Button variant", + controlType: "ICON_TABS", + fullWidth: true, + helpText: "Sets the variant of the button", + options: Object.values(BUTTON_VARIANTS).map((variant) => ({ + label: capitalize(variant), + value: variant, + })), + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: Object.values(BUTTON_VARIANTS), + default: BUTTON_VARIANTS.filled, + }, + }, + }, + { + propertyName: "buttonColor", + label: "Button color", + controlType: "DROP_DOWN", + fullWidth: true, + helpText: "Sets the semantic color of the button", + options: Object.values(COLORS).map((semantic) => ({ + label: capitalize(semantic), + value: semantic, + })), + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: Object.values(COLORS), + default: COLORS.accent, + }, + }, + }, + { + helpText: "Controls widget orientation", + propertyName: "orientation", + label: "Orientation", + controlType: "ICON_TABS", + fullWidth: true, + options: [ + { + label: capitalize(BUTTON_GROUP_ORIENTATIONS.horizontal), + value: BUTTON_GROUP_ORIENTATIONS.horizontal, + }, + { + label: capitalize(BUTTON_GROUP_ORIENTATIONS.vertical), + value: BUTTON_GROUP_ORIENTATIONS.vertical, + }, + ], + defaultValue: BUTTON_GROUP_ORIENTATIONS.horizontal, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, + ], + }, +]; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/config/settersConfig.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/settersConfig.ts new file mode 100644 index 0000000000..888f2f9bcb --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/config/settersConfig.ts @@ -0,0 +1,12 @@ +export const settersConfig = { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, +}; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/icon.svg b/app/client/src/widgets/wds/WDSButtonGroupWidget/icon.svg new file mode 100644 index 0000000000..f7a2e1551b --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/index.tsx b/app/client/src/widgets/wds/WDSButtonGroupWidget/index.tsx new file mode 100644 index 0000000000..edbc714dfb --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/index.tsx @@ -0,0 +1,3 @@ +import { WDSButtonGroupWidget } from "./widget"; + +export { WDSButtonGroupWidget }; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/index.tsx b/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/index.tsx new file mode 100644 index 0000000000..0571c9a4a5 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/index.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import type { SetterConfig } from "entities/AppTheming"; +import type { WidgetState } from "widgets/BaseWidget"; +import BaseWidget from "widgets/BaseWidget"; +import { + metaConfig, + defaultsConfig, + autocompleteConfig, + propertyPaneContentConfig, + propertyPaneStyleConfig, + settersConfig, +} from "./../config"; +import type { ButtonGroupWidgetProps } from "./types"; +import { ButtonGroupComponent } from "../component"; +import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; + +class WDSButtonGroupWidget extends BaseWidget< + ButtonGroupWidgetProps, + WidgetState +> { + constructor(props: ButtonGroupWidgetProps) { + super(props); + } + + static type = "WDS_BUTTON_GROUP_WIDGET"; + + static getConfig() { + return metaConfig; + } + + static getDefaults() { + return defaultsConfig; + } + + static getAutoLayoutConfig() { + return {}; + } + + static getAutocompleteDefinitions() { + return autocompleteConfig; + } + + static getPropertyPaneContentConfig() { + return propertyPaneContentConfig; + } + + static getPropertyPaneStyleConfig() { + return propertyPaneStyleConfig; + } + + static getSetterConfig(): SetterConfig { + return settersConfig; + } + + onButtonClick = ( + onClick: string, + callback: (result: ExecutionResult) => void, + ) => { + super.executeAction({ + triggerPropertyName: "onClick", + dynamicString: onClick, + event: { + type: EventType.ON_CLICK, + callback: callback, + }, + }); + + return; + }; + + getWidgetView() { + return ( + + ); + } +} + +export { WDSButtonGroupWidget }; diff --git a/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/types.ts b/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/types.ts new file mode 100644 index 0000000000..a9059e9ad0 --- /dev/null +++ b/app/client/src/widgets/wds/WDSButtonGroupWidget/widget/types.ts @@ -0,0 +1,13 @@ +import type { ButtonGroupProps } from "@design-system/widgets"; +import type { WidgetProps } from "widgets/BaseWidget"; +import type { ButtonGroupItemComponentProps } from "../component/types"; + +export type ButtonsList = Record; + +export interface ButtonGroupWidgetProps extends WidgetProps { + buttonColor: ButtonGroupProps["color"]; + buttonVariant: ButtonGroupProps["variant"]; + orientation: ButtonGroupProps["orientation"]; + isVisible: boolean; + buttonsList: ButtonsList; +}