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

feat: [M3-8611]- New DatePicker Component #11151

Merged
merged 84 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
b274baf
unit test coverage for HostNameTableCell
cpathipa Jun 19, 2024
f958dab
Revert "unit test coverage for HostNameTableCell"
cpathipa Jun 19, 2024
5d0a476
Merge branch 'linode:develop' into develop
cpathipa Jun 25, 2024
93aab07
Merge branch 'linode:develop' into develop
cpathipa Jun 25, 2024
d7deb4f
Merge branch 'linode:develop' into develop
cpathipa Jul 1, 2024
a550f05
Merge branch 'linode:develop' into develop
cpathipa Jul 3, 2024
de0f63e
Merge branch 'linode:develop' into develop
cpathipa Jul 5, 2024
426c42c
Merge branch 'linode:develop' into develop
cpathipa Jul 17, 2024
3fb49a6
Merge branch 'linode:develop' into develop
cpathipa Jul 22, 2024
6c76508
Merge branch 'linode:develop' into develop
cpathipa Jul 24, 2024
1653a6b
Merge branch 'linode:develop' into develop
cpathipa Jul 25, 2024
00421cf
Merge branch 'linode:develop' into develop
cpathipa Jul 29, 2024
959730a
Merge branch 'linode:develop' into develop
cpathipa Jul 31, 2024
d9bd490
Merge branch 'linode:develop' into develop
cpathipa Jul 31, 2024
960415e
Merge branch 'linode:develop' into develop
cpathipa Aug 1, 2024
b9f4745
Merge branch 'linode:develop' into develop
cpathipa Aug 2, 2024
b0b9264
Merge branch 'linode:develop' into develop
cpathipa Aug 21, 2024
6c70559
Merge branch 'linode:develop' into develop
cpathipa Aug 28, 2024
96eb34d
Merge branch 'linode:develop' into develop
cpathipa Sep 3, 2024
74b1635
Merge branch 'linode:develop' into develop
cpathipa Sep 4, 2024
70d1422
Merge branch 'linode:develop' into develop
cpathipa Sep 5, 2024
342fd96
Merge branch 'linode:develop' into develop
cpathipa Sep 9, 2024
bfed239
Merge branch 'linode:develop' into develop
cpathipa Sep 13, 2024
8a19f9b
Merge branch 'linode:develop' into develop
cpathipa Sep 17, 2024
9e9c14f
Merge branch 'linode:develop' into develop
cpathipa Sep 17, 2024
a25728e
Merge branch 'linode:develop' into develop
cpathipa Sep 18, 2024
3196f2a
Merge branch 'linode:develop' into develop
cpathipa Sep 19, 2024
4794d04
Merge branch 'linode:develop' into develop
cpathipa Sep 23, 2024
e977a94
Merge branch 'linode:develop' into develop
cpathipa Sep 24, 2024
add3f10
Merge branch 'linode:develop' into develop
cpathipa Sep 24, 2024
2fafd33
Merge branch 'linode:develop' into develop
cpathipa Sep 25, 2024
b3463ae
Merge branch 'linode:develop' into develop
cpathipa Sep 26, 2024
a325e30
Merge branch 'linode:develop' into develop
cpathipa Sep 30, 2024
b1e2a51
chore: [M3-8662] - Update Github Actions actions (#11009)
bnussman-akamai Sep 26, 2024
1b0931b
Merge branch 'develop' of github.com:cpathipa/manager into develop
cpathipa Sep 30, 2024
9b2de1d
Merge branch 'linode:develop' into develop
cpathipa Sep 30, 2024
ff89c50
Merge branch 'linode:develop' into develop
cpathipa Oct 9, 2024
4dbe28d
Merge branch 'linode:develop' into develop
cpathipa Oct 11, 2024
b2b7d97
Merge branch 'linode:develop' into develop
cpathipa Oct 14, 2024
04514de
Merge branch 'linode:develop' into develop
cpathipa Oct 16, 2024
e343821
Merge branch 'linode:develop' into develop
cpathipa Oct 21, 2024
7fd4fa5
Merge branch 'linode:develop' into develop
cpathipa Oct 22, 2024
81ec4f8
Merge branch 'linode:develop' into develop
cpathipa Oct 22, 2024
4e25a94
Basci date picker component
cpathipa Oct 23, 2024
52a7276
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Oct 24, 2024
423f4b7
Test coverage for date picker component
cpathipa Oct 25, 2024
34f03e3
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Oct 30, 2024
f87fbce
DatePicker Stories
cpathipa Oct 31, 2024
f47f919
Custom DateTimePicker component
cpathipa Oct 31, 2024
2578b83
Reusable TimeZone Select Component
cpathipa Oct 31, 2024
43810e7
Create custom DateTimeRangePicker component
cpathipa Oct 31, 2024
fa7bd52
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Nov 6, 2024
3b6f981
Storybook for DateTimePicker
cpathipa Nov 6, 2024
9b10623
Fix tests and remove console warnings
cpathipa Nov 7, 2024
9d32d3d
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Nov 18, 2024
11d8780
changeset
cpathipa Nov 18, 2024
862134f
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Nov 19, 2024
2643bd6
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Nov 20, 2024
7aec8c9
Update packages/manager/src/components/DatePicker/DateTimeRangePicker…
cpathipa Nov 20, 2024
0c379a2
Adjust styles for DatePicker
cpathipa Nov 21, 2024
73e0a20
Merge branch 'M3-8611' of github.com:cpathipa/manager into M3-8611
cpathipa Nov 21, 2024
194b0d2
Adjust styles for DateTimePicker
cpathipa Nov 22, 2024
0ffaade
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Dec 2, 2024
b2d591f
update imports
cpathipa Dec 2, 2024
efcfe5c
Render time and timezone conditionally in DateTimePicker component
cpathipa Dec 2, 2024
2ca7adf
Move DatePicker to UI package
cpathipa Dec 3, 2024
31dc03e
Add DatePicker dependencies
cpathipa Dec 3, 2024
cd875be
Code cleanup
cpathipa Dec 4, 2024
5f35d70
PR feedback
cpathipa Dec 4, 2024
208b2a0
code cleanup
cpathipa Dec 4, 2024
d1c3fa1
Move DatePicker back to src/components
cpathipa Dec 4, 2024
6186d26
Reverting changes
cpathipa Dec 4, 2024
2cd8366
Code cleanup
cpathipa Dec 4, 2024
b8f2d73
Adjust broken tests
cpathipa Dec 4, 2024
51b1a29
Merge remote-tracking branch 'origin/develop' into M3-8611
cpathipa Dec 4, 2024
e523043
Update TimeZoneSelect.tsx
cpathipa Dec 4, 2024
f0dd5c0
Code cleanup
cpathipa Dec 9, 2024
9abec60
Add validation for start date agains end date.
cpathipa Dec 9, 2024
fa3e186
Adjust styles for TimePicker component.
cpathipa Dec 10, 2024
1ab71f9
PR feedback - @jaalah-akamai
cpathipa Dec 11, 2024
ee326f1
allow error messages from props.
cpathipa Dec 11, 2024
18d4483
Update storybook components with args
cpathipa Dec 11, 2024
dc5291f
Update props
cpathipa Dec 12, 2024
8e3c8c1
PR feedback - @hana-akamai
cpathipa Dec 12, 2024
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
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11151-added-1731944151381.md
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))
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 ControlledExample: Story = {
hana-akamai marked this conversation as resolved.
Show resolved Hide resolved
render: () => {
const ControlledDatePicker = () => {
const [selectedDate, setSelectedDate] = React.useState<DateTime | null>();

const handleChange = (newDate: DateTime | null) => {
setSelectedDate(newDate);
action('Controlled date change')(newDate?.toISO());
};

return (
<DatePicker
format="yyyy-MM-dd"
helperText="This is a controlled DatePicker"
label="Controlled Date Picker"
onChange={handleChange}
placeholder="yyyy-MM-dd"
value={selectedDate}
/>
);
};

return <ControlledDatePicker />;
},
};

const meta: Meta<typeof DatePicker> = {
component: DatePicker,
title: 'Components/DatePicker',
hana-akamai marked this conversation as resolved.
Show resolved Hide resolved
};

export default meta;
80 changes: 80 additions & 0 deletions packages/manager/src/components/DatePicker/DatePicker.test.tsx
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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test for typing the date in the input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I tried to cover this test but faced an issue because the TextField component currently allows free-form typing. Addressing this would require updating our TextField component to support a date type field or exploring alternative approaches to handle date-specific input more effectively. This also, involves in restricting free from typing on the input field. I will create a separate ticket to tackle this.

  it.only('should handle typing a date and pressing Enter', async () => {
    renderWithTheme(<DatePicker {...props} />);

    const datePickerField = screen.getByRole('textbox', {
      name: 'Select a date',
    });

    // Select all text and clear with Backspace
    await userEvent.click(datePickerField);

    // Type a valid date into the input field
    await userEvent.type(datePickerField, '2024-10-25');

    // Simulate pressing Enter
    await userEvent.keyboard('{Enter}');

    // Verify that the field value is updated
    expect(datePickerField).toHaveValue('2024-10-25');
  });

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');
});
});
92 changes: 92 additions & 0 deletions packages/manager/src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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;
hana-akamai marked this conversation as resolved.
Show resolved Hide resolved
/** 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 = ({
coliu-akamai marked this conversation as resolved.
Show resolved Hide resolved
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) => {
if (onChange) {
onChange(newDate);
}
};

return (
<LocalizationProvider dateAdapter={AdapterLuxon}>
<MuiDatePicker
format={format}
onChange={onChangeHandler}
reduceAnimations // disables the rendering animation
value={value}
{...props}
slotProps={{
popper: {
sx: {
'& .MuiDayCalendar-weekDayLabel': {
fontSize: '0.875rem',
},
'& .MuiPickersCalendarHeader-label': {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like stuff that belongs in the MUI theme. Can we move styles like this into light.ts? Seems like this might help

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Moving these to foundations/themes/light.ts is not reflecting in the Storybook component but is working in the CM app. I will address this in the following PR for the DateRange Picker - M3-8990.

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>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { action } from '@storybook/addon-actions';
import * as React from 'react';

import { DateTimePicker } from './DateTimePicker';

import type { Meta, StoryObj } from '@storybook/react';
import type { DateTime } from 'luxon';

type Story = StoryObj<typeof DateTimePicker>;

export const ControlledExample: Story = {
coliu-akamai marked this conversation as resolved.
Show resolved Hide resolved
render: () => {
const ControlledDateTimePicker = () => {
const [
selectedDateTime,
setSelectedDateTime,
] = React.useState<DateTime | null>(null); // Start with null

const handleChange = (newDateTime: DateTime | null) => {
setSelectedDateTime(newDateTime);
action('Controlled dateTime change')(newDateTime?.toISO());
};

return (
<DateTimePicker
timeZoneSelectProps={{
label: 'Timezone',
onChange: action('Timezone changed'),
}}
label="Controlled Date-Time Picker"
onApply={action('Apply clicked')}
onCancel={action('Cancel clicked')}
onChange={handleChange}
placeholder="yyyy-MM-dd HH:mm"
showTimeZone={false}
timeSelectProps={{ label: 'Select Time' }}
value={selectedDateTime}
/>
);
};

return <ControlledDateTimePicker />;
},
};

export const DefaultExample: Story = {
args: {
label: 'Default Date-Time Picker',
onApply: action('Apply clicked'),
onCancel: action('Cancel clicked'),
onChange: action('Date-Time selected'),
placeholder: 'yyyy-MM-dd HH:mm',
},
};

export const WithErrorText: Story = {
args: {
errorText: 'This field is required',
label: 'Date-Time Picker with Error',
onApply: action('Apply clicked with error'),
onCancel: action('Cancel clicked with error'),
onChange: action('Date-Time selected with error'),
placeholder: 'yyyy-MM-dd HH:mm',
},
};

const meta: Meta<typeof DateTimePicker> = {
argTypes: {
errorText: {
control: { type: 'text' },
},
format: {
control: { type: 'text' },
},
label: {
control: { type: 'text' },
},
onApply: { action: 'applyClicked' },
onCancel: { action: 'cancelClicked' },
onChange: { action: 'dateTimeChanged' },
placeholder: {
control: { type: 'text' },
},
},
args: {
format: 'yyyy-MM-dd HH:mm',
label: 'Date-Time Picker',
placeholder: 'Select a date and time',
},
component: DateTimePicker,
title: 'Components/DateTimePicker',
};

export default meta;
Loading
Loading