-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
frontend: Add Time picker component (#2743)
- Loading branch information
Showing
6 changed files
with
170 additions
and
6 deletions.
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
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
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
45 changes: 45 additions & 0 deletions
45
frontend/packages/core/src/Input/stories/time-picker.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,45 @@ | ||
import * as React from "react"; | ||
import type { Meta } from "@storybook/react"; | ||
|
||
import type { TimePickerProps } from "../time-picker"; | ||
import TimePicker from "../time-picker"; | ||
|
||
export default { | ||
title: "Core/Input/TimePicker", | ||
component: TimePicker, | ||
argTypes: { | ||
label: { | ||
control: "text", | ||
}, | ||
value: { | ||
control: "date", | ||
}, | ||
}, | ||
} as Meta; | ||
|
||
const Template = (props: TimePickerProps) => <TimePicker {...props} />; | ||
|
||
export const PrimaryDemo = ({ ...props }) => { | ||
const [timeValue, setTimeValue] = React.useState<Date | null>(props.value); | ||
|
||
return ( | ||
<TimePicker | ||
label={props.label} | ||
onChange={(newValue: unknown) => { | ||
setTimeValue(newValue as Date); | ||
}} | ||
value={timeValue ?? props.value} | ||
/> | ||
); | ||
}; | ||
|
||
PrimaryDemo.args = { | ||
label: "My Label", | ||
value: new Date(), | ||
} as TimePickerProps; | ||
|
||
export const Disabled = Template.bind({}); | ||
Disabled.args = { | ||
...PrimaryDemo.args, | ||
disabled: true, | ||
} as TimePickerProps; |
65 changes: 65 additions & 0 deletions
65
frontend/packages/core/src/Input/tests/time-picker.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,65 @@ | ||
import * as React from "react"; | ||
import { fireEvent, render, screen } from "@testing-library/react"; | ||
|
||
import "@testing-library/jest-dom"; | ||
|
||
import TimePicker from "../time-picker"; | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
const onChange = jest.fn(); | ||
test("has padding", () => { | ||
const { container } = render(<TimePicker value={new Date()} onChange={onChange} />); | ||
|
||
expect(container.querySelectorAll(".MuiInputBase-adornedEnd")).toHaveLength(1); | ||
expect(container.querySelector(".MuiInputBase-adornedEnd")).toHaveStyle({ | ||
"padding-right": "14px", | ||
}); | ||
}); | ||
|
||
test("onChange is called when valid value", () => { | ||
render(<TimePicker value={new Date()} onChange={onChange} />); | ||
|
||
expect(screen.getByPlaceholderText("hh:mm (a|p)m")).toBeVisible(); | ||
fireEvent.change(screen.getByPlaceholderText("hh:mm (a|p)m"), { | ||
target: { value: "02:55 AM" }, | ||
}); | ||
expect(onChange).toHaveBeenCalled(); | ||
}); | ||
|
||
test("onChange is not called when invalid value", () => { | ||
render(<TimePicker value={new Date()} onChange={onChange} />); | ||
|
||
expect(screen.getByPlaceholderText("hh:mm (a|p)m")).toBeVisible(); | ||
fireEvent.change(screen.getByPlaceholderText("hh:mm (a|p)m"), { | ||
target: { value: "invalid" }, | ||
}); | ||
expect(onChange).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test("sets passed value correctly", () => { | ||
const date = new Date(); | ||
const formattedTime = new Intl.DateTimeFormat("en-US", { | ||
hour: "2-digit", | ||
minute: "2-digit", | ||
}).format(date); | ||
const formattedDate = `${formattedTime}`; | ||
render(<TimePicker value={date} onChange={onChange} />); | ||
|
||
expect(screen.getByPlaceholderText("hh:mm (a|p)m")).toHaveValue(formattedDate); | ||
}); | ||
|
||
test("displays label correctly", () => { | ||
const label = "testing"; | ||
render(<TimePicker value={new Date()} onChange={onChange} label={label} />); | ||
|
||
expect(screen.getByLabelText(label)).toBeVisible(); | ||
}); | ||
|
||
test("is disabled", () => { | ||
render(<TimePicker value={new Date()} onChange={onChange} disabled />); | ||
|
||
expect(screen.getByPlaceholderText("hh:mm (a|p)m")).toBeDisabled(); | ||
}); |
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,41 @@ | ||
import React from "react"; | ||
import type { TimePickerProps as MuiTimePickerProps } from "@mui/lab"; | ||
import { TimePicker as MuiTimePicker } from "@mui/x-date-pickers"; | ||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | ||
import type { Dayjs } from "dayjs"; | ||
|
||
import styled from "../styled"; | ||
|
||
import { TextField } from "./text-field"; | ||
|
||
const PaddedTextField = styled(TextField)({ | ||
// This is required as TextField intentionally unsets the right padding for | ||
// end adornment styles since material introduced it in v5 | ||
// Clutch has TextFields with end adornments that are end aligned (e.g. resolvers). | ||
".MuiInputBase-adornedEnd": { | ||
paddingRight: "14px", | ||
}, | ||
}); | ||
|
||
export interface TimePickerProps | ||
extends Pick< | ||
MuiTimePickerProps, | ||
"disabled" | "value" | "onChange" | "label" | "PaperProps" | "PopperProps" | ||
> {} | ||
|
||
const TimePicker = ({ onChange, ...props }: TimePickerProps) => ( | ||
<LocalizationProvider dateAdapter={AdapterDayjs}> | ||
<MuiTimePicker | ||
renderInput={inputProps => <PaddedTextField {...inputProps} />} | ||
onChange={(value: Dayjs | null) => { | ||
if (value && value.isValid()) { | ||
onChange(value.toDate()); | ||
} | ||
}} | ||
{...props} | ||
/> | ||
</LocalizationProvider> | ||
); | ||
|
||
export default TimePicker; |