From 444e97fa3ecf3490e2b111c2d2aea8e6bbd343ce Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 23 Apr 2024 18:45:32 +0200 Subject: [PATCH] Fixed date picker UI that was too overloaded (#5039) Date picker UI was off because of the recent refactor with new field types Date and DateTime. We had to allow the date picker to edit both. In this PR we come back to the previous design and we only use the input to modify time. Also we use our Select component instead of the ones from the library `react-datepicker` --------- Co-authored-by: Weiko --- .../DateTimeFieldInput.stories.tsx | 10 +- .../ui/field/input/components/DateInput.tsx | 4 +- .../button/components/LightIconButton.tsx | 1 + .../date/components/InternalDatePicker.tsx | 85 +++++++++++------ .../date/components/MonthAndYearDropdown.tsx | 91 ------------------- .../internal/date/components/TimeInput.tsx | 80 ---------------- .../__stories__/DatePicker.stories.tsx | 6 -- 7 files changed, 66 insertions(+), 211 deletions(-) delete mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/components/MonthAndYearDropdown.tsx delete mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/components/TimeInput.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx index 69316c528..00813a0bb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx @@ -126,9 +126,9 @@ type Story = StoryObj; export const Default: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const div = await canvas.findByText('February - 2022'); + const div = await canvas.findByText('February'); - await expect(div.innerText).toContain('February - 2022'); + await expect(div.innerText).toContain('February'); }, }; @@ -138,7 +138,7 @@ export const ClickOutside: Story = { await expect(clickOutsideJestFn).toHaveBeenCalledTimes(0); - await canvas.findByText('February - 2022'); + await canvas.findByText('February'); const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div'); await userEvent.click(emptyDiv); @@ -151,7 +151,7 @@ export const Escape: Story = { await expect(escapeJestFn).toHaveBeenCalledTimes(0); const canvas = within(canvasElement); - await canvas.findByText('February - 2022'); + await canvas.findByText('February'); await userEvent.keyboard('{escape}'); await expect(escapeJestFn).toHaveBeenCalledTimes(1); @@ -163,7 +163,7 @@ export const Enter: Story = { await expect(enterJestFn).toHaveBeenCalledTimes(0); const canvas = within(canvasElement); - await canvas.findByText('February - 2022'); + await canvas.findByText('February'); await userEvent.keyboard('{enter}'); await expect(enterJestFn).toHaveBeenCalledTimes(1); diff --git a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx index 8865ad2f2..4b2131ad5 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx @@ -2,12 +2,12 @@ import { useRef, useState } from 'react'; import styled from '@emotion/styled'; import { Nullable } from 'twenty-ui'; -import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; import { + InternalDatePicker, MONTH_AND_YEAR_DROPDOWN_ID, MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID, -} from '@/ui/input/components/internal/date/components/MonthAndYearDropdown'; +} from '@/ui/input/components/internal/date/components/InternalDatePicker'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; diff --git a/packages/twenty-front/src/modules/ui/input/button/components/LightIconButton.tsx b/packages/twenty-front/src/modules/ui/input/button/components/LightIconButton.tsx index e8247c036..739930c73 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/LightIconButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/LightIconButton.tsx @@ -62,6 +62,7 @@ const StyledButton = styled.button< white-space: nowrap; width: ${({ size }) => (size === 'small' ? '24px' : '32px')}; + min-width: ${({ size }) => (size === 'small' ? '24px' : '32px')}; &:hover { background: ${({ theme, disabled }) => diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index 7bc43b866..be2666fc9 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -6,13 +6,7 @@ import { IconCalendarX, IconChevronLeft, IconChevronRight } from 'twenty-ui'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; -import { - MONTH_AND_YEAR_DROPDOWN_ID, - MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, - MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID, - MonthAndYearDropdown, -} from '@/ui/input/components/internal/date/components/MonthAndYearDropdown'; -import { TimeInput } from '@/ui/input/components/internal/date/components/TimeInput'; +import { Select } from '@/ui/input/components/Select'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/components/MenuItemLeftContent'; import { StyledHoverableMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase'; @@ -21,6 +15,32 @@ import { isDefined } from '~/utils/isDefined'; import 'react-datepicker/dist/react-datepicker.css'; +export const months = [ + { label: 'January', value: 0 }, + { label: 'February', value: 1 }, + { label: 'March', value: 2 }, + { label: 'April', value: 3 }, + { label: 'May', value: 4 }, + { label: 'June', value: 5 }, + { label: 'July', value: 6 }, + { label: 'August', value: 7 }, + { label: 'September', value: 8 }, + { label: 'October', value: 9 }, + { label: 'November', value: 10 }, + { label: 'December', value: 11 }, +]; + +export const years = Array.from( + { length: 200 }, + (_, i) => new Date().getFullYear() + 5 - i, +).map((year) => ({ label: year.toString(), value: year })); + +export const MONTH_AND_YEAR_DROPDOWN_ID = 'date-picker-month-and-year-dropdown'; +export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = + 'date-picker-month-and-year-dropdown-month-select'; +export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID = + 'date-picker-month-and-year-dropdown-year-select'; + const StyledContainer = styled.div` & .react-datepicker { border-color: ${({ theme }) => theme.border.color.light}; @@ -252,10 +272,10 @@ const StyledContainer = styled.div` `; const StyledButtonContainer = styled(StyledHoverableMenuItemBase)` - width: auto; - height: ${({ theme }) => theme.spacing(8)}; - padding: 0 ${({ theme }) => theme.spacing(2)}; + height: ${({ theme }) => theme.spacing(4)}; margin: ${({ theme }) => theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(1)}; + width: auto; `; const StyledButton = styled(MenuItemLeftContent)` @@ -273,13 +293,6 @@ const StyledCustomDatePickerHeader = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -const StyledMonthText = styled.div` - color: ${({ theme }) => theme.font.color.secondary}; - font-family: ${({ theme }) => theme.font.family}; - font-size: ${({ theme }) => theme.font.size.md}; - padding: ${({ theme }) => theme.spacing(2)}; -`; - export type InternalDatePickerProps = { date: Date; onMouseSelect?: (date: Date | null) => void; @@ -305,9 +318,6 @@ export const InternalDatePicker = ({ }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); - const monthLabel = DateTime.fromJSDate(internalDate).toFormat('LLLL'); - const yearLabel = DateTime.fromJSDate(internalDate).toFormat('yyyy'); - const { closeDropdown } = useDropdown(MONTH_AND_YEAR_DROPDOWN_ID); const { closeDropdown: closeDropdownMonthSelect } = useDropdown( MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, @@ -357,6 +367,18 @@ export const InternalDatePicker = ({ } }; + const handleChangeMonth = (month: number) => { + const newDate = new Date(date); + newDate.setMonth(month); + onChange?.(newDate); + }; + + const handleChangeYear = (year: number) => { + const newDate = new Date(date); + newDate.setFullYear(year); + onChange?.(newDate); + }; + return (
@@ -393,10 +415,22 @@ export const InternalDatePicker = ({ onChange={onChange} /> - {isDateTimeInput && ( - - )} - + decreaseMonth()} @@ -410,9 +444,6 @@ export const InternalDatePicker = ({ disabled={nextMonthButtonDisabled} /> - - {monthLabel} - {yearLabel} - )} onSelect={(date: Date, event) => { diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/MonthAndYearDropdown.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/MonthAndYearDropdown.tsx deleted file mode 100644 index 25aeecb76..000000000 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/MonthAndYearDropdown.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { IconCalendarDue } from 'twenty-ui'; - -import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; -import { Select } from '@/ui/input/components/Select'; -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; - -type MonthAndYearDropdownProps = { - date: Date; - onChange?: (newDate: Date) => void; -}; - -const months = [ - { label: 'January', value: 0 }, - { label: 'February', value: 1 }, - { label: 'March', value: 2 }, - { label: 'April', value: 3 }, - { label: 'May', value: 4 }, - { label: 'June', value: 5 }, - { label: 'July', value: 6 }, - { label: 'August', value: 7 }, - { label: 'September', value: 8 }, - { label: 'October', value: 9 }, - { label: 'November', value: 10 }, - { label: 'December', value: 11 }, -]; - -const years = Array.from( - { length: 200 }, - (_, i) => new Date().getFullYear() + 5 - i, -).map((year) => ({ label: year.toString(), value: year })); - -export const MONTH_AND_YEAR_DROPDOWN_ID = 'date-picker-month-and-year-dropdown'; -export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = - 'date-picker-month-and-year-dropdown-month-select'; -export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID = - 'date-picker-month-and-year-dropdown-year-select'; - -export const MonthAndYearDropdown = ({ - date, - onChange, -}: MonthAndYearDropdownProps) => { - const handleChangeMonth = (month: number) => { - const newDate = new Date(date); - newDate.setMonth(month); - onChange?.(newDate); - }; - - const handleChangeYear = (year: number) => { - const newDate = new Date(date); - newDate.setFullYear(year); - onChange?.(newDate); - }; - - return ( - - } - dropdownComponents={ - - - - } - /> - ); -}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/TimeInput.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/TimeInput.tsx deleted file mode 100644 index b8c9ba54f..000000000 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/TimeInput.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useEffect } from 'react'; -import { useIMask } from 'react-imask'; -import styled from '@emotion/styled'; -import { DateTime } from 'luxon'; -import { IconClockHour8 } from 'twenty-ui'; - -import { TIME_BLOCKS } from '@/ui/input/components/internal/date/constants/TimeBlocks'; -import { TIME_MASK } from '@/ui/input/components/internal/date/constants/TimeMask'; - -const StyledIconClock = styled(IconClockHour8)` - position: absolute; -`; - -const StyledTimeInputContainer = styled.div` - align-items: center; - background-color: ${({ theme }) => theme.background.tertiary}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - display: flex; - margin-right: 0; - padding: 0 ${({ theme }) => theme.spacing(2)}; - - text-align: left; - width: 136px; - height: 32px; - gap: ${({ theme }) => theme.spacing(1)}; - - z-index: 10; -`; - -const StyledTimeInput = styled.input` - background: transparent; - border: none; - color: ${({ theme }) => theme.font.color.primary}; - outline: none; - font-weight: 500; - font-size: ${({ theme }) => theme.font.size.md}; - margin-left: ${({ theme }) => theme.spacing(5)}; -`; - -type TimeInputProps = { - onChange?: (date: Date) => void; - date: Date; -}; - -export const TimeInput = ({ date, onChange }: TimeInputProps) => { - const handleComplete = (value: string) => { - const [hours, minutes] = value.split(':'); - - const newDate = new Date(date); - - newDate.setHours(parseInt(hours, 10)); - newDate.setMinutes(parseInt(minutes, 10)); - - onChange?.(newDate); - }; - - const { ref, setValue } = useIMask( - { - mask: TIME_MASK, - blocks: TIME_BLOCKS, - lazy: false, - }, - { - onComplete: handleComplete, - }, - ); - - useEffect(() => { - const formattedDate = DateTime.fromJSDate(date).toFormat('HH:mm'); - - setValue(formattedDate); - }, [date, setValue]); - - return ( - - - - - ); -}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/DatePicker.stories.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/DatePicker.stories.tsx index 8b79d17d5..2d7360cd6 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/DatePicker.stories.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/DatePicker.stories.tsx @@ -23,12 +23,6 @@ export const WithOpenMonthSelect: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const monthAndYearButton = await canvas.findByTestId( - 'month-and-year-dropdown', - ); - - await userEvent.click(monthAndYearButton); - const monthSelect = await canvas.findByText('January'); await userEvent.click(monthSelect);