fix: month and year dropdowns in settings logs date picker (#21529)

### Summary
- Fixes #21514 and Issue 2
- **Issue 1**: when opening the calendar and choosing a month or year,
those lists could appear underneath the calendar, making them impossible
to see and use. (Issue #21514)
- **Issue 2**: after opening the calendar icon menu, clicking the month
or year controls don't work, so you couldn’t actually change the month
or year.

Before:
<img width="355" height="434"
alt="607363028-0d3a302e-9dba-4d9a-b354-ad7cbcd1fba5"
src="https://github.com/user-attachments/assets/b1c357d1-a7cf-4572-8737-721cf4e2597a"
/>

After:


https://github.com/user-attachments/assets/ffc26447-ff34-4f11-a3b4-4c329e446ec4



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21529?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Parship Chowdhury
2026-06-23 15:10:27 +05:30
committed by GitHub
parent e1c962acba
commit c18787350f
4 changed files with 119 additions and 25 deletions
@@ -5,6 +5,7 @@ import { VariableChipStandalone } from '@/object-record/record-field/ui/form-typ
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { InputLabel } from '@/ui/input/components/InputLabel';
import {
DATE_TIME_PICKER_MONTH_YEAR_PANEL_DROPDOWN_ID,
DateTimePicker,
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID,
@@ -15,6 +16,7 @@ import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUs
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { ParentClickOutsideIdContext } from '@/ui/utilities/pointer-event/contexts/ParentClickOutsideIdContext';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@@ -133,6 +135,7 @@ export const FormDateTimeFieldInput = ({
enabled: displayDatePicker,
excludedClickOutsideIds: [
FORM_DATE_TIME_FIELD_PICKER_CLICK_OUTSIDE_ID,
DATE_TIME_PICKER_MONTH_YEAR_PANEL_DROPDOWN_ID,
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID,
],
@@ -296,17 +299,21 @@ export const FormDateTimeFieldInput = ({
}
>
<OverlayContainer>
<DateTimePicker
instanceId={instanceId}
date={dateValue}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
timeZone={timeZone}
/>
<ParentClickOutsideIdContext.Provider
value={FORM_DATE_TIME_FIELD_PICKER_CLICK_OUTSIDE_ID}
>
<DateTimePicker
instanceId={instanceId}
date={dateValue}
onChange={handlePickerChange}
onClose={handlePickerMouseSelect}
onEnter={handlePickerEnter}
onEscape={handlePickerEscape}
onClear={handlePickerClear}
hideHeaderInput
timeZone={timeZone}
/>
</ParentClickOutsideIdContext.Provider>
</OverlayContainer>
</div>
</FloatingPortal>
@@ -11,12 +11,14 @@ import {
} from '@floating-ui/react';
import {
DATE_TIME_PICKER_MONTH_YEAR_PANEL_DROPDOWN_ID,
DateTimePicker,
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID,
} from '@/ui/input/components/internal/date/components/DateTimePicker';
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { ParentClickOutsideIdContext } from '@/ui/utilities/pointer-event/contexts/ParentClickOutsideIdContext';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from 'twenty-shared/utils';
import { IconCalendar } from 'twenty-ui/icon';
@@ -62,10 +64,6 @@ const StyledIconContainer = styled.div`
display: flex;
`;
const StyledFloatingContainer = styled.div`
z-index: 1000;
`;
export type SettingsDatePickerInputProps = {
label?: string;
instanceId?: string;
@@ -107,6 +105,7 @@ export const SettingsDatePickerInput = ({
enabled: isOpen,
excludedClickOutsideIds: [
SETTINGS_DATE_PICKER_CLICK_OUTSIDE_ID,
DATE_TIME_PICKER_MONTH_YEAR_PANEL_DROPDOWN_ID,
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID,
],
@@ -163,22 +162,26 @@ export const SettingsDatePickerInput = ({
</StyledInput>
{isOpen && (
<FloatingPortal>
<StyledFloatingContainer
<div
ref={refs.setFloating}
style={floatingStyles}
data-click-outside-id={SETTINGS_DATE_PICKER_CLICK_OUTSIDE_ID}
>
<OverlayContainer>
<DateTimePicker
instanceId={`settings-date-picker-${pickerInstanceId}`}
date={zonedDateTime}
onChange={handleDateTimeChange}
onClose={handleDateTimeClose}
onClear={handleClear}
clearable
/>
<ParentClickOutsideIdContext.Provider
value={SETTINGS_DATE_PICKER_CLICK_OUTSIDE_ID}
>
<DateTimePicker
instanceId={`settings-date-picker-${pickerInstanceId}`}
date={zonedDateTime}
onChange={handleDateTimeChange}
onClose={handleDateTimeClose}
onClear={handleClear}
clearable
/>
</ParentClickOutsideIdContext.Provider>
</OverlayContainer>
</StyledFloatingContainer>
</div>
</FloatingPortal>
)}
</StyledInputContainer>
@@ -0,0 +1,82 @@
import { SettingsDatePickerInput } from '@/settings/components/SettingsDatePickerInput';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { expect, userEvent, within } from 'storybook/test';
import { ComponentDecorator } from 'twenty-ui/testing';
// Midday UTC so the rendered date stays on 15 Jan 2023 across timezones
const INITIAL_DATE = new Date('2023-01-15T12:00:00Z');
const SettingsDatePickerInputStory = () => {
const [value, setValue] = useState<Date | undefined>(INITIAL_DATE);
return (
<SettingsDatePickerInput label="Date" value={value} onChange={setValue} />
);
};
const meta: Meta<typeof SettingsDatePickerInput> = {
title: 'Modules/Settings/SettingsDatePickerInput',
component: SettingsDatePickerInput,
decorators: [ComponentDecorator],
render: () => <SettingsDatePickerInputStory />,
};
export default meta;
type Story = StoryObj<typeof SettingsDatePickerInput>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await canvas.findByText(/2023/, {}, { timeout: 10000 }),
);
await body.findByRole(
'button',
{ name: 'Select month and year' },
{ timeout: 10000 },
);
},
};
// Regression test for the month/year panel closing the whole picker on click.
// Opening the month select used to trigger the parent click-outside listener and
// close the date picker, so the month/year controls could never be used.
export const ChangingMonthKeepsPickerOpen: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await canvas.findByText(/2023/, {}, { timeout: 10000 }),
);
const monthYearButton = await body.findByRole(
'button',
{ name: 'Select month and year' },
{ timeout: 10000 },
);
await userEvent.click(monthYearButton);
await userEvent.click(
await body.findByText('January', {}, { timeout: 10000 }),
);
// The picker must stay open after interacting with the month select
expect(
body.getByRole('button', { name: 'Select month and year' }),
).toBeInTheDocument();
await userEvent.click(
await body.findByText('February', {}, { timeout: 10000 }),
);
expect(await body.findByText('February')).toBeInTheDocument();
expect(
body.getByRole('button', { name: 'Select month and year' }),
).toBeInTheDocument();
},
};
@@ -205,6 +205,8 @@ const StyledContainer = styled.div<{
& .react-datepicker__month {
margin-top: 0;
margin-left: 0;
margin-right: 0;
pointer-events: ${({ calendarDisabled }) =>
calendarDisabled ? 'none' : 'auto'};