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

update packages and refactor DateTime component #140

Merged
merged 6 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@
"@mui/material": "^6.1.6",
"@mui/system": "^6.1.6",
"@mui/types": "^7.2.19",
"@mui/x-date-pickers": "5.0.20",
"@mui/x-date-pickers": "^7.22.2",
"attr-accept": "^2.2.5",
"chart.js": "4.4.6",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"date-fns": "^4.1.0",
"i18next": "^23.16.5",
"lodash": "^4.17.21",
"ramda": "^0.30.1",
Expand Down
22 changes: 11 additions & 11 deletions src/components/inputs/DateTime/DateTime.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import DateTime from './DateTime'
import { render, userClick } from 'testingUtils'
import DateTime from './DateTime'

const value = '2022-03-14 16:35:25.123'
const value = new Date('2022-03-14 16:35:25.123')

describe('Standard Date Picker', () => {
it('renders a Date component by default', () => {
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('Standard Date Picker', () => {
describe('Date Picker Formats', () => {
it('renders the date in french', () => {
// arrange
render(<DateTime showPicker="dateTime" value={value} format="fr" />)
render(<DateTime showPicker="dateTime" value={value} localeFormat="fr" />)

// act
const pickers = screen.getAllByDisplayValue('14/03/2022 16:35')
Expand All @@ -59,18 +59,18 @@ describe('Date Picker Formats', () => {

it('renders the date in united states', () => {
// arrange
render(<DateTime showPicker="dateTime" value={value} format="en-US" />)
render(<DateTime showPicker="dateTime" value={value} localeFormat="en-US" />)

// act
const picker = screen.getAllByDisplayValue('03/14/2022 04:35 pm')
const picker = screen.getAllByDisplayValue('03/14/2022 04:35 PM')

// assert
expect(picker).toHaveLength(1)
})

it('renders the date in romanian', () => {
// arrange
render(<DateTime showPicker="dateTime" value={value} format="ro" />)
render(<DateTime showPicker="dateTime" value={value} localeFormat="ro" />)

// act
const picker = screen.getAllByDisplayValue('14.03.2022 16:35')
Expand All @@ -83,22 +83,22 @@ describe('Date Picker Formats', () => {
describe('Date Time buttons work', () => {
it('clears the value', async () => {
// arrange
render(<DateTime value={value} isClearable={true} />)
render(<DateTime value={value} label="Clear" isClearable={true} />)
DCosti marked this conversation as resolved.
Show resolved Hide resolved

// act
await waitFor(() => fireEvent.click(screen.getByLabelText('Clear')))
await waitFor(() => fireEvent.click(screen.getByTitle('Clear')))

// assert
expect(() => screen.getAllByDisplayValue('14.03.2022')).toThrow()
})

it('opens dialog to choose date', async () => {
// arrange
render(<DateTime value={value} />)
render(<DateTime value={null} />)
expect(() => screen.getByRole('dialog')).toThrow()

// act
await waitFor(() => fireEvent.click(screen.getByLabelText('Open')))
await waitFor(() => fireEvent.click(screen.getByLabelText('Choose date')))

// assert
expect(() => screen.getByRole('dialog')).not.toThrow()
Expand All @@ -122,7 +122,7 @@ describe('Date Time helper text', () => {
it('displays helper text', () => {
// arrange
const helperText = 'Helper Text'
render(<DateTime value={value} helperText={helperText} />)
render(<DateTime value={value} slotProps={{ textField: { helperText } }} />)
DCosti marked this conversation as resolved.
Show resolved Hide resolved

// act
const helper = screen.getByText(helperText)
Expand Down
259 changes: 73 additions & 186 deletions src/components/inputs/DateTime/DateTime.tsx
Original file line number Diff line number Diff line change
@@ -1,166 +1,84 @@
import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react'
import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import {
TimePicker,
LocalizationProvider,
DatePicker,
DatePickerProps,
DatePickerSlotProps,
DatePickerSlots,
DateTimePicker,
DateTimePickerProps,
DateTimePickerSlotProps,
DateTimePickerSlots,
LocalizationProvider,
TimePicker,
TimePickerProps,
DatePickerProps
TimePickerSlotProps,
TimePickerSlots
} from '@mui/x-date-pickers'
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'
import { CalendarTodaySmallIcon } from './DateTimeStyles'
import * as R from 'ramda'
import ro from 'date-fns/locale/ro'
import fr from 'date-fns/locale/fr'
import ru from 'date-fns/locale/ru'
import de from 'date-fns/locale/de'
import enUS from 'date-fns/locale/en-US'
import DateTimeEndAdornment from './DateTimeEndAdornment'
import TextField, { TextFieldProps } from '../TextField'
import { T, DateTimeProps, CustomSlotsComponent } from './types'
import { SvgIconComponent } from '@mui/icons-material'
import { DateTimePickerSlotsComponent } from '@mui/x-date-pickers/DateTimePicker/DateTimePicker'
import { TimePickerSlotsComponent } from '@mui/x-date-pickers/TimePicker/TimePicker'
import { DatePickerSlotsComponent } from '@mui/x-date-pickers/DatePicker/DatePicker'

const localeMap = {
de: de,
['en-US']: enUS,
fr: fr,
ro: ro,
ru: ru
}
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'
import { de, enUS, fr, ro } from 'date-fns/locale'
import { DateTimeProps } from './types'
import { cond, equals } from 'ramda'

const defaultComponents = {
OpenPickerIcon: CalendarTodaySmallIcon
} as CustomSlotsComponent
const localeMap = { de, ['en-US']: enUS, fr, ro }

const DateTime: React.FC<DateTimeProps> = ({
const DateTime: React.FC<DateTimeProps<Date, string>> = ({
dateAdapter = AdapterDateFns,
adapterLocale,
value,
onChange,
isClearable = false,
showPicker = 'date',
components,
inputProps,
format = 'ro',
open: origOpen = false,
onClose,
value: origValue = null,
onChange: origOnChange,
isClearable,
required,
disabled,
error,
helperText,
slotProps = {},
slots = {},
localeFormat = 'ro',
...rest
}) => {
// Code to serve the "Open/Close" functionality
const [open, setOpen] = useState(origOpen)
useLayoutEffect(() => {
setOpen(origOpen)
}, [origOpen])
const locale = useMemo(() => adapterLocale ?? localeMap[localeFormat] ?? ro, [adapterLocale, localeFormat])
const commonSlotProps = useMemo(() => ({ field: { clearable: isClearable }, ...slotProps }), [isClearable, slotProps])

DCosti marked this conversation as resolved.
Show resolved Hide resolved
const handleOpen = useCallback(() => {
setOpen(true)
}, [])
const handleClose = useCallback(() => {
setOpen(false)
if (onClose) onClose()
}, [onClose])
// ---------------------------------------------

// Code to serve the "Clearable" functionality
const [value, setValue] = useState(origValue)
useLayoutEffect(() => {
setValue(origValue)
}, [origValue])
const handleChange = useCallback(
(value: T) => {
const changeValue = origOnChange ?? setValue
changeValue(value)
},
[origOnChange]
)
const internalIsClearable = useMemo(() => Boolean(isClearable) && Boolean(value), [isClearable, value])
const handleClear = useCallback(() => {
handleChange(null)
}, [handleChange])
// ---------------------------------------------

const mergedComponents = useMemo(() => R.mergeRight(defaultComponents, components), [components])

const renderInput = useCallback(
(params: Partial<TextFieldProps>) => {
const OpenPickerIcon = mergedComponents.OpenPickerIcon as SvgIconComponent
return (
<TextField
fullWidth
{...inputProps}
{...params}
required={required}
error={error}
helperText={helperText}
endAdornment={
<DateTimeEndAdornment
isClearable={internalIsClearable}
onClear={handleClear}
onOpen={handleOpen}
OpenPickerIcon={OpenPickerIcon}
disabled={disabled}
/>
}
const renderPicker = cond([
[
DCosti marked this conversation as resolved.
Show resolved Hide resolved
equals('date'),
() => (
<DatePicker
value={value}
onChange={onChange}
slotProps={commonSlotProps as DatePickerSlotProps<Date, false>}
slots={slots as DatePickerSlots<Date>}
{...(rest as DatePickerProps<Date>)}
/>
)
},
],
[
disabled,
error,
handleClear,
handleOpen,
helperText,
inputProps,
required,
internalIsClearable,
mergedComponents.OpenPickerIcon
equals('dateTime'),
() => (
<DateTimePicker
value={value}
onChange={onChange}
slotProps={commonSlotProps as DateTimePickerSlotProps<Date, false>}
slots={slots as DateTimePickerSlots<Date>}
{...(rest as DateTimePickerProps<Date>)}
/>
)
],
[
equals('time'),
() => (
<TimePicker
value={value}
onChange={onChange}
slotProps={commonSlotProps as TimePickerSlotProps<Date, false>}
slots={slots as TimePickerSlots<Date>}
{...(rest as TimePickerProps<Date>)}
/>
)
]
)

const localeUsed = useMemo(() => localeMap[format] ?? adapterLocale ?? localeMap.ro, [format, adapterLocale])

const commonProps = { renderInput, open, onClose: handleClose, value, onChange: handleChange, disabled }
const renderPicker = () => {
switch (showPicker) {
case 'dateTime':
return (
<DateTimePicker
components={mergedComponents as Partial<DateTimePickerSlotsComponent & CustomSlotsComponent>}
{...commonProps}
{...(rest as DateTimePickerProps<T, T>)}
/>
)
case 'time':
return (
<TimePicker
components={mergedComponents as Partial<TimePickerSlotsComponent & CustomSlotsComponent>}
{...commonProps}
{...(rest as TimePickerProps<T, T>)}
/>
)
])

default:
return (
<DatePicker
components={mergedComponents as Partial<DatePickerSlotsComponent & CustomSlotsComponent>}
{...commonProps}
{...(rest as DatePickerProps<T, T>)}
/>
)
}
}
return (
<LocalizationProvider dateAdapter={dateAdapter} adapterLocale={localeUsed}>
{renderPicker()}
<LocalizationProvider dateAdapter={dateAdapter} adapterLocale={locale}>
{renderPicker(showPicker)}
</LocalizationProvider>
)
}
Expand All @@ -177,66 +95,35 @@ DateTime.propTypes = {
* The adapterLocale object/string from the engine you use for displaying the date
*/
adapterLocale: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
/**
* Choose the type of picker you want displayed by the component
* @default 'date'
*/
showPicker: PropTypes.oneOf(['date', 'dateTime', 'time']),
/**
* The components used for each slot. Either a string to use a HTML element or a component.
*/
components: PropTypes.shape({
LeftArrowButton: PropTypes.any,
LeftArrowIcon: PropTypes.any,
OpenPickerIcon: PropTypes.any,
RightArrowButton: PropTypes.any,
RightArrowIcon: PropTypes.any,
SwitchViewButton: PropTypes.any,
SwitchViewIcon: PropTypes.any
}),
/**
* Properties that will be passed to the rendered input. This is a TextField.
*/
inputProps: PropTypes.object,
/**
* A small sample of ISO format names that will be used to display the date.
* @default 'ro'
*/
format: PropTypes.oneOf(['de', 'en-US', 'fr', 'ro', 'ru']),
/**
* @default null
* Value of the picker
*/
value: PropTypes.any,
value: PropTypes.instanceOf(Date),
/**
* Callback fired when the value (the selected date) changes @DateIOType.
*/
onChange: PropTypes.func,
/**
* Dedicated button for clearing the value
*/
isClearable: PropTypes.bool,
/**
* @default false
* Control the popup or dialog open state.
*/
open: PropTypes.bool,
/**
* Callback fired when the popup requests to be closed. Use in controlled mode (see open).
* @default 'date'
* Choose the type of picker you want displayed by the component
*/
onClose: PropTypes.func,
showPicker: PropTypes.oneOf(['date', 'dateTime', 'time']),
/**
* If true, the picker and text field are disabled.
* @default {}
* The props used for each component slot.
*/
disabled: PropTypes.bool,
slotProps: PropTypes.object,
/**
* If true, the label is displayed in an error state.
* @default {}
* Override component slots.
*/
error: PropTypes.bool,
slots: PropTypes.object,
/**
* The helper text content.
* @default 'ro'
* A small sample of ISO format names that will be used to display the date.
*/
helperText: PropTypes.node
localeFormat: PropTypes.oneOf(['de', 'en-US', 'fr', 'ro'])
}

export default DateTime
Loading
Loading