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:
committed by
GitHub
parent
05df528fd5
commit
c635a191bf
+6
-241
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-251
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-239
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+263
@@ -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};
|
||||
}
|
||||
`;
|
||||
+106
@@ -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();
|
||||
}
|
||||
},
|
||||
};
|
||||
+42
@@ -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();
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -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 />,
|
||||
Reference in New Issue
Block a user