Skip to content

Commit beb059e

Browse files
feat(date-time-picker-web): implement useSetupProps hook and date-utils for date picker
1 parent 3419d5e commit beb059e

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
2+
import { DatePickerProps } from "react-datepicker";
3+
import { DateTimePickerContainerProps, TypeEnum } from "typings/DateTimePickerProps";
4+
import { MXSessionLocale } from "../../typings/global";
5+
import { DatePickerController } from "../helpers/DatePickerController";
6+
import { getLocale, pickerDateFormat, setupLocales } from "../utils/date-utils";
7+
8+
export type Day = 0 | 1 | 2 | 3 | 4 | 5 | 6;
9+
10+
export function useSetupProps(
11+
{
12+
tabIndex,
13+
type,
14+
dateFormat,
15+
timeFormat,
16+
dateTimeFormat,
17+
placeholder,
18+
dateAttribute,
19+
editable,
20+
ariaRequired,
21+
onEnter,
22+
onLeave
23+
}: DateTimePickerContainerProps,
24+
controller: DatePickerController
25+
): DatePickerProps {
26+
const id = `DateFilter${generateUUID()}`;
27+
const locale = getLocale();
28+
const calendarStartDay = locale.firstDayOfWeek as Day;
29+
const calendarLocale = setupLocales(locale);
30+
31+
const formatProps = formatPropsBuilder(
32+
type,
33+
dateFormat,
34+
timeFormat,
35+
dateTimeFormat,
36+
dateAttribute.value ?? new Date(),
37+
null,
38+
locale
39+
);
40+
41+
const popperProps: Pick<
42+
DatePickerProps,
43+
"popperPlacement" | "popperProps" | "showPopperArrow" | "popperModifiers"
44+
> = {
45+
popperPlacement: "bottom-start",
46+
popperProps: {
47+
strategy: "fixed",
48+
transform: false
49+
},
50+
showPopperArrow: false,
51+
popperModifiers: [
52+
{
53+
name: "computeStyles",
54+
options: {
55+
gpuAcceleration: false
56+
},
57+
fn: () => ({})
58+
}
59+
]
60+
};
61+
62+
return {
63+
// Static props
64+
allowSameDay: false,
65+
autoFocus: false,
66+
calendarClassName: "widget-datetimepicker-calendar",
67+
className: "widget-datetimepicker-input",
68+
dropdownMode: "select",
69+
enableTabLoop: true,
70+
shouldCloseOnSelect: false,
71+
showMonthDropdown: true,
72+
showYearDropdown: true,
73+
strictParsing: true,
74+
useWeekdaysShort: false,
75+
76+
// Base props
77+
ariaLabelledBy: `${id}-label`,
78+
ariaRequired: ariaRequired.toString(),
79+
disabled: editable === "never",
80+
locale: calendarLocale,
81+
placeholderText: placeholder?.status === "available" ? placeholder.value : "",
82+
tabIndex: tabIndex ?? 0,
83+
84+
// Formatting props
85+
calendarStartDay,
86+
...formatProps,
87+
88+
// Events props
89+
onBlur: () => onLeave?.canExecute && !onLeave.isExecuting && onLeave.execute(),
90+
onCalendarClose: controller.handleCalendarClose,
91+
onCalendarOpen: controller.handleCalendarOpen,
92+
onChange: controller.handlePickerChange,
93+
onChangeRaw: controller.UNSAFE_handleChangeRaw,
94+
onFocus: () => onEnter?.canExecute && !onEnter.isExecuting && onEnter.execute(),
95+
onKeyDown: controller.handleKeyDown,
96+
97+
// Popper props
98+
...popperProps
99+
};
100+
}
101+
102+
function formatPropsBuilder(
103+
type: TypeEnum,
104+
dateFormat: string,
105+
timeFormat: string,
106+
dateTimeFormat: string,
107+
date: Date,
108+
endDate: Date | null,
109+
locale: MXSessionLocale
110+
): Omit<DatePickerProps, "onChange"> {
111+
switch (type) {
112+
case "date":
113+
return {
114+
dateFormat: dateFormat || pickerDateFormat(locale),
115+
showTimeSelect: false,
116+
showTimeSelectOnly: false,
117+
selected: date
118+
};
119+
case "time":
120+
return {
121+
dateFormat: timeFormat || "h:mm aa",
122+
showTimeSelect: true,
123+
showTimeSelectOnly: true,
124+
timeIntervals: 15,
125+
timeCaption: "Time",
126+
selected: date
127+
};
128+
case "datetime":
129+
return {
130+
dateFormat: dateTimeFormat || pickerDateFormat(locale),
131+
showTimeSelect: true,
132+
timeIntervals: 15,
133+
timeCaption: "Time",
134+
timeFormat: timeFormat || "h:mm aa",
135+
selected: date
136+
};
137+
case "range":
138+
return {
139+
dateFormat: dateFormat || pickerDateFormat(locale),
140+
selected: date,
141+
isClearable: true,
142+
selectsRange: true,
143+
startDate: date,
144+
endDate: endDate
145+
};
146+
}
147+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as locales from "date-fns/locale";
2+
import { registerLocale } from "react-datepicker";
3+
import { MXSessionLocale } from "../../typings/global";
4+
5+
/**
6+
* This function takes format string and replace
7+
* single "d" and "M" with their "double" equivalent
8+
* Main purpose is to "fix" value formats accepted
9+
* by DateFilter input. Idea is that if format string is
10+
* uses short format (e.g d-M-yyyy) user should still
11+
* be able to type value with leading zeros "09-02-2002".
12+
*
13+
* Example:
14+
* "d/M/yyyy" -> "dd/MM/yyyy"
15+
*
16+
* @param formatString
17+
*/
18+
export function doubleMonthOrDayWhenSingle(formatString: string): string {
19+
return formatString.replaceAll(/d+|M+/g, m => (m.length > 1 ? m : m + m));
20+
}
21+
22+
/**
23+
* Given a date formatter string replaces all uppercase 'E' to the lowercase 'e'
24+
* this function returns the same string with the above characters replaced.
25+
* Example: "YYww.E" returns "YYww.e"
26+
* @function dayOfWeekWhenUpperCase
27+
* @param {string} formatString The string used to find cases of uppercase E.
28+
* @return {string} The same string but with 'E' in lowercase ('e').
29+
*
30+
*/
31+
export function dayOfWeekWhenUpperCase(formatString: string): string {
32+
return formatString.replaceAll(/E/g, m => m.toLowerCase());
33+
}
34+
35+
/**
36+
* Map current app date format to date picker date format(s).
37+
* @returns {string|string[]}
38+
*/
39+
export function pickerDateFormat(locale: MXSessionLocale): string | string[] {
40+
const {
41+
patterns: { date: appDateFormat }
42+
} = locale;
43+
let dateFormat: string | string[];
44+
// Replace with full patterns d -> dd, M -> MM
45+
dateFormat = doubleMonthOrDayWhenSingle(appDateFormat);
46+
// Replace Date format E to follow unicode standard (e...eeee)
47+
dateFormat = dayOfWeekWhenUpperCase(dateFormat);
48+
// Use multiple formats if formats are not equal
49+
return dateFormat === appDateFormat ? dateFormat : [dateFormat, appDateFormat];
50+
}
51+
52+
interface DateFilterLocale {
53+
[key: string]: locales.Locale;
54+
}
55+
56+
/**
57+
* Reg current locale in datepicker config.
58+
* Later this locale can be passed to datepicker as locale prop.
59+
* @returns {string} registered locale.
60+
*/
61+
export function setupLocales(locale: MXSessionLocale): string {
62+
const { languageTag = "en-US" } = locale;
63+
64+
const [language] = languageTag.split("-");
65+
const languageTagWithoutDash = languageTag.replace("-", "");
66+
67+
if (languageTagWithoutDash in locales) {
68+
registerLocale(language, (locales as DateFilterLocale)[languageTagWithoutDash]);
69+
} else if (language in locales) {
70+
registerLocale(language, (locales as DateFilterLocale)[language]);
71+
}
72+
73+
return language;
74+
}
75+
76+
export function getLocale(): MXSessionLocale {
77+
return window.mx
78+
? window.mx.session.getConfig().locale
79+
: {
80+
languageTag: "en-US",
81+
code: "en_US",
82+
firstDayOfWeek: 0,
83+
patterns: {
84+
date: "M/d/yyyy",
85+
datetime: "M/d/yyyy, h:mm a",
86+
time: "h:mm a"
87+
}
88+
};
89+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export interface MXLocalePatterns {
2+
date: string;
3+
datetime: string;
4+
time: string;
5+
}
6+
7+
export interface MXSessionLocale {
8+
code: string;
9+
firstDayOfWeek: number;
10+
languageTag: string;
11+
patterns: MXLocalePatterns;
12+
}
13+
14+
export interface MXSessionConfig {
15+
locale: MXSessionLocale;
16+
}
17+
18+
export interface MXSession {
19+
getConfig(): MXSessionConfig;
20+
}
21+
22+
export interface MXGlobalObject {
23+
session: MXSession;
24+
}
25+
26+
declare global {
27+
interface Window {
28+
mx?: MXGlobalObject;
29+
}
30+
}

0 commit comments

Comments
 (0)