chore: add wds datepicker widget (#37711)
 /ok-to-test tags="@tag.Anvil" <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced the WDS Date Picker Widget, enhancing date selection capabilities within the UI. - Added configuration options for widget size, visibility, and autocomplete behavior. - Implemented comprehensive validation for date input, ensuring accurate user selections. - Expanded widget collection to include the new WDS Date Picker Widget. - Introduced new constants for date format options, facilitating diverse formatting choices. - Added support for a new "Date" input type, enhancing input widget configurability. - **Documentation** - Updated property pane configurations to include detailed settings for date format, validation, and event handling. - **Bug Fixes** - Improved handling of derived properties to ensure proper context during widget interactions. These updates collectively improve user experience and flexibility in date selection within the application. <!-- end of auto-generated comment: release notes by coderabbit.ai --> <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/12083004714> > Commit: b17348e03db911501970d2c8a59c4fea30a175e1 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12083004714&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Anvil` > Spec: > <hr>Fri, 29 Nov 2024 10:44:39 UTC <!-- end of auto-generated comment: Cypress test results --> --------- Co-authored-by: Vadim Vaitenko <vadim@appsmith.com>
This commit is contained in:
parent
8a369e1096
commit
63eec76635
|
|
@ -61,6 +61,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar tbody [role="button"][data-focus-visible] {
|
.calendar tbody [role="button"][data-focus-visible] {
|
||||||
outline: var(--border-width-2) solid var(--color-bd-accent);
|
--box-shadow-offset: 2px;
|
||||||
outline-offset: var(--border-width-2);
|
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 var(--box-shadow-offset) var(--color-bg),
|
||||||
|
0 0 0 calc(var(--box-shadow-offset) + var(--border-width-2))
|
||||||
|
var(--color-bd-focus);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const INPUT_TYPES = {
|
||||||
PASSWORD: "PASSWORD",
|
PASSWORD: "PASSWORD",
|
||||||
PHONE_NUMBER: "PHONE_NUMBER",
|
PHONE_NUMBER: "PHONE_NUMBER",
|
||||||
MULTI_LINE_TEXT: "MULTI_LINE_TEXT",
|
MULTI_LINE_TEXT: "MULTI_LINE_TEXT",
|
||||||
|
DATE: "DATE",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const INPUT_TYPE_TO_WIDGET_TYPE_MAP = {
|
export const INPUT_TYPE_TO_WIDGET_TYPE_MAP = {
|
||||||
|
|
@ -16,4 +17,5 @@ export const INPUT_TYPE_TO_WIDGET_TYPE_MAP = {
|
||||||
[INPUT_TYPES.MULTI_LINE_TEXT]: "WDS_MULTILINE_INPUT_WIDGET",
|
[INPUT_TYPES.MULTI_LINE_TEXT]: "WDS_MULTILINE_INPUT_WIDGET",
|
||||||
[INPUT_TYPES.CURRENCY]: "WDS_CURRENCY_INPUT_WIDGET",
|
[INPUT_TYPES.CURRENCY]: "WDS_CURRENCY_INPUT_WIDGET",
|
||||||
[INPUT_TYPES.PHONE_NUMBER]: "WDS_PHONE_INPUT_WIDGET",
|
[INPUT_TYPES.PHONE_NUMBER]: "WDS_PHONE_INPUT_WIDGET",
|
||||||
|
[INPUT_TYPES.DATE]: "WDS_DATEPICKER_WIDGET",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { WDSBaseInputWidget } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
|
import {
|
||||||
|
INPUT_TYPES,
|
||||||
|
WDSBaseInputWidget,
|
||||||
|
} from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
|
||||||
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
||||||
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
||||||
|
|
||||||
|
|
@ -14,4 +17,5 @@ export const defaultsConfig = {
|
||||||
showStepArrows: false,
|
showStepArrows: false,
|
||||||
label: "Current Price",
|
label: "Current Price",
|
||||||
responsiveBehavior: ResponsiveBehavior.Fill,
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
||||||
|
inputType: INPUT_TYPES.CURRENCY,
|
||||||
} as WidgetDefaultProps;
|
} as WidgetDefaultProps;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import type { AnvilConfig } from "WidgetProvider/constants";
|
||||||
|
|
||||||
|
export const anvilConfig: AnvilConfig = {
|
||||||
|
isLargeWidget: false,
|
||||||
|
widgetSize: {
|
||||||
|
minWidth: {
|
||||||
|
base: "100%",
|
||||||
|
"180px": "sizing-30",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
|
||||||
|
|
||||||
|
export const autocompleteConfig = {
|
||||||
|
"!doc":
|
||||||
|
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
|
||||||
|
"!url": "https://docs.appsmith.com/widget-reference/datepicker",
|
||||||
|
isVisible: DefaultAutocompleteDefinitions.isVisible,
|
||||||
|
selectedDate: "string",
|
||||||
|
formattedDate: "string",
|
||||||
|
isDisabled: "bool",
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
||||||
|
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
||||||
|
import { INPUT_TYPES } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
|
||||||
|
|
||||||
|
export const defaultsConfig = {
|
||||||
|
animateLoading: true,
|
||||||
|
label: "Label",
|
||||||
|
dateFormat: "YYYY-MM-DD HH:mm",
|
||||||
|
defaultOptionValue: "",
|
||||||
|
isRequired: false,
|
||||||
|
isDisabled: false,
|
||||||
|
isVisible: true,
|
||||||
|
isInline: false,
|
||||||
|
widgetName: "DatePicker",
|
||||||
|
widgetType: "WDS_DATE_PICKER",
|
||||||
|
version: 1,
|
||||||
|
timePrecision: "day",
|
||||||
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
||||||
|
inputType: INPUT_TYPES.DATE,
|
||||||
|
} as unknown as WidgetDefaultProps;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from "./propertyPaneConfig";
|
||||||
|
export { metaConfig } from "./metaConfig";
|
||||||
|
export { anvilConfig } from "./anvilConfig";
|
||||||
|
export { defaultsConfig } from "./defaultsConfig";
|
||||||
|
export { settersConfig } from "./settersConfig";
|
||||||
|
export { methodsConfig } from "./methodsConfig";
|
||||||
|
export { autocompleteConfig } from "./autocompleteConfig";
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||||
|
|
||||||
|
export const metaConfig = {
|
||||||
|
name: "DatePicker",
|
||||||
|
tags: [WIDGET_TAGS.INPUTS],
|
||||||
|
needsMeta: true,
|
||||||
|
searchTags: [
|
||||||
|
"datepicker",
|
||||||
|
"appointment",
|
||||||
|
"calendar",
|
||||||
|
"date",
|
||||||
|
"day",
|
||||||
|
"hour",
|
||||||
|
"meeting",
|
||||||
|
"moment",
|
||||||
|
"schedule",
|
||||||
|
"time",
|
||||||
|
"week",
|
||||||
|
"year",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { DatePickerIcon, DatePickerThumbnail } from "appsmith-icons";
|
||||||
|
|
||||||
|
export const methodsConfig = {
|
||||||
|
IconCmp: DatePickerIcon,
|
||||||
|
ThumbnailCmp: DatePickerThumbnail,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
import { ValidationTypes } from "constants/WidgetValidation";
|
||||||
|
import { DATE_FORMAT_OPTIONS } from "../../constants";
|
||||||
|
|
||||||
|
import { propertyPaneContentConfig as WdsInputWidgetPropertyPaneContentConfig } from "modules/ui-builder/ui/wds/WDSInputWidget/config/propertyPaneConfig/contentConfig";
|
||||||
|
import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||||
|
|
||||||
|
const inputTypeSectionConfig = WdsInputWidgetPropertyPaneContentConfig.find(
|
||||||
|
(config) => config.sectionName === "Type",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const propertyPaneContentConfig = [
|
||||||
|
inputTypeSectionConfig,
|
||||||
|
{
|
||||||
|
sectionName: "Data",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
helpText: "Sets the format of the selected date",
|
||||||
|
propertyName: "dateFormat",
|
||||||
|
label: "Date format",
|
||||||
|
controlType: "DROP_DOWN",
|
||||||
|
isJSConvertible: true,
|
||||||
|
optionWidth: "340px",
|
||||||
|
options: DATE_FORMAT_OPTIONS,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.TEXT },
|
||||||
|
hideSubText: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: "defaultDate",
|
||||||
|
label: "Default Date",
|
||||||
|
helpText:
|
||||||
|
"Sets the default date of the widget. The date is updated if the default date changes",
|
||||||
|
controlType: "DATE_PICKER",
|
||||||
|
placeholderText: "Enter Default Date",
|
||||||
|
useValidationMessage: true,
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.DATE_ISO_STRING },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: "timePrecision",
|
||||||
|
label: "Time Precision",
|
||||||
|
controlType: "DROP_DOWN",
|
||||||
|
helpText: "Sets the time precision or hides the time picker.",
|
||||||
|
defaultValue: "day",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Day",
|
||||||
|
value: "day",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Hour",
|
||||||
|
value: "hour",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Minute",
|
||||||
|
value: "minute",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Second",
|
||||||
|
value: "second",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: {
|
||||||
|
type: ValidationTypes.TEXT,
|
||||||
|
params: {
|
||||||
|
allowedValues: ["day", "hour", "minute", "second"],
|
||||||
|
default: "day",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sectionName: "Label",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
helpText: "Sets the label text of the date picker widget",
|
||||||
|
propertyName: "label",
|
||||||
|
label: "Text",
|
||||||
|
controlType: "INPUT_TEXT",
|
||||||
|
placeholderText: "Label",
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.TEXT },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sectionName: "Validations",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
propertyName: "isRequired",
|
||||||
|
label: "Required",
|
||||||
|
helpText: "Makes input to the widget mandatory",
|
||||||
|
controlType: "SWITCH",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.BOOLEAN },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: "minDate",
|
||||||
|
label: "Minimum Date",
|
||||||
|
helpText: "Sets the minimum date that can be selected",
|
||||||
|
controlType: "DATE_PICKER",
|
||||||
|
placeholderText: "Enter Minimum Date",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.DATE_ISO_STRING },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: "maxDate",
|
||||||
|
label: "Maximum Date",
|
||||||
|
helpText: "Sets the maximum date that can be selected",
|
||||||
|
controlType: "DATE_PICKER",
|
||||||
|
placeholderText: "Enter Maximum Date",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.DATE_ISO_STRING },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sectionName: "General",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
helpText: "Shows help text or details about the current input",
|
||||||
|
propertyName: "labelTooltip",
|
||||||
|
label: "Tooltip",
|
||||||
|
controlType: "INPUT_TEXT",
|
||||||
|
placeholderText: "",
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.TEXT },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
helpText: "Controls the visibility of the widget",
|
||||||
|
propertyName: "isVisible",
|
||||||
|
label: "Visible",
|
||||||
|
controlType: "SWITCH",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.BOOLEAN },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: "isDisabled",
|
||||||
|
label: "Disabled",
|
||||||
|
helpText: "Disables input to this widget",
|
||||||
|
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 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sectionName: "Events",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
propertyName: "onDateSelected",
|
||||||
|
label: "onDateSelected",
|
||||||
|
helpText: "when a date is selected in the calendar",
|
||||||
|
controlType: "ACTION_SELECTOR",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as PropertyPaneConfig[];
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { propertyPaneContentConfig } from "./contentConfig";
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export const settersConfig = {
|
||||||
|
__setters: {
|
||||||
|
setVisibility: {
|
||||||
|
path: "isVisible",
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
setDisabled: {
|
||||||
|
path: "isDisabled",
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import moment from "moment";
|
||||||
|
import { SubTextPosition } from "components/constants";
|
||||||
|
|
||||||
|
export const DATE_FORMAT_OPTIONS = [
|
||||||
|
{
|
||||||
|
label: moment().format("YYYY-MM-DDTHH:mm:ss.sssZ"),
|
||||||
|
subText: "ISO 8601",
|
||||||
|
value: "YYYY-MM-DDTHH:mm:ss.sssZ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("LLL"),
|
||||||
|
subText: "LLL",
|
||||||
|
value: "LLL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("LL"),
|
||||||
|
subText: "LL",
|
||||||
|
value: "LL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("YYYY-MM-DD HH:mm"),
|
||||||
|
subText: "YYYY-MM-DD HH:mm",
|
||||||
|
value: "YYYY-MM-DD HH:mm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("YYYY-MM-DDTHH:mm:ss"),
|
||||||
|
subText: "YYYY-MM-DDTHH:mm:ss",
|
||||||
|
value: "YYYY-MM-DDTHH:mm:ss",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("YYYY-MM-DD hh:mm:ss A"),
|
||||||
|
subText: "YYYY-MM-DD hh:mm:ss A",
|
||||||
|
value: "YYYY-MM-DD hh:mm:ss A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("DD/MM/YYYY HH:mm"),
|
||||||
|
subText: "DD/MM/YYYY HH:mm",
|
||||||
|
value: "DD/MM/YYYY HH:mm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("D MMMM, YYYY"),
|
||||||
|
subText: "D MMMM, YYYY",
|
||||||
|
value: "D MMMM, YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("H:mm A D MMMM, YYYY"),
|
||||||
|
subText: "H:mm A D MMMM, YYYY",
|
||||||
|
value: "H:mm A D MMMM, YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("YYYY-MM-DD"),
|
||||||
|
subText: "YYYY-MM-DD",
|
||||||
|
value: "YYYY-MM-DD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("MM-DD-YYYY"),
|
||||||
|
subText: "MM-DD-YYYY",
|
||||||
|
value: "MM-DD-YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("DD-MM-YYYY"),
|
||||||
|
subText: "DD-MM-YYYY",
|
||||||
|
value: "DD-MM-YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("MM/DD/YYYY"),
|
||||||
|
subText: "MM/DD/YYYY",
|
||||||
|
value: "MM/DD/YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("DD/MM/YYYY"),
|
||||||
|
subText: "DD/MM/YYYY",
|
||||||
|
value: "DD/MM/YYYY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("DD/MM/YY"),
|
||||||
|
subText: "DD/MM/YY",
|
||||||
|
value: "DD/MM/YY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: moment().format("MM/DD/YY"),
|
||||||
|
subText: "MM/DD/YY",
|
||||||
|
value: "MM/DD/YY",
|
||||||
|
},
|
||||||
|
].map((x) => ({
|
||||||
|
...x,
|
||||||
|
subTextPosition: SubTextPosition.BOTTOM,
|
||||||
|
}));
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { WDSDatePickerWidget } from "./widget";
|
||||||
|
|
||||||
|
export { WDSDatePickerWidget };
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
export default {
|
||||||
|
isValid: (props, moment) => {
|
||||||
|
const parsedMinDate = new Date(props.minDate);
|
||||||
|
const parsedMaxDate = new Date(props.maxDate);
|
||||||
|
const parsedSelectedDate = props.selectedDate
|
||||||
|
? moment(new Date(props.selectedDate))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// only do validation when the date is dirty
|
||||||
|
if (!props.isDirty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedSelectedDate && !props.isRequired) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedSelectedDate && props.isRequired) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.minDate && props.maxDate) {
|
||||||
|
return parsedSelectedDate.isBetween(parsedMinDate, parsedMaxDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.minDate) {
|
||||||
|
return parsedSelectedDate.isAfter(parsedMinDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.maxDate) {
|
||||||
|
return parsedSelectedDate.isBefore(parsedMaxDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import moment from "moment";
|
||||||
|
import derived from "./derived";
|
||||||
|
|
||||||
|
describe("isValid function", () => {
|
||||||
|
const mockMoment = (date: string) => moment(date);
|
||||||
|
|
||||||
|
it("should return true when isDirty is false", () => {
|
||||||
|
const props = { isDirty: false };
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when selectedDate is null and not required", () => {
|
||||||
|
const props = { isDirty: true, isRequired: false, selectedDate: null };
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when selectedDate is null and required", () => {
|
||||||
|
const props = { isDirty: true, isRequired: true, selectedDate: null };
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when selectedDate is between minDate and maxDate", () => {
|
||||||
|
const props = {
|
||||||
|
isDirty: true,
|
||||||
|
minDate: "2023-01-01",
|
||||||
|
maxDate: "2023-12-31",
|
||||||
|
selectedDate: "2023-06-15",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when selectedDate is before minDate", () => {
|
||||||
|
const props = {
|
||||||
|
isDirty: true,
|
||||||
|
minDate: "2023-01-01",
|
||||||
|
selectedDate: "2022-12-31",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when selectedDate is after maxDate", () => {
|
||||||
|
const props = {
|
||||||
|
isDirty: true,
|
||||||
|
maxDate: "2023-12-31",
|
||||||
|
selectedDate: "2024-01-01",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when selectedDate is valid and no min/max dates are set", () => {
|
||||||
|
const props = {
|
||||||
|
isDirty: true,
|
||||||
|
selectedDate: "2023-06-15",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(derived.isValid(props, mockMoment)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { WDSDatePickerWidgetProps } from "./types";
|
||||||
|
|
||||||
|
export function validateInput(props: WDSDatePickerWidgetProps) {
|
||||||
|
if (props.isValid === false) {
|
||||||
|
return {
|
||||||
|
validationStatus: "invalid",
|
||||||
|
errorMessage: "Please select a valid date",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
validationStatus: "valid",
|
||||||
|
errorMessage: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
import React from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
import BaseWidget from "widgets/BaseWidget";
|
||||||
|
import type { WidgetState } from "widgets/BaseWidget";
|
||||||
|
import type {
|
||||||
|
AnvilConfig,
|
||||||
|
AutocompletionDefinitions,
|
||||||
|
} from "WidgetProvider/constants";
|
||||||
|
import { parseDateTime } from "@internationalized/date";
|
||||||
|
import { DatePicker, type DateValue } from "@appsmith/wds";
|
||||||
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
|
|
||||||
|
import * as config from "../config";
|
||||||
|
import { validateInput } from "./helpers";
|
||||||
|
import derivedPropertyFns from "./derived";
|
||||||
|
import type { WDSDatePickerWidgetProps } from "./types";
|
||||||
|
import { parseDerivedProperties } from "widgets/WidgetUtils";
|
||||||
|
|
||||||
|
class WDSDatePickerWidget extends BaseWidget<
|
||||||
|
WDSDatePickerWidgetProps,
|
||||||
|
WidgetState
|
||||||
|
> {
|
||||||
|
static type = "WDS_DATEPICKER_WIDGET";
|
||||||
|
|
||||||
|
static getConfig() {
|
||||||
|
return config.metaConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaults() {
|
||||||
|
return config.defaultsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMethods() {
|
||||||
|
return config.methodsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAnvilConfig(): AnvilConfig | null {
|
||||||
|
return config.anvilConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAutocompleteDefinitions(): AutocompletionDefinitions {
|
||||||
|
return config.autocompleteConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPropertyPaneContentConfig() {
|
||||||
|
return config.propertyPaneContentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPropertyPaneStyleConfig() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedPropertiesMap() {
|
||||||
|
const parsedDerivedProperties = parseDerivedProperties(derivedPropertyFns);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: `{{(() => {${parsedDerivedProperties.isValid}})()}}`,
|
||||||
|
selectedDate: `{{ this.value ? moment(this.value).toISOString() : "" }}`,
|
||||||
|
formattedDate: `{{ this.value ? moment(this.value).format(this.dateFormat) : "" }}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultPropertiesMap(): Record<string, string> {
|
||||||
|
return {
|
||||||
|
value: "defaultDate",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMetaPropertiesMap() {
|
||||||
|
return {
|
||||||
|
value: undefined,
|
||||||
|
isDirty: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStylesheetConfig() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSetterConfig() {
|
||||||
|
return config.settersConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDependencyMap() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: WDSDatePickerWidgetProps): void {
|
||||||
|
if (!this.shouldResetDirtyState(prevProps)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetDirtyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDateChange = (date: DateValue) => {
|
||||||
|
if (!this.props.isDirty) {
|
||||||
|
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.updateWidgetMetaProperty("value", date.toString(), {
|
||||||
|
triggerPropertyName: "onDateSelected",
|
||||||
|
dynamicString: this.props.onDateSelected,
|
||||||
|
event: {
|
||||||
|
type: EventType.ON_DATE_SELECTED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private shouldResetDirtyState(prevProps: WDSDatePickerWidgetProps): boolean {
|
||||||
|
const { defaultDate, isDirty } = this.props;
|
||||||
|
const hasDefaultDateChanged = defaultDate !== prevProps.defaultDate;
|
||||||
|
|
||||||
|
return hasDefaultDateChanged && isDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetDirtyState() {
|
||||||
|
this.props.updateWidgetMetaProperty("isDirty", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseDate(date: string | undefined) {
|
||||||
|
return date
|
||||||
|
? parseDateTime(moment(date).format("YYYY-MM-DDTHH:mm:ss"))
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidgetView() {
|
||||||
|
const { label, labelTooltip, maxDate, minDate, value, ...rest } =
|
||||||
|
this.props;
|
||||||
|
const { errorMessage, validationStatus } = validateInput(this.props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
contextualHelp={labelTooltip}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
granularity={this.props.timePrecision}
|
||||||
|
isInvalid={validationStatus === "invalid"}
|
||||||
|
label={label}
|
||||||
|
maxValue={this.parseDate(maxDate)}
|
||||||
|
minValue={this.parseDate(minDate)}
|
||||||
|
onChange={this.handleDateChange}
|
||||||
|
value={this.parseDate(value)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WDSDatePickerWidget };
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import type { WidgetProps } from "widgets/BaseWidget";
|
||||||
|
|
||||||
|
export interface WDSDatePickerWidgetProps extends WidgetProps {
|
||||||
|
selectedDate: string;
|
||||||
|
defaultDate: string;
|
||||||
|
onDateSelected: string;
|
||||||
|
isRequired?: boolean;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
label: string;
|
||||||
|
labelTooltip?: string;
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,10 @@ export const propertyPaneContentConfig = [
|
||||||
label: "Currency",
|
label: "Currency",
|
||||||
value: "CURRENCY",
|
value: "CURRENCY",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Date",
|
||||||
|
value: "DATE",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
isBindProperty: false,
|
isBindProperty: false,
|
||||||
isTriggerProperty: false,
|
isTriggerProperty: false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { WDSBaseInputWidget } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
|
import {
|
||||||
|
INPUT_TYPES,
|
||||||
|
WDSBaseInputWidget,
|
||||||
|
} from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
|
||||||
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
||||||
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
||||||
|
|
||||||
|
|
@ -13,4 +16,5 @@ export const defaultsConfig = {
|
||||||
allowFormatting: true,
|
allowFormatting: true,
|
||||||
responsiveBehavior: ResponsiveBehavior.Fill,
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
||||||
label: "Phone number",
|
label: "Phone number",
|
||||||
|
inputType: INPUT_TYPES.PHONE_NUMBER,
|
||||||
} as WidgetDefaultProps;
|
} as WidgetDefaultProps;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ export const WDS_V2_WIDGET_MAP = {
|
||||||
MULTILINE_INPUT_WIDGET: "WDS_MULTILINE_INPUT_WIDGET",
|
MULTILINE_INPUT_WIDGET: "WDS_MULTILINE_INPUT_WIDGET",
|
||||||
WDS_SELECT_WIDGET: "WDS_SELECT_WIDGET",
|
WDS_SELECT_WIDGET: "WDS_SELECT_WIDGET",
|
||||||
WDS_COMBOBOX_WIDGET: "WDS_COMBOBOX_WIDGET",
|
WDS_COMBOBOX_WIDGET: "WDS_COMBOBOX_WIDGET",
|
||||||
|
WDS_DATEPICKER_WIDGET: "WDS_DATEPICKER_WIDGET",
|
||||||
|
|
||||||
// Anvil layout widgets
|
// Anvil layout widgets
|
||||||
ZONE_WIDGET: anvilWidgets.ZONE_WIDGET,
|
ZONE_WIDGET: anvilWidgets.ZONE_WIDGET,
|
||||||
|
|
|
||||||
|
|
@ -989,3 +989,48 @@ export const checkForOnClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the derived properties from the given property functions. Used in getDerivedPropertiesMap
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* isValidDate: (props, moment, _) => {
|
||||||
|
* return props.value === 1;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* It will return
|
||||||
|
* ```js
|
||||||
|
* {
|
||||||
|
* isValidDate: "{{ this.value === 1 }}"
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Main rule to remember is don't deconstruct the props like `const { value } = props;` in the derived property function.
|
||||||
|
* Directly access props like `props.value`
|
||||||
|
*/
|
||||||
|
export function parseDerivedProperties(propertyFns: Record<string, unknown>) {
|
||||||
|
const derivedProperties: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(propertyFns)) {
|
||||||
|
if (typeof value === "function") {
|
||||||
|
const functionString = value.toString();
|
||||||
|
const functionBody = functionString.match(/(?<=\{)(.|\n)*(?=\})/)?.[0];
|
||||||
|
|
||||||
|
if (functionBody) {
|
||||||
|
const paramMatch = functionString.match(/\((.*?),/);
|
||||||
|
const propsParam = paramMatch ? paramMatch[1].trim() : "props";
|
||||||
|
|
||||||
|
const modifiedBody = functionBody
|
||||||
|
.trim()
|
||||||
|
.replace(new RegExp(`${propsParam}\\.`, "g"), "this.");
|
||||||
|
|
||||||
|
derivedProperties[key] = modifiedBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return derivedProperties;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ import { WDSNumberInputWidget } from "modules/ui-builder/ui/wds/WDSNumberInputWi
|
||||||
import { WDSMultilineInputWidget } from "modules/ui-builder/ui/wds/WDSMultilineInputWidget";
|
import { WDSMultilineInputWidget } from "modules/ui-builder/ui/wds/WDSMultilineInputWidget";
|
||||||
import { WDSSelectWidget } from "modules/ui-builder/ui/wds/WDSSelectWidget";
|
import { WDSSelectWidget } from "modules/ui-builder/ui/wds/WDSSelectWidget";
|
||||||
import { EEWDSWidgets } from "ee/modules/ui-builder/ui/wds";
|
import { EEWDSWidgets } from "ee/modules/ui-builder/ui/wds";
|
||||||
|
import { WDSDatePickerWidget } from "modules/ui-builder/ui/wds/WDSDatePickerWidget";
|
||||||
|
|
||||||
const LegacyWidgets = [
|
const LegacyWidgets = [
|
||||||
CanvasWidget,
|
CanvasWidget,
|
||||||
|
|
@ -185,6 +186,7 @@ const WDSWidgets = [
|
||||||
WDSNumberInputWidget,
|
WDSNumberInputWidget,
|
||||||
WDSMultilineInputWidget,
|
WDSMultilineInputWidget,
|
||||||
WDSSelectWidget,
|
WDSSelectWidget,
|
||||||
|
WDSDatePickerWidget,
|
||||||
];
|
];
|
||||||
|
|
||||||
const Widgets = [
|
const Widgets = [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user