-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [M3-8611]- New DatePicker Component (#11151)
* unit test coverage for HostNameTableCell * Revert "unit test coverage for HostNameTableCell" This reverts commit b274baf. * chore: [M3-8662] - Update Github Actions actions (#11009) * update actions * add changeset --------- Co-authored-by: Banks Nussman <banks@nussman.us> * Basci date picker component * Test coverage for date picker component * DatePicker Stories * Custom DateTimePicker component * Reusable TimeZone Select Component * Create custom DateTimeRangePicker component * Storybook for DateTimePicker * Fix tests and remove console warnings * changeset * Update packages/manager/src/components/DatePicker/DateTimeRangePicker.tsx Co-authored-by: Connie Liu <139280159+coliu-akamai@users.noreply.github.com> * Adjust styles for DatePicker * Adjust styles for DateTimePicker * update imports * Render time and timezone conditionally in DateTimePicker component * Move DatePicker to UI package * Add DatePicker dependencies * Code cleanup * PR feedback * code cleanup * Move DatePicker back to src/components * Reverting changes * Code cleanup * Adjust broken tests * Update TimeZoneSelect.tsx * Code cleanup * Add validation for start date agains end date. * Adjust styles for TimePicker component. * PR feedback - @jaalah-akamai * allow error messages from props. * Update storybook components with args * Update props * PR feedback - @hana-akamai --------- Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> Co-authored-by: Banks Nussman <banks@nussman.us> Co-authored-by: Connie Liu <139280159+coliu-akamai@users.noreply.github.com>
- Loading branch information
1 parent
4f19394
commit 518d85d
Showing
12 changed files
with
1,287 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Added | ||
--- | ||
|
||
New DatePicker Component ([#11151](https://github.com/linode/manager/pull/11151)) |
124 changes: 124 additions & 0 deletions
124
packages/manager/src/components/DatePicker/DatePicker.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { action } from '@storybook/addon-actions'; | ||
import * as React from 'react'; | ||
|
||
import { DatePicker } from './DatePicker'; | ||
|
||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import type { DateTime } from 'luxon'; | ||
|
||
type Story = StoryObj<typeof DatePicker>; | ||
|
||
export const Default: Story = { | ||
argTypes: { | ||
errorText: { | ||
control: 'text', | ||
description: 'Error text to display below the input', | ||
}, | ||
format: { | ||
control: 'text', | ||
description: 'Format of the date when rendered in the input field', | ||
}, | ||
helperText: { | ||
control: 'text', | ||
description: 'Helper text to display below the input', | ||
}, | ||
label: { | ||
control: 'text', | ||
description: 'Label to display for the date picker input', | ||
}, | ||
onChange: { | ||
action: 'date-changed', | ||
description: 'Callback function fired when the value changes', | ||
}, | ||
placeholder: { | ||
control: 'text', | ||
description: 'Placeholder text for the date picker input', | ||
}, | ||
textFieldProps: { | ||
control: 'object', | ||
description: | ||
'Additional props to pass to the underlying TextField component', | ||
}, | ||
value: { | ||
control: 'date', | ||
description: 'The currently selected date', | ||
}, | ||
}, | ||
args: { | ||
errorText: '', | ||
format: 'yyyy-MM-dd', | ||
label: 'Select a Date', | ||
onChange: action('date-changed'), | ||
placeholder: 'yyyy-MM-dd', | ||
textFieldProps: { label: 'Select a Date' }, | ||
value: null, | ||
}, | ||
}; | ||
|
||
export const ControlledExample: Story = { | ||
args: { | ||
errorText: '', | ||
format: 'yyyy-MM-dd', | ||
helperText: 'This is a controlled DatePicker', | ||
label: 'Controlled Date Picker', | ||
placeholder: 'yyyy-MM-dd', | ||
value: null, | ||
}, | ||
render: (args) => { | ||
const ControlledDatePicker = () => { | ||
const [selectedDate, setSelectedDate] = React.useState<DateTime | null>(); | ||
|
||
const handleChange = (newDate: DateTime | null) => { | ||
setSelectedDate(newDate); | ||
action('Controlled date change')(newDate?.toISO()); | ||
}; | ||
|
||
return ( | ||
<DatePicker {...args} onChange={handleChange} value={selectedDate} /> | ||
); | ||
}; | ||
|
||
return <ControlledDatePicker />; | ||
}, | ||
}; | ||
|
||
const meta: Meta<typeof DatePicker> = { | ||
argTypes: { | ||
errorText: { | ||
control: 'text', | ||
}, | ||
format: { | ||
control: 'text', | ||
}, | ||
helperText: { | ||
control: 'text', | ||
}, | ||
label: { | ||
control: 'text', | ||
}, | ||
onChange: { | ||
action: 'date-changed', | ||
}, | ||
placeholder: { | ||
control: 'text', | ||
}, | ||
textFieldProps: { | ||
control: 'object', | ||
}, | ||
value: { | ||
control: 'date', | ||
}, | ||
}, | ||
args: { | ||
errorText: '', | ||
format: 'yyyy-MM-dd', | ||
helperText: '', | ||
label: 'Select a Date', | ||
placeholder: 'yyyy-MM-dd', | ||
value: null, | ||
}, | ||
component: DatePicker, | ||
title: 'Components/DatePicker/DatePicker', | ||
}; | ||
|
||
export default meta; |
80 changes: 80 additions & 0 deletions
80
packages/manager/src/components/DatePicker/DatePicker.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { DateTime } from 'luxon'; | ||
import * as React from 'react'; | ||
|
||
import { renderWithTheme } from 'src/utilities/testHelpers'; | ||
|
||
import { DatePicker } from './DatePicker'; | ||
|
||
import type { DatePickerProps } from './DatePicker'; | ||
|
||
const props: DatePickerProps = { | ||
onChange: vi.fn(), | ||
placeholder: 'Pick a date', | ||
textFieldProps: { errorText: 'Invalid date', label: 'Select a date' }, | ||
value: null, | ||
}; | ||
|
||
describe('DatePicker', () => { | ||
it('should render the DatePicker component', () => { | ||
renderWithTheme(<DatePicker {...props} />); | ||
const DatePickerField = screen.getByRole('textbox', { | ||
name: 'Select a date', | ||
}); | ||
|
||
expect(DatePickerField).toBeVisible(); | ||
}); | ||
|
||
it('should handle value changes', async () => { | ||
renderWithTheme(<DatePicker {...props} />); | ||
|
||
const calendarButton = screen.getByRole('button', { name: 'Choose date' }); | ||
|
||
// Click the calendar button to open the date picker | ||
await userEvent.click(calendarButton); | ||
|
||
// Find a date button to click (e.g., the 15th of the month) | ||
const dateToSelect = screen.getByRole('gridcell', { name: '15' }); | ||
await userEvent.click(dateToSelect); | ||
|
||
// Check if onChange was called after selecting a date | ||
expect(props.onChange).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should display the error text when provided', () => { | ||
renderWithTheme(<DatePicker {...props} />); | ||
const errorMessage = screen.getByText('Invalid date'); | ||
expect(errorMessage).toBeVisible(); | ||
}); | ||
|
||
it('should display the helper text when provided', () => { | ||
renderWithTheme(<DatePicker {...props} helperText="Choose a valid date" />); | ||
const helperText = screen.getByText('Choose a valid date'); | ||
expect(helperText).toBeVisible(); | ||
}); | ||
|
||
it('should use the default format when no format is specified', () => { | ||
renderWithTheme( | ||
<DatePicker {...props} value={DateTime.fromISO('2024-10-25')} /> | ||
); | ||
const datePickerField = screen.getByRole('textbox', { | ||
name: 'Select a date', | ||
}); | ||
expect(datePickerField).toHaveValue('2024-10-25'); | ||
}); | ||
|
||
it('should handle the custom format correctly', () => { | ||
renderWithTheme( | ||
<DatePicker | ||
{...props} | ||
format="dd/MM/yyyy" | ||
value={DateTime.fromISO('2024-10-25')} | ||
/> | ||
); | ||
const datePickerField = screen.getByRole('textbox', { | ||
name: 'Select a date', | ||
}); | ||
expect(datePickerField).toHaveValue('25/10/2024'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { TextField } from '@linode/ui'; | ||
import { useTheme } from '@mui/material/styles'; | ||
import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; | ||
import { DatePicker as MuiDatePicker } from '@mui/x-date-pickers/DatePicker'; | ||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; | ||
import React from 'react'; | ||
|
||
import type { TextFieldProps } from '@linode/ui'; | ||
import type { DatePickerProps as MuiDatePickerProps } from '@mui/x-date-pickers/DatePicker'; | ||
import type { DateTime } from 'luxon'; | ||
|
||
export interface DatePickerProps | ||
extends Omit<MuiDatePickerProps<DateTime>, 'onChange' | 'value'> { | ||
/** Error text to display below the input */ | ||
errorText?: string; | ||
/** Format of the date when rendered in the input field. */ | ||
format?: string; | ||
/** Helper text to display below the input */ | ||
helperText?: string; | ||
/** Label to display for the date picker input */ | ||
label?: string; | ||
/** Callback function fired when the value changes */ | ||
onChange: (newDate: DateTime | null) => void; | ||
/** Placeholder text for the date picker input */ | ||
placeholder?: string; | ||
/** Additional props to pass to the underlying TextField component */ | ||
textFieldProps?: Omit<TextFieldProps, 'onChange' | 'value'>; | ||
/** The currently selected date */ | ||
value?: DateTime | null; | ||
} | ||
|
||
export const DatePicker = ({ | ||
format = 'yyyy-MM-dd', | ||
helperText = '', | ||
label = 'Select a date', | ||
onChange, | ||
placeholder = 'Pick a date', | ||
textFieldProps, | ||
value = null, | ||
...props | ||
}: DatePickerProps) => { | ||
const theme = useTheme(); | ||
|
||
const onChangeHandler = (newDate: DateTime | null) => { | ||
onChange(newDate); | ||
}; | ||
|
||
return ( | ||
<LocalizationProvider dateAdapter={AdapterLuxon}> | ||
<MuiDatePicker | ||
format={format} | ||
onChange={onChangeHandler} | ||
reduceAnimations // disables the rendering animation | ||
value={value} | ||
{...props} | ||
slotProps={{ | ||
// TODO: Move styling customization to global theme styles. | ||
popper: { | ||
sx: { | ||
'& .MuiDayCalendar-weekDayLabel': { | ||
fontSize: '0.875rem', | ||
}, | ||
'& .MuiPickersCalendarHeader-label': { | ||
fontWeight: 'bold', | ||
}, | ||
'& .MuiPickersCalendarHeader-root': { | ||
fontSize: '0.875rem', | ||
}, | ||
'& .MuiPickersDay-root': { | ||
fontSize: '0.875rem', | ||
margin: `${theme.spacing(0.5)}px`, | ||
}, | ||
backgroundColor: theme.bg.main, | ||
borderRadius: `${theme.spacing(2)}`, | ||
boxShadow: `0px 4px 16px ${theme.color.boxShadowDark}`, | ||
}, | ||
}, | ||
textField: { | ||
...textFieldProps, | ||
helperText, | ||
label, | ||
placeholder, | ||
}, | ||
}} | ||
slots={{ | ||
textField: TextField, | ||
}} | ||
/> | ||
</LocalizationProvider> | ||
); | ||
}; |
Oops, something went wrong.