Skip to content

Commit

Permalink
feat(Field.Date): add DatePickerProps (#4160)
Browse files Browse the repository at this point in the history
- [x] Add documentation
- [x] Add some tests
- [x] Double check if any of the props needs accommodate for field props
- [x] Fix startMonth and endMonth bugs
- [x] Fix date correction test
- [x] Merge `DatePicker` camelCase changes

---------

Co-authored-by: Tobias Høegh <[email protected]>
Co-authored-by: Anders <[email protected]>
  • Loading branch information
3 people authored Nov 18, 2024
1 parent e541e22 commit 6a3765b
Show file tree
Hide file tree
Showing 7 changed files with 996 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ showTabs: true

import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { FieldEvents } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs'
import { DateEvents } from '@dnb/eufemia/src/extensions/forms/Field/Date/DateDocs'

## Date Events

<PropertiesTable props={DateEvents} />

## Events

Expand Down
111 changes: 54 additions & 57 deletions packages/dnb-eufemia/src/components/date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
Expand Down Expand Up @@ -56,11 +57,6 @@ import { DatePickerContextValues, DateType } from './DatePickerContext'
import { DatePickerDates } from './hooks/useDates'
import { useTranslation } from '../../shared'
import { convertSnakeCaseProps } from '../../shared/helpers/withSnakeCaseProps'
import {
TranslationsEnGB,
TranslationsEnUS,
TranslationsNbNO,
} from '../../shared/locales'

export type DatePickerEventAttributes = {
day?: string
Expand Down Expand Up @@ -546,11 +542,6 @@ export type DatePickerAllProps = DatePickerProps &
| 'start'
>

// Added to prevent type errors when destructuring the translations from props
type DatePickerTranslations = Partial<TranslationsNbNO['DatePicker']> &
Partial<TranslationsEnGB['DatePicker']> &
Partial<TranslationsEnUS['DatePicker']>

const defaultProps: DatePickerProps = {
maskOrder: 'dd/mm/yyyy',
maskPlaceholder: 'dd/mm/åååå', // have to be same setup as "mask" - but can be like
Expand Down Expand Up @@ -851,53 +842,10 @@ function DatePicker(externalProps: DatePickerAllProps) {
...restProps
} = extendedProps

let attributes = null

{
const {
locale,
id,
month,
date,
startDate,
endDate,
minDate,
maxDate,
enableKeyboardNav,
hideNavigation,
returnFormat,
dateFormat,
hideDays,
correctInvalidDate,
opened,
direction,
range,
showInput,
noAnimation,
onDaysRender,
onShow,
onType,
onHide,
showSubmitButton,
showCancelButton,
// These translations needs to be filtered out here, since the validateDOMAttributes
// is unable to filter out the translation props after they have been camelCased
selectedDate,
selectedMonth,
selectedYear,
nextMonth,
nextYear,
openPickerText,
placeholderCharacters,
prevMonth,
prevYear,
endMonth,
startMonth,

...rest
} = restProps as DatePickerAllProps & DatePickerTranslations
attributes = rest
}
const attributes = useMemo(
() => filterOutNonAttributes(restProps),
[restProps]
)

const shouldHideDays = onlyMonth ? true : hideDays
const shouldHideNavigation = onlyMonth
Expand Down Expand Up @@ -1086,6 +1034,55 @@ function DatePicker(externalProps: DatePickerAllProps) {
)
}

const NonAttributes = [
'locale',
'id',
'month',
'date',
'startDate',
'endDate',
'minDate',
'maxDate',
'enableKeyboardNav',
'hideNavigation',
'returnFormat',
'dateFormat',
'hideDays',
'correctInvalidDate',
'opened',
'direction',
'range',
'showInput',
'noAnimation',
'onDaysRender',
'onShow',
'onType',
'onHide',
'showSubmitButton',
'showCancelButton',
'selectedDate',
'selectedMonth',
'selectedYear',
'nextMonth',
'nextYear',
'openPickerText',
'placeholderCharacters',
'prevMonth',
'prevYear',
'endMonth',
'startMonth',
'alignPicker',
]

function filterOutNonAttributes(props: DatePickerProps) {
return Object.keys(props).reduce((attributes, key) => {
if (!NonAttributes.includes(key)) {
attributes[key] = props[key]
}
return attributes
}, {})
}

export default DatePicker

DatePicker._supportsSpacingProps = true
15 changes: 15 additions & 0 deletions packages/dnb-eufemia/src/components/date-picker/DatePickerDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export const DatePickerProperties: PropertiesTableProps = {
}

export const DatePickerEvents: PropertiesTableProps = {
onChange: {
doc: 'Will be called on a date change event. Returns an object. See Returned Object below.',
type: 'function',
status: 'optional',
},
onType: {
doc: 'Will be called on every input and date picker interaction. Returns an `object`. See Returned Object below.',
type: 'function',
Expand Down Expand Up @@ -272,4 +277,14 @@ export const DatePickerEvents: PropertiesTableProps = {
type: 'function',
status: 'optional',
},
onFocus: {
doc: 'Will be called once the input gets focus.',
type: 'function',
status: 'optional',
},
onBlur: {
doc: 'Will be called once the input lose focus.',
type: 'function',
status: 'optional',
},
}
147 changes: 134 additions & 13 deletions packages/dnb-eufemia/src/extensions/forms/Field/Date/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import FieldBlock, { Props as FieldBlockProps } from '../../FieldBlock'
import SharedContext from '../../../../shared/Context'
import { parseISO, isValid } from 'date-fns'
import useTranslation from '../../hooks/useTranslation'
import { DatePickerEvent } from '../../../../components/DatePicker'
import { formatDate } from '../../Value/Date'
import {
DatePickerEvent,
DatePickerProps,
} from '../../../../components/DatePicker'

// `range`, `showInput`, `showCancelButton` and `showResetButton` are not picked from the `DatePickerProps`
// Since they require `Field.Date` specific comments, due to them having different default values
export type Props = FieldProps<string, undefined | string> & {
// Validation
pattern?: string
Expand All @@ -19,8 +24,57 @@ export type Props = FieldProps<string, undefined | string> & {
* The value needs to be a string containing two dates, separated by a pipe character (`|`) i.e. (`01-09-2024|30-09-2024`) when this is set to `true`.
* Defaults to `false`.
*/
range?: boolean
}
range?: DatePickerProps['range']
/**
* If the input fields with the mask should be visible. Defaults to `true`.
*/
showInput?: DatePickerProps['showInput']

/**
* If set to `true`, a cancel button will be shown. You can change the default text by using `cancel_button_text="Avbryt"` Defaults to `true`. If the `range` prop is `true`, then the cancel button is shown.
*/
showCancelButton?: DatePickerProps['showCancelButton']
/**
* If set to `true`, a reset button will be shown. You can change the default text by using `reset_button_text="Tilbakestill"` Defaults to `true`.
*/
showResetButton?: DatePickerProps['showResetButton']
} & Pick<
DatePickerProps,
| 'month'
| 'startMonth'
| 'endMonth'
| 'minDate'
| 'maxDate'
| 'correctInvalidDate'
| 'maskOrder'
| 'maskPlaceholder'
| 'dateFormat'
| 'returnFormat'
| 'hideNavigation'
| 'hideDays'
| 'onlyMonth'
| 'hideLastWeek'
| 'disableAutofocus'
| 'showSubmitButton'
| 'submitButtonText'
| 'cancelButtonText'
| 'resetButtonText'
| 'firstDay'
| 'link'
| 'sync'
| 'addonElement'
| 'shortcuts'
| 'opened'
| 'direction'
| 'alignPicker'
| 'onDaysRender'
| 'onType'
| 'onShow'
| 'onHide'
| 'onSubmit'
| 'onCancel'
| 'onReset'
>

function DateComponent(props: Props) {
const translations = useTranslation()
Expand Down Expand Up @@ -82,8 +136,15 @@ function DateComponent(props: Props) {
handleChange,
setDisplayValue,
range,
showCancelButton = true,
showResetButton = true,
showInput = true,
onReset,
...rest
} = useFieldProps(preparedProps)

const datePickerProps = pickDatePickerProps(rest)

const { value, startDate, endDate } = useMemo(() => {
if (!range || !valueProp) {
return { value: valueProp, startDate: undefined, endDate: undefined }
Expand All @@ -101,10 +162,10 @@ function DateComponent(props: Props) {
}, [range, valueProp])

useMemo(() => {
if (path && value) {
setDisplayValue(path, formatDate(value, { locale }))
if (path && valueProp) {
setDisplayValue(path, formatDate(valueProp, { locale }))
}
}, [locale, path, setDisplayValue, value])
}, [locale, path, setDisplayValue, valueProp])

const fieldBlockProps: FieldBlockProps = {
forId: id,
Expand All @@ -119,22 +180,82 @@ function DateComponent(props: Props) {
id={id}
date={value}
disabled={disabled}
show_input={true}
show_cancel_button={true}
show_reset_button={true}
start_date={startDate}
end_date={endDate}
showInput={showInput}
showCancelButton={showCancelButton}
showResetButton={showResetButton}
startDate={startDate}
endDate={endDate}
status={hasError ? 'error' : undefined}
range={range}
on_change={handleChange}
on_reset={handleChange}
onChange={handleChange}
onReset={(event) => {
handleChange(event)
onReset?.(event)
}}
onFocus={handleFocus}
onBlur={handleBlur}
{...datePickerProps}
{...htmlAttributes}
/>
</FieldBlock>
)
}

// Used to filter out DatePickerProps from the FieldProps.
// Includes DatePickerProps that are not destructured in useFieldProps
const datePickerPropKeys = [
'month',
'startMonth',
'endMonth',
'minDate',
'maxDate',
'correctInvalidDate',
'maskOrder',
'maskPlaceholder',
'dateFormat',
'returnFormat',
'hideNavigation',
'hideDays',
'onlyMonth',
'hideLastWeek',
'disableAutofocus',
'showSubmitButton',
'submitButtonText',
'cancelButtonText',
'resetButtonText',
'firstDay',
'link',
'sync',
'addonElement',
'shortcuts',
'opened',
'direction',
'alignPicker',
'onDaysRender',
'showInput',
'onDaysRender',
'onType',
'onShow',
'onHide',
'onSubmit',
'onCancel',
'onReset',
]

function pickDatePickerProps(props: Props) {
const datePickerProps = Object.keys(props).reduce(
(datePickerProps, key) => {
if (datePickerPropKeys.includes(key)) {
datePickerProps[key] = props[key]
}

return datePickerProps
},
{}
)

return datePickerProps
}

DateComponent._supportsSpacingProps = true
export default DateComponent
Loading

0 comments on commit 6a3765b

Please sign in to comment.