Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Use zone as form #38550

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,8 @@ export interface ButtonProps extends HeadlessButtonProps {
* @default medium
*/
size?: Omit<keyof typeof SIZES, "large">;
/** Indicates if the button should be disabled when the form is invalid */
disableOnInvalidForm?: boolean;
/** Indicates if the button should reset the form when clicked */
resetFormOnClick?: boolean;
}
4 changes: 2 additions & 2 deletions app/client/src/WidgetProvider/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import type { Stylesheet } from "entities/AppTheming";
import { omit } from "lodash";
import moment from "moment";
import type { SVGProps } from "react";
import type { DerivedPropertiesMap } from "WidgetProvider/factory";
import type { WidgetFeatures } from "utils/WidgetFeatures";
import type { WidgetProps } from "../widgets/BaseWidget";
import type { ExtraDef } from "utils/autocomplete/defCreatorUtils";
import type { WidgetEntityConfig } from "ee/entities/DataTree/types";
import type {
WidgetQueryConfig,
Expand All @@ -27,6 +25,8 @@ import type {
Positioning,
ResponsiveBehavior,
} from "layoutSystems/common/utils/constants";
import type { DerivedPropertiesMap } from "./factory/types";
import type { ExtraDef } from "utils/autocomplete/types";

export interface WidgetSizeConfig {
viewportMinWidth: number;
Expand Down
7 changes: 6 additions & 1 deletion app/client/src/WidgetProvider/factory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ import type {
PasteDestinationInfo,
} from "layoutSystems/anvil/utils/paste/types";
import { call } from "redux-saga/effects";
import type { DerivedPropertiesMap } from "./types";

// exporting it as well so that existing imports are not affected
// TODO: remove this once all imports are updated
export type { DerivedPropertiesMap };

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type WidgetDerivedPropertyType = any;
export type DerivedPropertiesMap = Record<string, string>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant type declaration.

The WidgetDerivedPropertyType is declared as any with a TODO comment. Since DerivedPropertiesMap is now properly typed as Record<string, string>, this type declaration appears to be redundant.

-// TODO: Fix this the next time the file is edited
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type WidgetDerivedPropertyType = any;

Also applies to: 54-54

export type WidgetType = (typeof WidgetFactory.widgetTypes)[number];

class WidgetFactory {
Expand Down
1 change: 1 addition & 0 deletions app/client/src/WidgetProvider/factory/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type DerivedPropertiesMap = Record<string, string>;
2 changes: 1 addition & 1 deletion app/client/src/constants/WidgetConstants.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SupportedLayouts } from "reducers/entityReducers/pageListReducer";
import type { SupportedLayouts } from "reducers/entityReducers/types";
import type { WidgetType as FactoryWidgetType } from "WidgetProvider/factory";
import { THEMEING_TEXT_SIZES } from "./ThemeConstants";
import type { WidgetCardProps } from "widgets/BaseWidget";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ export function RecaptchaV2(props: RecaptchaV2Props) {
const {
isDisabled,
isLoading,
onPress: onClickProp,
onClick: onClickProp,
onRecaptchaSubmitError = noop,
onRecaptchaSubmitSuccess,
onReset,
recaptchaKey,
} = props;
const onClick = () => {
if (isDisabled) return onClickProp;
if (isDisabled) return () => onClickProp?.(onReset);

if (isLoading) return onClickProp;
if (isLoading) return () => onClickProp?.(onReset);

if (isInvalidKey) {
// Handle incorrent google recaptcha site key
Expand All @@ -43,7 +44,7 @@ export function RecaptchaV2(props: RecaptchaV2Props) {
.then((token: any) => {
if (token) {
if (typeof onRecaptchaSubmitSuccess === "function") {
onRecaptchaSubmitSuccess(token);
onRecaptchaSubmitSuccess(token, onReset);
}
} else {
// Handle incorrent google recaptcha site key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ export function RecaptchaV3(props: RecaptchaV3Props) {
};

const {
onPress: onClickProp,
onClick: onClickProp,
onRecaptchaSubmitError = noop,
onRecaptchaSubmitSuccess,
onReset,
recaptchaKey,
} = props;

const onClick: ButtonComponentProps["onPress"] = () => {
if (props.isDisabled) return onClickProp;
if (props.isDisabled) return () => onClickProp?.(onReset);

if (props.isLoading) return onClickProp;
if (props.isLoading) return () => onClickProp?.(onReset);

if (status === ScriptStatus.READY) {
// TODO: Fix this the next time the file is edited
Expand All @@ -42,7 +43,7 @@ export function RecaptchaV3(props: RecaptchaV3Props) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((token: any) => {
if (typeof onRecaptchaSubmitSuccess === "function") {
onRecaptchaSubmitSuccess(token);
onRecaptchaSubmitSuccess(token, onReset);
}
})
.catch(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import React from "react";
import { Button, Tooltip } from "@appsmith/wds";
import type { ButtonProps } from "@appsmith/wds";

import { Container } from "./Container";
import { useRecaptcha } from "./useRecaptcha";
import type { UseRecaptchaProps } from "./useRecaptcha";
import { Button, Tooltip } from "@appsmith/wds";
import type { ButtonProps } from "@appsmith/wds";
import { useWDSZoneWidgetContext } from "../../WDSZoneWidget/widget/context";

export interface ButtonComponentProps extends ButtonProps {
text?: string;
tooltip?: string;
isVisible?: boolean;
isLoading: boolean;
isDisabled?: boolean;
onClick?: (onReset?: () => void) => void;
}

function ButtonComponent(props: ButtonComponentProps & UseRecaptchaProps) {
const { icon, text, tooltip, ...rest } = props;
const { isFormValid, onReset } = useWDSZoneWidgetContext();
const { onClick, recpatcha } = useRecaptcha({ ...props, onReset });

const { onClick, recpatcha } = useRecaptcha(props);
const isDisabled =
props.isDisabled || (props.disableOnInvalidForm && isFormValid === false);

return (
<Container>
<Tooltip tooltip={tooltip}>
<Button icon={icon} {...rest} onPress={onClick}>
<Button
icon={icon}
{...rest}
isDisabled={isDisabled}
onPress={() => onClick?.(onReset)}
>
{text}
</Button>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ export interface UseRecaptchaProps {
recaptchaKey?: string;
recaptchaType?: RecaptchaType;
onRecaptchaSubmitError?: (error: string) => void;
onRecaptchaSubmitSuccess?: (token: string) => void;
onRecaptchaSubmitSuccess?: (token: string, onReset?: () => void) => void;
handleRecaptchaV2Loading?: (isLoading: boolean) => void;
}

export type RecaptchaProps = ButtonComponentProps & UseRecaptchaProps;
export type RecaptchaProps = ButtonComponentProps &
UseRecaptchaProps & {
onReset?: () => void;
onClick?: (onReset?: () => void) => void;
};

interface UseRecaptchaReturn {
// TODO: Fix this the next time the file is edited
Expand All @@ -21,10 +25,10 @@ interface UseRecaptchaReturn {
}

export const useRecaptcha = (props: RecaptchaProps): UseRecaptchaReturn => {
const { onPress: onClickProp, recaptchaKey } = props;
const { onClick: onClickProp, recaptchaKey } = props;

if (!recaptchaKey) {
return { onClick: onClickProp };
return { onClick: () => onClickProp?.(props?.onReset) };
}

if (props.recaptchaType === "V2") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const defaultsConfig = {
widgetName: "Button",
isDisabled: false,
isVisible: true,
disabledWhenInvalid: false,
disableOnInvalidForm: false,
resetFormOnClick: false,
recaptchaType: RecaptchaTypes.V3,
version: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,31 @@ export const propertyPaneContentConfig = [
},
],
},
{
sectionName: "Form settings",
children: [
{
helpText:
"Disabled if the form is invalid, if this widget exists directly within a Form widget.",
propertyName: "disableOnInvalidForm",
label: "Disable when form invalid",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText:
"Resets the fields of the form, on click, if this widget exists directly within a Form widget.",
propertyName: "resetFormOnClick",
label: "Reset form on success",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class WDSButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
return config.settersConfig;
}

onButtonClick = () => {
onButtonClick = (onReset?: () => void) => {
if (this.props.onClick) {
this.setState({ isLoading: true });

Expand All @@ -79,33 +79,35 @@ class WDSButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
dynamicString: this.props.onClick,
event: {
type: EventType.ON_CLICK,
callback: this.handleActionComplete,
callback: (result: ExecutionResult) =>
this.handleActionComplete(result, onReset),
},
});

return;
}

if (this.props.resetFormOnClick && this.props.onReset) {
this.props.onReset();
if (this.props.resetFormOnClick && onReset) {
onReset();

return;
}
};

hasOnClickAction = () => {
const { isDisabled, onClick, onReset, resetFormOnClick } = this.props;
const { isDisabled, onClick, resetFormOnClick } = this.props;

return Boolean((onClick || onReset || resetFormOnClick) && !isDisabled);
return Boolean((onClick || resetFormOnClick) && !isDisabled);
};

onRecaptchaSubmitSuccess = (token: string) => {
onRecaptchaSubmitSuccess = (token: string, onReset?: () => void) => {
this.props.updateWidgetMetaProperty("recaptchaToken", token, {
triggerPropertyName: "onClick",
dynamicString: this.props.onClick,
event: {
type: EventType.ON_CLICK,
callback: this.handleActionComplete,
callback: (result: ExecutionResult) =>
this.handleActionComplete(result, onReset),
},
});
};
Expand All @@ -124,49 +126,34 @@ class WDSButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
}
};

handleActionComplete = (result: ExecutionResult) => {
handleActionComplete = (result: ExecutionResult, onReset?: () => void) => {
this.setState({
isLoading: false,
});

if (result.success) {
if (this.props.resetFormOnClick && this.props.onReset)
this.props.onReset();
if (this.props.resetFormOnClick && onReset) onReset();
}
};

getWidgetView() {
const isDisabled = (() => {
const { disabledWhenInvalid, isFormValid } = this.props;
const isDisabledWhenFormIsInvalid =
disabledWhenInvalid && "isFormValid" in this.props && !isFormValid;

return this.props.isDisabled || isDisabledWhenFormIsInvalid;
})();

const onPress = (() => {
if (this.hasOnClickAction()) {
return this.onButtonClick;
}

return undefined;
})();

return (
<ButtonComponent
color={this.props.buttonColor}
disableOnInvalidForm={this.props.disableOnInvalidForm}
excludeFromTabOrder={this.props.disableWidgetInteraction}
handleRecaptchaV2Loading={this.handleRecaptchaV2Loading}
icon={this.props.iconName}
iconPosition={this.props.iconAlign}
isDisabled={isDisabled}
isDisabled={this.props.isDisabled}
isLoading={this.props.isLoading || this.state.isLoading}
key={this.props.widgetId}
onPress={onPress}
onClick={this.onButtonClick}
onRecaptchaSubmitError={this.onRecaptchaSubmitError}
onRecaptchaSubmitSuccess={this.onRecaptchaSubmitSuccess}
recaptchaKey={this.props.googleRecaptchaKey}
recaptchaType={this.props.recaptchaType}
resetFormOnClick={this.props.resetFormOnClick}
text={this.props.text}
tooltip={this.props.tooltip}
variant={this.props.buttonVariant}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ export interface ButtonWidgetState extends WidgetState {

export interface ButtonWidgetProps
extends WidgetProps,
Omit<ButtonComponentProps, "type"> {
Omit<ButtonComponentProps, "type" | "onClick"> {
text?: string;
isVisible?: boolean;
isDisabled?: boolean;
resetFormOnClick?: boolean;
googleRecaptchaKey?: string;
recaptchaType?: RecaptchaType;
disabledWhenInvalid?: boolean;
onClick?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const defaultConfig: WidgetDefaultProps = {
version: 1,
widgetName: "Zone",
isVisible: true,
useAsForm: false,
blueprint: {
operations: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export const propertyPaneContent = [
{
sectionName: "General",
children: [
{
propertyName: "useAsForm",
label: "Use as a form",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
Comment on lines +30 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect helpText for useAsForm property.

The helpText "Controls the visibility of the widget" is incorrect for the useAsForm property. It should describe the form functionality instead.

-        helpText: "Controls the visibility of the widget",
+        helpText: "Enables form functionality for this zone widget",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
propertyName: "useAsForm",
label: "Use as a form",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "useAsForm",
label: "Use as a form",
helpText: "Enables form functionality for this zone widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},

{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
Expand Down
Loading
Loading