fix: uneven spacing in date picker calendar grid (#21881)

### Summary
While working on #19739, I found that in the date filter calendar
dropdown, day cells and highlighted dates looked misaligned i.e. tighter
on the right side. The solution is to apply a uniform margin in
`DatePicker.tsx` and `DateTimePicker.tsx`.

### Before:
<img width="377" height="436" alt="Screenshot 2026-06-20 021856"
src="https://github.com/user-attachments/assets/4ef62a48-6b99-4647-95f6-bd39f43eaa26"
/>
<img width="442" height="518" alt="Screenshot 2026-06-20 021932"
src="https://github.com/user-attachments/assets/e6e1e8f8-257a-41e5-825f-bd2fe91e372a"
/>


### After:
<img width="346" height="380" alt="Screenshot 2026-06-20 021813"
src="https://github.com/user-attachments/assets/5fdd03fe-e61b-4fec-a0f3-f6ac4ee9effb"
/>
<img width="322" height="457" alt="Screenshot 2026-06-20 022013"
src="https://github.com/user-attachments/assets/a33c6439-f2d5-43e7-a043-f9cf4fec770a"
/>

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21881?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-26 11:21:37 +05:30
committed by GitHub
parent 05df528fd5
commit c635a191bf
7 changed files with 427 additions and 732 deletions
@@ -10,6 +10,10 @@ import { CalendarStartDay } from 'twenty-shared/constants';
import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay';
import { DatePickerHeader } from '@/ui/input/components/internal/date/components/DatePickerHeader';
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
import {
DATE_PICKER_CONTAINER_WIDTH,
StyledDatePickerContainer,
} from '@/ui/input/components/internal/date/components/StyledDatePickerContainer';
import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { t } from '@lingui/core/macro';
@@ -33,245 +37,6 @@ export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID =
export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID =
'date-picker-month-and-year-dropdown-year-select';
const DATE_PICKER_CONTAINER_WIDTH = 280;
const StyledContainer = styled.div<{ calendarDisabled?: boolean }>`
width: ${DATE_PICKER_CONTAINER_WIDTH}px;
& .react-datepicker {
border-color: ${themeCssVariables.border.color.light};
background: transparent;
font-family: 'Inter';
font-size: ${themeCssVariables.font.size.md};
border: none;
display: block;
font-weight: ${themeCssVariables.font.weight.regular};
}
& .react-datepicker-popper {
position: relative !important;
inset: auto !important;
transform: none !important;
padding: 0 !important;
}
& .react-datepicker__triangle {
display: none;
}
& .react-datepicker__triangle::after {
display: none;
}
& .react-datepicker__triangle::before {
display: none;
}
& .react-datepicker-wrapper {
display: none;
}
// Header
& .react-datepicker__header {
background: transparent;
border: none;
padding: 0;
}
&
.react-datepicker__input-time-container
.react-datepicker-time__input-container
.react-datepicker-time__input {
outline: none;
}
& .react-datepicker__header__dropdown {
display: flex;
color: ${themeCssVariables.font.color.primary};
margin-left: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[10]};
}
& .react-datepicker__month-dropdown-container,
& .react-datepicker__year-dropdown-container {
text-align: left;
border-radius: ${themeCssVariables.border.radius.sm};
margin-left: ${themeCssVariables.spacing[1]};
margin-right: 0;
padding: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[4]};
background-color: ${themeCssVariables.background.tertiary};
}
& .react-datepicker__month-read-view--down-arrow,
& .react-datepicker__year-read-view--down-arrow {
height: 5px;
width: 5px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.border.color.light};
top: 3px;
right: -6px;
}
& .react-datepicker__year-read-view,
& .react-datepicker__month-read-view {
padding-right: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown-container {
width: 80px;
}
& .react-datepicker__year-dropdown-container {
width: 50px;
}
& .react-datepicker__month-dropdown,
& .react-datepicker__year-dropdown {
overflow-y: scroll;
top: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown {
left: ${themeCssVariables.spacing[2]};
height: 260px;
}
& .react-datepicker__year-dropdown {
left: calc(${themeCssVariables.spacing[9]} + 80px);
width: 100px;
height: 260px;
}
& .react-datepicker__navigation--years {
display: none;
}
& .react-datepicker__month-option--selected,
& .react-datepicker__year-option--selected {
display: none;
}
& .react-datepicker__year-option,
& .react-datepicker__month-option {
text-align: left;
padding: ${themeCssVariables.spacing[2]}
calc(${themeCssVariables.spacing[2]} - 2px);
width: calc(100% - ${themeCssVariables.spacing[4]});
border-radius: ${themeCssVariables.border.radius.xs};
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
margin: 2px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__year-option {
&:first-of-type,
&:last-of-type {
display: none;
}
}
& .react-datepicker__current-month {
display: none;
}
& .react-datepicker__day-name {
color: ${themeCssVariables.font.color.secondary};
width: 34px;
height: 40px;
line-height: 40px;
}
& .react-datepicker__month-container {
float: none;
}
// Days
& .react-datepicker__month {
margin-top: 0;
pointer-events: ${({ calendarDisabled }) =>
calendarDisabled ? 'none' : 'auto'};
opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')};
}
& .react-datepicker__day {
width: 34px;
height: 34px;
line-height: 34px;
}
& .react-datepicker__navigation--previous,
& .react-datepicker__navigation--next {
height: 34px;
border-radius: ${themeCssVariables.border.radius.sm};
padding-top: 6px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__navigation--previous {
right: 38px;
top: 6px;
left: auto;
& > span {
margin-left: -6px;
}
}
& .react-datepicker__navigation--next {
right: 6px;
top: 6px;
& > span {
margin-left: 6px;
}
}
& .react-datepicker__navigation-icon::before {
height: 7px;
width: 7px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day--keyboard-selected {
background-color: inherit;
}
& .react-datepicker__day,
.react-datepicker__time-name {
color: ${themeCssVariables.font.color.primary};
}
& .react-datepicker__day--selected {
background-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.background.primary};
&.react-datepicker__day:hover {
color: ${themeCssVariables.background.primary};
}
}
& .react-datepicker__day--outside-month {
color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day:hover {
color: ${themeCssVariables.font.color.tertiary};
}
& .clearable {
border-bottom: 1px solid ${themeCssVariables.border.color.light};
}
`;
const StyledButtonContainer = styled.div`
align-items: center;
border-radius: ${themeCssVariables.border.radius.sm};
@@ -468,7 +233,7 @@ export const DatePicker = ({
: null;
return (
<StyledContainer calendarDisabled={isRelative}>
<StyledDatePickerContainer calendarDisabled={isRelative}>
<div className={clearable ? 'clearable ' : ''}>
<Suspense
fallback={
@@ -546,6 +311,6 @@ export const DatePicker = ({
</StyledButtonContent>
</StyledButtonContainer>
)}
</StyledContainer>
</StyledDatePickerContainer>
);
};
@@ -9,6 +9,10 @@ import { CalendarStartDay } from 'twenty-shared/constants';
import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay';
import { DatePickerHeader } from '@/ui/input/components/internal/date/components/DatePickerHeader';
import {
DATE_PICKER_CONTAINER_WIDTH,
StyledDatePickerContainer,
} from '@/ui/input/components/internal/date/components/StyledDatePickerContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import 'react-datepicker/dist/react-datepicker.css';
@@ -23,257 +27,8 @@ export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID =
export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID =
'date-picker-month-and-year-dropdown-year-select';
const DATE_PICKER_CONTAINER_WIDTH = 280;
const DATE_PICKER_SKELETON_PADDING = 16;
const StyledContainer = styled.div<{
calendarDisabled?: boolean;
hideCalendar?: boolean;
}>`
width: ${DATE_PICKER_CONTAINER_WIDTH}px;
& .react-datepicker {
border-color: ${themeCssVariables.border.color.light};
background: transparent;
font-family: 'Inter';
font-size: ${themeCssVariables.font.size.md};
border: none;
display: block;
font-weight: ${themeCssVariables.font.weight.regular};
}
& .react-datepicker-popper {
position: relative !important;
inset: auto !important;
transform: none !important;
padding: 0 !important;
}
& .react-datepicker__triangle {
display: none;
}
& .react-datepicker__triangle::after {
display: none;
}
& .react-datepicker__triangle::before {
display: none;
}
& .react-datepicker-wrapper {
display: none;
}
// Header
& .react-datepicker__header {
background: transparent;
border: none;
padding: 0;
}
&
.react-datepicker__input-time-container
.react-datepicker-time__input-container
.react-datepicker-time__input {
outline: none;
}
& .react-datepicker__header__dropdown {
display: flex;
color: ${themeCssVariables.font.color.primary};
margin-left: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[10]};
}
& .react-datepicker__month-dropdown-container,
& .react-datepicker__year-dropdown-container {
text-align: left;
border-radius: ${themeCssVariables.border.radius.sm};
margin-left: ${themeCssVariables.spacing[1]};
margin-right: 0;
padding: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[4]};
background-color: ${themeCssVariables.background.tertiary};
}
& .react-datepicker__month-read-view--down-arrow,
& .react-datepicker__year-read-view--down-arrow {
height: 5px;
width: 5px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.border.color.light};
top: 3px;
right: -6px;
}
& .react-datepicker__year-read-view,
& .react-datepicker__month-read-view {
padding-right: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown-container {
width: 80px;
}
& .react-datepicker__year-dropdown-container {
width: 50px;
}
& .react-datepicker__month-dropdown,
& .react-datepicker__year-dropdown {
overflow-y: scroll;
top: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown {
left: ${themeCssVariables.spacing[2]};
height: 260px;
}
& .react-datepicker__year-dropdown {
left: calc(${themeCssVariables.spacing[9]} + 80px);
width: 100px;
height: 260px;
}
& .react-datepicker__navigation--years {
display: none;
}
& .react-datepicker__month-option--selected,
& .react-datepicker__year-option--selected {
display: none;
}
& .react-datepicker__year-option,
& .react-datepicker__month-option {
text-align: left;
padding: ${themeCssVariables.spacing[2]}
calc(${themeCssVariables.spacing[2]} - 2px);
width: calc(100% - ${themeCssVariables.spacing[4]});
border-radius: ${themeCssVariables.border.radius.xs};
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
margin: 2px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__year-option {
&:first-of-type,
&:last-of-type {
display: none;
}
}
& .react-datepicker__current-month {
display: none;
}
& .react-datepicker__day-name {
color: ${themeCssVariables.font.color.secondary};
width: 34px;
height: 40px;
line-height: 40px;
}
& .react-datepicker__month-container {
float: none;
}
// Days
& .react-datepicker__month {
margin-top: 0;
pointer-events: ${({ calendarDisabled }) =>
calendarDisabled ? 'none' : 'auto'};
opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')};
display: ${({ hideCalendar }) =>
hideCalendar === true ? 'none' : 'visible'};
}
& .react-datepicker__day-names {
display: ${({ hideCalendar }) =>
hideCalendar === true ? 'none' : 'visible'};
}
& .react-datepicker__day {
width: 34px;
height: 34px;
line-height: 34px;
}
& .react-datepicker__navigation--previous,
& .react-datepicker__navigation--next {
height: 34px;
border-radius: ${themeCssVariables.border.radius.sm};
padding-top: 6px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__navigation--previous {
right: 38px;
top: 6px;
left: auto;
& > span {
margin-left: -6px;
}
}
& .react-datepicker__navigation--next {
right: 6px;
top: 6px;
& > span {
margin-left: 6px;
}
}
& .react-datepicker__navigation-icon::before {
height: 7px;
width: 7px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day--keyboard-selected {
background-color: inherit;
}
& .react-datepicker__day,
.react-datepicker__time-name {
color: ${themeCssVariables.font.color.primary};
}
& .react-datepicker__day--selected {
background-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.background.primary};
&.react-datepicker__day:hover {
color: ${themeCssVariables.background.primary};
}
}
& .react-datepicker__day--outside-month {
color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day:hover {
color: ${themeCssVariables.font.color.tertiary};
}
& .clearable {
border-bottom: 1px solid ${themeCssVariables.border.color.light};
}
`;
const StyledSpacer = styled.div`
height: ${themeCssVariables.spacing[2]};
width: auto;
@@ -399,7 +154,7 @@ export const DatePickerWithoutCalendar = ({
: null;
return (
<StyledContainer hideCalendar={true}>
<StyledDatePickerContainer hideCalendar={true}>
<div>
<Suspense
fallback={
@@ -469,6 +224,6 @@ export const DatePickerWithoutCalendar = ({
/>
</Suspense>
</div>
</StyledContainer>
</StyledDatePickerContainer>
);
};
@@ -10,6 +10,7 @@ import {
DateTimePickerHeader,
} from '@/ui/input/components/internal/date/components/DateTimePickerHeader';
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
import { StyledDatePickerContainer } from '@/ui/input/components/internal/date/components/StyledDatePickerContainer';
import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { styled } from '@linaria/react';
@@ -44,243 +45,6 @@ const StyledOuterWrapper = styled.div`
width: 280px;
`;
const StyledContainer = styled.div<{
calendarDisabled?: boolean;
}>`
width: 280px;
& .react-datepicker {
border-color: ${themeCssVariables.border.color.light};
background: transparent;
font-family: 'Inter';
font-size: ${themeCssVariables.font.size.md};
border: none;
display: block;
font-weight: ${themeCssVariables.font.weight.regular};
}
& .react-datepicker-popper {
position: relative !important;
inset: auto !important;
transform: none !important;
padding: 0 !important;
}
& .react-datepicker__triangle {
display: none;
}
& .react-datepicker__triangle::after {
display: none;
}
& .react-datepicker__triangle::before {
display: none;
}
& .react-datepicker-wrapper {
display: none;
}
// Header
& .react-datepicker__header {
background: transparent;
border: none;
padding: 0;
}
&
.react-datepicker__input-time-container
.react-datepicker-time__input-container
.react-datepicker-time__input {
outline: none;
}
& .react-datepicker__header__dropdown {
display: flex;
color: ${themeCssVariables.font.color.primary};
margin-left: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[10]};
}
& .react-datepicker__month-dropdown-container,
& .react-datepicker__year-dropdown-container {
text-align: left;
border-radius: ${themeCssVariables.border.radius.sm};
margin-left: ${themeCssVariables.spacing[1]};
margin-right: 0;
padding: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[4]};
background-color: ${themeCssVariables.background.tertiary};
}
& .react-datepicker__month-read-view--down-arrow,
& .react-datepicker__year-read-view--down-arrow {
height: 5px;
width: 5px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.border.color.light};
top: 3px;
right: -6px;
}
& .react-datepicker__year-read-view,
& .react-datepicker__month-read-view {
padding-right: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown-container {
width: 80px;
}
& .react-datepicker__year-dropdown-container {
width: 50px;
}
& .react-datepicker__month-dropdown,
& .react-datepicker__year-dropdown {
overflow-y: scroll;
top: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown {
left: ${themeCssVariables.spacing[2]};
height: 260px;
}
& .react-datepicker__year-dropdown {
left: calc(${themeCssVariables.spacing[9]} + 80px);
width: 100px;
height: 260px;
}
& .react-datepicker__navigation--years {
display: none;
}
& .react-datepicker__month-option--selected,
& .react-datepicker__year-option--selected {
display: none;
}
& .react-datepicker__year-option,
& .react-datepicker__month-option {
text-align: left;
padding: ${themeCssVariables.spacing[2]}
calc(${themeCssVariables.spacing[2]} - 2px);
width: calc(100% - ${themeCssVariables.spacing[4]});
border-radius: ${themeCssVariables.border.radius.xs};
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
margin: 2px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__year-option {
&:first-of-type,
&:last-of-type {
display: none;
}
}
& .react-datepicker__current-month {
display: none;
}
& .react-datepicker__day-name {
color: ${themeCssVariables.font.color.secondary};
width: 34px;
height: 40px;
line-height: 40px;
}
& .react-datepicker__month-container {
float: none;
}
// Days
& .react-datepicker__month {
margin-top: 0;
margin-left: 0;
margin-right: 0;
pointer-events: ${({ calendarDisabled }) =>
calendarDisabled ? 'none' : 'auto'};
opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')};
}
& .react-datepicker__day {
width: 34px;
height: 34px;
line-height: 34px;
}
& .react-datepicker__navigation--previous,
& .react-datepicker__navigation--next {
height: 34px;
border-radius: ${themeCssVariables.border.radius.sm};
padding-top: 6px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__navigation--previous {
right: 38px;
top: 6px;
left: auto;
& > span {
margin-left: -6px;
}
}
& .react-datepicker__navigation--next {
right: 6px;
top: 6px;
& > span {
margin-left: 6px;
}
}
& .react-datepicker__navigation-icon::before {
height: 7px;
width: 7px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day--keyboard-selected {
background-color: inherit;
}
& .react-datepicker__day,
.react-datepicker__time-name {
color: ${themeCssVariables.font.color.primary};
}
& .react-datepicker__day--selected {
background-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.background.primary};
&.react-datepicker__day:hover {
color: ${themeCssVariables.background.primary};
}
}
& .react-datepicker__day--outside-month {
color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day:hover {
color: ${themeCssVariables.font.color.tertiary};
}
`;
const StyledSeparator = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
width: 100%;
@@ -502,7 +266,7 @@ export const DateTimePicker = ({
return (
<StyledOuterWrapper>
<StyledContainer calendarDisabled={isRelative}>
<StyledDatePickerContainer calendarDisabled={isRelative}>
<Suspense
fallback={
<StyledDatePickerFallback>
@@ -582,7 +346,7 @@ export const DateTimePicker = ({
</StyledButtonContainer>
</>
)}
</StyledContainer>
</StyledDatePickerContainer>
</StyledOuterWrapper>
);
};
@@ -0,0 +1,263 @@
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const DATE_PICKER_CONTAINER_WIDTH = 280;
// Shared react-datepicker overrides for every internal date picker
// (DatePicker, DateTimePicker, DatePickerWithoutCalendar). Keep the calendar
// grid styling in one place so day/day-name spacing stays consistent across all
// pickers instead of drifting between per-component copies.
// - calendarDisabled: dims and freezes the grid (used for relative date mode)
// - hideCalendar: hides the day grid, keeping only the month/year header
export const StyledDatePickerContainer = styled.div<{
calendarDisabled?: boolean;
hideCalendar?: boolean;
}>`
width: ${DATE_PICKER_CONTAINER_WIDTH}px;
& .react-datepicker {
border-color: ${themeCssVariables.border.color.light};
background: transparent;
font-family: 'Inter';
font-size: ${themeCssVariables.font.size.md};
border: none;
display: block;
font-weight: ${themeCssVariables.font.weight.regular};
}
& .react-datepicker-popper {
position: relative !important;
inset: auto !important;
transform: none !important;
padding: 0 !important;
}
& .react-datepicker__triangle {
display: none;
}
& .react-datepicker__triangle::after {
display: none;
}
& .react-datepicker__triangle::before {
display: none;
}
& .react-datepicker-wrapper {
display: none;
}
// Header
& .react-datepicker__header {
background: transparent;
border: none;
padding: 0;
}
&
.react-datepicker__input-time-container
.react-datepicker-time__input-container
.react-datepicker-time__input {
outline: none;
}
& .react-datepicker__header__dropdown {
display: flex;
color: ${themeCssVariables.font.color.primary};
margin-left: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[10]};
}
& .react-datepicker__month-dropdown-container,
& .react-datepicker__year-dropdown-container {
text-align: left;
border-radius: ${themeCssVariables.border.radius.sm};
margin-left: ${themeCssVariables.spacing[1]};
margin-right: 0;
padding: ${themeCssVariables.spacing[2]};
padding-right: ${themeCssVariables.spacing[4]};
background-color: ${themeCssVariables.background.tertiary};
}
& .react-datepicker__month-read-view--down-arrow,
& .react-datepicker__year-read-view--down-arrow {
height: 5px;
width: 5px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.border.color.light};
top: 3px;
right: -6px;
}
& .react-datepicker__year-read-view,
& .react-datepicker__month-read-view {
padding-right: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown-container {
width: 80px;
}
& .react-datepicker__year-dropdown-container {
width: 50px;
}
& .react-datepicker__month-dropdown,
& .react-datepicker__year-dropdown {
overflow-y: scroll;
top: ${themeCssVariables.spacing[2]};
}
& .react-datepicker__month-dropdown {
left: ${themeCssVariables.spacing[2]};
height: 260px;
}
& .react-datepicker__year-dropdown {
left: calc(${themeCssVariables.spacing[9]} + 80px);
width: 100px;
height: 260px;
}
& .react-datepicker__navigation--years {
display: none;
}
& .react-datepicker__month-option--selected,
& .react-datepicker__year-option--selected {
display: none;
}
& .react-datepicker__year-option,
& .react-datepicker__month-option {
text-align: left;
padding: ${themeCssVariables.spacing[2]}
calc(${themeCssVariables.spacing[2]} - 2px);
width: calc(100% - ${themeCssVariables.spacing[4]});
border-radius: ${themeCssVariables.border.radius.xs};
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
margin: 2px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__year-option {
&:first-of-type,
&:last-of-type {
display: none;
}
}
& .react-datepicker__current-month {
display: none;
}
& .react-datepicker__day-name {
color: ${themeCssVariables.font.color.secondary};
width: 34px;
height: 40px;
line-height: 40px;
// Uniform 2px margin keeps the header row aligned with the day grid below;
// react-datepicker's default (0.166rem) leaves the grid tighter on the right.
margin: 2px;
}
& .react-datepicker__month-container {
float: none;
}
// Days
& .react-datepicker__month {
margin: 0;
pointer-events: ${({ calendarDisabled }) =>
calendarDisabled ? 'none' : 'auto'};
opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')};
display: ${({ hideCalendar }) =>
hideCalendar === true ? 'none' : 'visible'};
}
& .react-datepicker__day-names {
display: ${({ hideCalendar }) =>
hideCalendar === true ? 'none' : 'visible'};
}
& .react-datepicker__day {
width: 34px;
height: 34px;
line-height: 34px;
// Matches the day-name margin so each column lines up symmetrically.
margin: 2px;
}
& .react-datepicker__navigation--previous,
& .react-datepicker__navigation--next {
height: 34px;
border-radius: ${themeCssVariables.border.radius.sm};
padding-top: 6px;
&:hover {
background: ${themeCssVariables.background.transparent.light};
}
}
& .react-datepicker__navigation--previous {
right: 38px;
top: 6px;
left: auto;
& > span {
margin-left: -6px;
}
}
& .react-datepicker__navigation--next {
right: 6px;
top: 6px;
& > span {
margin-left: 6px;
}
}
& .react-datepicker__navigation-icon::before {
height: 7px;
width: 7px;
border-width: 1px 1px 0 0;
border-color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day--keyboard-selected {
background-color: inherit;
}
& .react-datepicker__day,
.react-datepicker__time-name {
color: ${themeCssVariables.font.color.primary};
}
& .react-datepicker__day--selected {
background-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.background.primary};
&.react-datepicker__day:hover {
color: ${themeCssVariables.background.primary};
}
}
& .react-datepicker__day--outside-month {
color: ${themeCssVariables.font.color.tertiary};
}
& .react-datepicker__day:hover {
color: ${themeCssVariables.font.color.tertiary};
}
& .clearable {
border-bottom: 1px solid ${themeCssVariables.border.color.light};
}
`;
@@ -0,0 +1,106 @@
import { DatePicker } from '@/ui/input/components/internal/date/components/DatePicker';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { expect, userEvent, within } from 'storybook/test';
import { type RelativeDateFilter } from 'twenty-shared/utils';
import { ComponentDecorator } from 'twenty-ui/testing';
const INITIAL_PLAIN_DATE = '2023-01-01';
const RELATIVE_DATE: RelativeDateFilter & { start: string; end: string } = {
direction: 'PAST',
amount: 7,
unit: 'DAY',
start: '2022-12-25',
end: '2023-01-01',
};
const DatePickerStory = ({
isRelative,
clearable,
}: {
isRelative?: boolean;
clearable?: boolean;
}) => {
const [plainDateString, setPlainDateString] = useState<string | null>(
INITIAL_PLAIN_DATE,
);
return (
<DatePicker
instanceId="story-date-picker"
plainDateString={plainDateString}
onChange={setPlainDateString}
clearable={clearable}
isRelative={isRelative}
relativeDate={isRelative ? RELATIVE_DATE : undefined}
/>
);
};
const meta: Meta<typeof DatePicker> = {
title: 'UI/Input/Internal/Date/DatePicker',
component: DatePicker,
decorators: [ComponentDecorator],
render: () => <DatePickerStory />,
};
export default meta;
type Story = StoryObj<typeof DatePicker>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Lazy-loaded react-datepicker renders the day grid; wait for a day cell.
await canvas.findByText('15', {}, { timeout: 10000 });
},
};
export const NotClearable: Story = {
render: () => <DatePickerStory clearable={false} />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('15', {}, { timeout: 10000 });
expect(canvas.queryByText('Clear')).not.toBeInTheDocument();
},
};
export const Relative: Story = {
render: () => <DatePickerStory isRelative />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('15', {}, { timeout: 10000 });
},
};
export const WithOpenMonthSelect: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);
const monthSelect = await canvas.findByText(
'January',
{},
{ timeout: 10000 },
);
await userEvent.click(monthSelect);
for (const monthLabel of ['February', 'June', 'December']) {
expect(await body.findByText(monthLabel)).toBeInTheDocument();
}
},
};
export const WithOpenYearSelect: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const body = within(canvasElement.ownerDocument.body);
const yearSelect = await canvas.findByText('2023', {}, { timeout: 10000 });
await userEvent.click(yearSelect);
for (const yearLabel of ['2022', '2024']) {
expect(await body.findByText(yearLabel)).toBeInTheDocument();
}
},
};
@@ -0,0 +1,42 @@
import { DatePickerWithoutCalendar } from '@/ui/input/components/internal/date/components/DatePickerWithoutCalendar';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { expect, within } from 'storybook/test';
import { ComponentDecorator } from 'twenty-ui/testing';
const INITIAL_PLAIN_DATE = '2023-01-01';
const DatePickerWithoutCalendarStory = () => {
const [date, setDate] = useState<string | null>(INITIAL_PLAIN_DATE);
return (
<DatePickerWithoutCalendar
instanceId="story-date-picker-without-calendar"
date={date}
onChange={setDate}
/>
);
};
const meta: Meta<typeof DatePickerWithoutCalendar> = {
title: 'UI/Input/Internal/Date/DatePickerWithoutCalendar',
component: DatePickerWithoutCalendar,
decorators: [ComponentDecorator],
render: () => <DatePickerWithoutCalendarStory />,
};
export default meta;
type Story = StoryObj<typeof DatePickerWithoutCalendar>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Calendar grid is hidden; only the month/year header should be present.
const monthSelect = await canvas.findByText(
'January',
{},
{ timeout: 10000 },
);
expect(monthSelect).toBeInTheDocument();
},
};
@@ -22,7 +22,7 @@ const DateTimePickerStory = () => {
};
const meta: Meta<typeof DateTimePicker> = {
title: 'UI/Input/Internal/InternalDatePicker',
title: 'UI/Input/Internal/Date/DateTimePicker',
component: DateTimePicker,
decorators: [ComponentDecorator],
render: () => <DateTimePickerStory />,