diff --git a/packages/react/src/components/DatePicker/DatePicker-test.js b/packages/react/src/components/DatePicker/DatePicker-test.js index b90f9212711b..54245fce6158 100644 --- a/packages/react/src/components/DatePicker/DatePicker-test.js +++ b/packages/react/src/components/DatePicker/DatePicker-test.js @@ -273,7 +273,7 @@ describe('Single date picker', () => { it('should support controlled value', async () => { const DatePickerExample = () => { - const [date, setDate] = useState(''); + const [date, setDate] = useState(); return ( <> { @@ -183,6 +186,214 @@ function updateClassNames(calendar, prefix) { }); } } +type ExcludedAttributes = 'value' | 'onChange' | 'locale'; +export type DatePickerTypes = 'simple' | 'single' | 'range'; +export type CalRef = { + inline: boolean; + disableMobile: boolean; + defaultDate: Date; + closeOnSelect: (evt: React.ChangeEvent) => void; + mode: 'simple' | 'single' | 'range'; + allowInput: boolean; + dateFormat: string; + locale: string; + plugins: []; + clickOpens: any; +}; +interface DatePickerProps + extends Omit, ExcludedAttributes> { + /** + * flatpickr prop passthrough. Allows the user to enter a date directly + * into the input field + */ + allowInput: boolean | undefined; + + /** + * The DOM element the flatpickr should be inserted into `` by default. + */ + appendTo: object | undefined; + + /** + * The child nodes. + */ + children: React.ReactNode | object; + + /** + * The CSS class names. + */ + className: string | undefined; + + /** + * flatpickr prop passthrough. Controls whether the calendar dropdown closes upon selection. + */ + closeOnSelect: boolean | undefined; + + /** + * The date format. + */ + dateFormat?: string; + + /** + * The type of the date picker: + * + * * `simple` - Without calendar dropdown. + * * `single` - With calendar dropdown and single date. + * * `range` - With calendar dropdown and a date range. + */ + datePickerType?: DatePickerTypes; + + /** + * The flatpickr `disable` option that allows a user to disable certain dates. + */ + disable?: string[]; + + /** + * The flatpickr `enable` option that allows a user to enable certain dates. + */ + enable?: string[]; + + /** + * The flatpickr `inline` option. + */ + inline: boolean | undefined; + + /** + * Specify whether or not the control is invalid (Fluid only) + */ + invalid: boolean | undefined; + + /** + * Provide the text that is displayed when the control is in error state (Fluid Only) + */ + invalidText: ReactNodeLike; + + /** + * `true` to use the light version. + */ + light: boolean; + + /** + * The language locale used to format the days of the week, months, and numbers. The full list of supported locales can be found here https://github.com/flatpickr/flatpickr/tree/master/src/l10n + */ + locale?: + | string + | any + | 'ar' // Arabic + | 'at' // Austria + | 'az' // Azerbaijan + | 'be' // Belarusian + | 'bg' // Bulgarian + | 'bn' // Bangla + | 'bs' // Bosnia + | 'cat' // Catalan + | 'cs' // Czech + | 'cy' // Welsh + | 'da' // Danish + | 'de' // German + | 'en' // English + | 'eo' // Esperanto + | 'es' // Spanish + | 'et' // Estonian + | 'fa' // Persian + | 'fi' // Finnish + | 'fo' // Faroese + | 'fr' // French + | 'ga' // Gaelic + | 'gr' // Greek + | 'he' // Hebrew + | 'hi' // Hindi + | 'hr' // Croatian + | 'hu' // Hungarian + | 'id' // Indonesian + | 'is' // Icelandic + | 'it' // Italian + | 'ja' // Japanese + | 'ka' // Georgian + | 'km' // Khmer + | 'ko' // Korean + | 'kz' // Kazakh + | 'lt' // Lithuanian + | 'lv' // Latvian + | 'mk' // Macedonian + | 'mn' // Mongolian + | 'ms' // Malaysian + | 'my' // Burmese + | 'nl' // Dutch + | 'no' // Norwegian + | 'pa' // Punjabi + | 'pl' // Polish + | 'pt' // Portuguese + | 'ro' // Romanian + | 'ru' // Russian + | 'si' // Sinhala + | 'sk' // Slovak + | 'sl' // Slovenian + | 'sq' // Albanian + | 'sr' // Serbian + | 'sv' // Swedish + | 'th' // Thai + | 'tr' // Turkish + | 'uk' // Ukrainian + | 'uz' // Uzbek + | 'uz_latn' // Uzbek Latin + | 'vn' // Vietnamese + | 'zh_tw' // Mandarin Traditional + | 'zh' + | undefined; // Mandarin; + + /** + * The maximum date that a user can pick to. + */ + maxDate?: string; + + /** + * The minimum date that a user can start picking from. + */ + minDate?: string; + + /** + * The `change` event handler. + */ + onChange?: ChangeEventHandler; + + /** + * The `close` event handler. + */ + onClose?: any; + + /** + * The `open` event handler. + */ + onOpen?: ChangeEventHandler; + + /** + * whether the DatePicker is to be readOnly + * if boolean applies to all inputs + * if array applies to each input in order + */ + readOnly?: boolean | [] | any | undefined; + + /** + * `true` to use the short version. + */ + short?: boolean; + + /** + * The value of the date value provided to flatpickr, could + * be a date, a date number, a date string, an array of dates. + */ + value?: string | number | (string | number | object)[] | object | undefined; + + /** + * Specify whether the control is currently in warning state (Fluid only) + */ + warn?: boolean; + + /** + * Provide the text that is displayed when the control is in warning state (Fluid only) + */ + warnText: ReactNodeLike; +} const DatePicker = React.forwardRef(function DatePicker( { @@ -211,19 +422,18 @@ const DatePicker = React.forwardRef(function DatePicker( short = false, value, ...rest - }, - ref + }: DatePickerProps, + ref: ForwardedRef ) { const prefix = usePrefix(); const { isFluid } = useContext(FormContext); const [hasInput, setHasInput] = useState(false); - const startInputField = useCallback((node) => { + const startInputField: any = useCallback((node) => { if (node !== null) { startInputField.current = node; setHasInput(true); } }, []); - const endInputField = useRef(null); const lastStartValue = useRef(''); @@ -237,7 +447,7 @@ const DatePicker = React.forwardRef(function DatePicker( ) { startInputField.current.value = lastStartValue.current; calendarRef.current.setDate( - [startInputField.current.value, endInputField.current.value], + [startInputField.current.value, endInputField?.current?.value], true, calendarRef.current.config.dateFormat ); @@ -252,12 +462,14 @@ const DatePicker = React.forwardRef(function DatePicker( }); }; - const calendarRef = useRef(null); - const savedOnChange = useSavedCallback(onChange); + //const savedOnOpen = useSavedCallback(onOpen); + const endInputField = useRef(null); + const calendarRef: any | undefined = useRef(null); + const savedOnChange = useSavedCallback(() => onChange); const savedOnClose = useSavedCallback( datePickerType === 'range' ? onCalendarClose : onClose ); - const savedOnOpen = useSavedCallback(onOpen); + const savedOnOpen = useSavedCallback(() => onOpen); const datePickerClasses = cx(`${prefix}--date-picker`, { [`${prefix}--date-picker--short`]: short, @@ -268,10 +480,12 @@ const DatePicker = React.forwardRef(function DatePicker( [`${prefix}--date-picker--nolabel`]: datePickerType === 'range' && isLabelTextEmpty(children), }); - const wrapperClasses = cx(`${prefix}--form-item`, { [className]: className }); + const wrapperClasses = cx(`${prefix}--form-item`, { + [String(className)]: className, + }); const childrenWithProps = React.Children.toArray(children).map( - (child, index) => { + (child: any, index) => { if ( index === 0 && child.type === React.createElement(DatePickerInput, child.props).type @@ -328,7 +542,7 @@ const DatePicker = React.forwardRef(function DatePicker( // Logic to determine if `enable` or `disable` will be passed down. If neither // is provided, we return the default empty disabled array, allowing all dates. - let enableOrDisable = enable ? 'enable' : 'disable'; + const enableOrDisable = enable ? 'enable' : 'disable'; let enableOrDisableArr; if (!enable && !disable) { enableOrDisableArr = []; @@ -340,7 +554,7 @@ const DatePicker = React.forwardRef(function DatePicker( let localeData; if (typeof locale === 'object') { - let location = locale.locale ? locale.locale : 'en'; + const location = locale.locale ? locale.locale : 'en'; localeData = { ...l10n[location], ...locale }; } else { localeData = l10n[locale]; @@ -348,7 +562,7 @@ const DatePicker = React.forwardRef(function DatePicker( const { current: start } = startInputField; const { current: end } = endInputField; - const calendar = new flatpickr(start, { + const flatpickerconfig: any = { inline: inline ?? false, disableMobile: true, defaultDate: value, @@ -396,12 +610,13 @@ const DatePicker = React.forwardRef(function DatePicker( onReady: onHook, onMonthChange: onHook, onYearChange: onHook, - onOpen: (...args) => { + onOpen: (...args: [any, string, string, string]) => { onHook(...args); savedOnOpen(...args); }, onValueUpdate: onHook, - }); + }; + const calendar = flatpickr(start, flatpickerconfig); calendarRef.current = calendar; @@ -421,10 +636,10 @@ const DatePicker = React.forwardRef(function DatePicker( const todayDateElem = calendarContainer.querySelector('.today') && fptodayDateElem; ( - selectedDateElem || - todayDateElem || - calendarContainer.querySelector('.flatpickr-day[tabindex]') || - calendarContainer + (selectedDateElem || + todayDateElem || + calendarContainer.querySelector('.flatpickr-day[tabindex]') || + calendarContainer) as HTMLElement ).focus(); } } @@ -511,7 +726,7 @@ const DatePicker = React.forwardRef(function DatePicker( // this hook allows consumers to access the flatpickr calendar // instance for cases where functions like open() or close() // need to be imperatively called on the calendar - useImperativeHandle(ref, () => ({ + useImperativeHandle(ref, (): any => ({ get calendar() { return calendarRef.current; }, diff --git a/packages/react/src/components/DatePickerInput/DatePickerInput.js b/packages/react/src/components/DatePickerInput/DatePickerInput.tsx similarity index 72% rename from packages/react/src/components/DatePickerInput/DatePickerInput.js rename to packages/react/src/components/DatePickerInput/DatePickerInput.tsx index ad2cd3c45595..f69f5a06763a 100644 --- a/packages/react/src/components/DatePickerInput/DatePickerInput.js +++ b/packages/react/src/components/DatePickerInput/DatePickerInput.tsx @@ -7,15 +7,129 @@ import { Calendar, WarningFilled, WarningAltFilled } from '@carbon/icons-react'; import cx from 'classnames'; -import PropTypes from 'prop-types'; -import React, { useContext } from 'react'; +import PropTypes, { ReactElementLike, ReactNodeArray } from 'prop-types'; +import React, { ForwardedRef, useContext } from 'react'; import { usePrefix } from '../../internal/usePrefix'; import { FormContext } from '../FluidForm'; import setupGetInstanceId from '../../tools/setupGetInstanceId'; +import { ReactAttr } from '../../types/common'; const getInstanceId = setupGetInstanceId(); +type ExcludedAttributes = 'value' | 'onChange' | 'locale' | 'children'; +export type ReactNodeLike = + | ReactElementLike + | ReactNodeArray + | string + | number + | boolean + | null + | undefined; + +export type func = (...args: any[]) => any; + +interface DatePickerInputProps + extends Omit, ExcludedAttributes> { + /** + * The type of the date picker: + * + * * `simple` - Without calendar dropdown. + * * `single` - With calendar dropdown and single date. + * * `range` - With calendar dropdown and a date range. + */ + datePickerType?: 'simple' | 'single' | 'range'; + + /** + * Specify whether or not the input should be disabled + */ + disabled?: boolean; + + /** + * Provide text that is used alongside the control label for additional help + */ + helperText?: ReactNodeLike; + + /** + * Specify if the label should be hidden + */ + hideLabel?: boolean; + + /** + * Specify an id that uniquely identifies the `` + */ + id: string; + + /** + * Specify whether or not the input should be invalid + */ + invalid?: boolean; + + /** + * Specify the text to be rendered when the input is invalid + */ + invalidText: ReactNodeLike; + + /** + * Provide the text that will be read by a screen reader when visiting this + * control + */ + labelText: ReactNodeLike; + + /** + * Specify an `onChange` handler that is called whenever a change in the + * input field has occurred + */ + onChange?: func; + + /** + * Provide a function to be called when the input field is clicked + */ + onClick?: func; + + /** + * Provide a regular expression that the input value must match + * TODO:need to be rewritten + */ + pattern: ( + props: { [key: string]: any }, + propName: string, + componentName: string + ) => null | any | Error; + + /** + * Specify the placeholder text + */ + placeholder?: string; + + /** + * whether the DatePicker is to be readOnly + */ + readOnly?: boolean; + + /** + * Specify the size of the Date Picker Input. Currently supports either `sm`, `md`, or `lg` as an option. + */ + size?: 'sm' | 'md' | 'lg'; + + /** + * Specify the type of the `` + */ + type?: string; + + /** + * Specify whether the control is currently in warning state + */ + warn?: boolean; + + /** + * Provide the text that is displayed when the control is in warning state + */ + warnText?: ReactNodeLike; +} -const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) { +const DatePickerInput = React.forwardRef(function DatePickerInput( + props: DatePickerInputProps, + ref: ForwardedRef +) { const { datePickerType, disabled = false, @@ -81,7 +195,7 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) { ? undefined : `detepicker-input-helper-text-${datePickerInputInstanceId}`; - const inputProps = { + const inputProps: any = { ...rest, ...datePickerInputProps, className: inputClasses, @@ -191,7 +305,7 @@ DatePickerInput.propTypes = { /** * Provide a regular expression that the input value must match */ - pattern: (props, propName, componentName) => { + pattern: (props, propName, componentName): null | any | Error => { if (props[propName] === undefined) { return; }