Skip to content

Commit 418983d

Browse files
committed
Fix responsive behaviour for single calendar
1 parent 7e1836f commit 418983d

2 files changed

Lines changed: 71 additions & 82 deletions

File tree

packages/@react-spectrum/s2/src/Calendar.tsx

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -72,44 +72,59 @@ export interface CalendarProps<T extends DateValue>
7272

7373
export const CalendarContext = createContext<ContextValue<Partial<CalendarProps<any>>, HTMLDivElement>>(null);
7474

75-
const calendarStyles = style({
75+
const calendarStyles = style<{isMultiMonth?: boolean}>({
7676
display: 'flex',
77+
containerType: {
78+
default: 'inline-size',
79+
isMultiMonth: 'unset'
80+
},
7781
flexDirection: 'column',
7882
gap: 24,
79-
width: 'fit',
8083
disableTapHighlight: true,
8184
'--cell-gap': {
8285
type: 'paddingStart',
8386
value: 4
8487
},
85-
'--cell-min-width': {
88+
'--cell-max-width': {
8689
type: 'width',
8790
value: 32
91+
},
92+
'--cell-responsive-size': {
93+
type: 'width',
94+
value: {
95+
default: '[min(var(--cell-max-width), (100cqw - (var(--cell-gap) * 12)) / 7)]',
96+
isMultiMonth: '--cell-max-width'
97+
}
98+
},
99+
width: {
100+
default: 'calc(7 * var(--cell-max-width) + var(--cell-gap) * 12)',
101+
isMultiMonth: 'fit'
102+
},
103+
maxWidth: {
104+
default: 'full',
105+
isMultiMonth: 'unset'
88106
}
89107
}, getAllowedOverrides());
90108

91109
const headerStyles = style({
92110
display: 'flex',
93111
alignItems: 'center',
94-
justifyContent: 'space-between',
95-
width: 'full'
112+
justifyContent: 'space-between'
96113
});
97114

98115
const headingStyles = style({
99116
display: 'flex',
100117
alignItems: 'center',
101118
justifyContent: 'space-between',
102119
margin: 0,
103-
width: 'full'
120+
flexGrow: 1
104121
});
105122

106123
const titleStyles = style({
107124
font: 'title-lg',
108125
textAlign: 'center',
109126
flexGrow: 1,
110-
flexShrink: 0,
111-
flexBasis: '0%',
112-
minWidth: 0
127+
flexShrink: 0
113128
});
114129

115130
const headerCellStyles = style({
@@ -154,11 +169,7 @@ const cellStyles = style({
154169
alignItems: 'center',
155170
justifyContent: 'center',
156171
disableTapHighlight: true,
157-
'--cell-min-width': {
158-
type: 'width',
159-
value: 32
160-
},
161-
width: '[min(var(--cell-min-width), calc((100cqw / 7) - var(--cell-gap)))]',
172+
width: '--cell-responsive-size',
162173
aspectRatio: 'square',
163174
height: 'auto'
164175
});
@@ -301,7 +312,7 @@ const cellInnerStyles = style<CalendarCellRenderProps & {selectionMode: 'single'
301312

302313
const todayStyles = style({
303314
position: 'absolute',
304-
bottom: 4,
315+
bottom: '12.5%',
305316
left: '50%',
306317
transform: 'translateX(-50%)',
307318
width: 4,
@@ -430,13 +441,14 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
430441
...otherProps
431442
} = props;
432443
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
444+
let isMultiMonth = visibleMonths > 1;
433445
return (
434446
<AriaCalendar
435447
{...otherProps}
436448
ref={ref}
437449
visibleDuration={{months: visibleMonths}}
438450
style={UNSAFE_style}
439-
className={(UNSAFE_className || '') + calendarStyles(null, styles)}>
451+
className={(UNSAFE_className || '') + calendarStyles({isMultiMonth}, styles)}>
440452
{({isInvalid, isDisabled}) => {
441453
return (
442454
<>
@@ -445,11 +457,7 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
445457
[HeaderContext, null],
446458
[HeadingContext, null]
447459
]}>
448-
<Header styles={headerStyles}>
449-
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
450-
<CalendarHeading />
451-
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
452-
</Header>
460+
<CalendarHeader />
453461
</Provider>
454462
<div
455463
className={style({
@@ -460,23 +468,7 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
460468
alignItems: 'start'
461469
})}>
462470
{Array.from({length: visibleMonths}).map((_, i) => (
463-
<div
464-
key={i}
465-
style={{'--visible-months': visibleMonths} as React.CSSProperties}
466-
className={style({
467-
containerType: 'inline-size',
468-
flexGrow: 1,
469-
flexShrink: 0,
470-
flexBasis: '0%',
471-
minWidth: 0,
472-
width: 'calc(7 * var(--cell-min-width) + var(--cell-gap) * 12)',
473-
maxWidth: {
474-
default: 'calc(100vw / var(--visible-months))',
475-
'@media (max-width: 375px)': '100%'
476-
}
477-
})}>
478-
<CalendarGrid months={i} />
479-
</div>
471+
<CalendarGrid key={i} months={i} />
480472
))}
481473
</div>
482474
{isInvalid && (
@@ -491,6 +483,16 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
491483
);
492484
});
493485

486+
export const CalendarHeader = (): ReactElement => {
487+
return (
488+
<Header styles={headerStyles}>
489+
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
490+
<CalendarHeading />
491+
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
492+
</Header>
493+
);
494+
};
495+
494496
export const CalendarGrid = (props: Omit<AriaCalendarGridProps, 'children'> & PropsWithChildren & {months: number}): ReactElement => {
495497
let rangeCalendarProps = useSlottedContext(RangeCalendarContext);
496498
let calendarProps = useSlottedContext(AriaCalendarContext);
@@ -502,8 +504,7 @@ export const CalendarGrid = (props: Omit<AriaCalendarGridProps, 'children'> & Pr
502504
className={style({
503505
borderCollapse: 'collapse',
504506
borderSpacing: 0,
505-
isolation: 'isolate',
506-
width: 'full'
507+
isolation: 'isolate'
507508
})}
508509
offset={{months: props.months}}>
509510
<CalendarGridHeader className="">
@@ -524,7 +525,7 @@ export const CalendarGrid = (props: Omit<AriaCalendarGridProps, 'children'> & Pr
524525

525526
// Ordinarily the heading is a formatted date range, ie January 2025 - February 2025.
526527
// However, we want to show each month individually.
527-
export const CalendarHeading = (): ReactElement => {
528+
const CalendarHeading = (): ReactElement => {
528529
let calendarStateContext = useContext(CalendarStateContext);
529530
let rangeCalendarStateContext = useContext(RangeCalendarStateContext);
530531
let {visibleRange, timeZone} = calendarStateContext ?? rangeCalendarStateContext ?? {};

packages/@react-spectrum/s2/src/RangeCalendar.tsx

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@ import {
1515
RangeCalendarProps as AriaRangeCalendarProps,
1616
DateValue
1717
} from 'react-aria-components/RangeCalendar';
18-
import {CalendarButton, CalendarGrid, CalendarHeading} from './Calendar';
19-
import ChevronLeftIcon from '../s2wf-icons/S2_Icon_ChevronLeft_20_N.svg';
20-
import ChevronRightIcon from '../s2wf-icons/S2_Icon_ChevronRight_20_N.svg';
18+
import {CalendarGrid, CalendarHeader} from './Calendar';
2119
import {ContextValue, Provider} from 'react-aria-components/utils';
2220
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode} from 'react';
2321
import {forwardRefType, GlobalDOMAttributes} from '@react-types/shared';
2422
import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
25-
import {Header, HeaderContext, HeadingContext} from './Content';
23+
import {HeaderContext, HeadingContext} from './Content';
2624
import {helpTextStyles} from './Field';
2725
// @ts-ignore
2826
import intlMessages from '../intl/*.json';
@@ -31,7 +29,6 @@ import {Text} from 'react-aria-components/Text';
3129
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
3230
import {useSpectrumContextProps} from './useSpectrumContextProps';
3331

34-
3532
export interface RangeCalendarProps<T extends DateValue>
3633
extends Omit<AriaRangeCalendarProps<T>, 'visibleDuration' | 'style' | 'className' | 'render' | 'children' | 'styles' | keyof GlobalDOMAttributes>,
3734
StyleProps {
@@ -48,29 +45,40 @@ export interface RangeCalendarProps<T extends DateValue>
4845

4946
export const RangeCalendarContext = createContext<ContextValue<Partial<RangeCalendarProps<any>>, HTMLDivElement>>(null);
5047

51-
52-
const calendarStyles = style({
48+
const calendarStyles = style<{isMultiMonth?: boolean}>({
5349
display: 'flex',
50+
containerType: {
51+
default: 'inline-size',
52+
isMultiMonth: 'unset'
53+
},
5454
flexDirection: 'column',
5555
gap: 24,
56-
width: 'fit',
56+
disableTapHighlight: true,
5757
'--cell-gap': {
5858
type: 'paddingStart',
5959
value: 4
6060
},
61-
'--cell-min-width': {
61+
'--cell-max-width': {
6262
type: 'width',
6363
value: 32
64+
},
65+
'--cell-responsive-size': {
66+
type: 'width',
67+
value: {
68+
default: '[min(var(--cell-max-width), (100cqw - (var(--cell-gap) * 12)) / 7)]',
69+
isMultiMonth: '--cell-max-width'
70+
}
71+
},
72+
width: {
73+
default: 'calc(7 * var(--cell-max-width) + var(--cell-gap) * 12)',
74+
isMultiMonth: 'fit'
75+
},
76+
maxWidth: {
77+
default: 'full',
78+
isMultiMonth: 'unset'
6479
}
6580
}, getAllowedOverrides());
6681

67-
const headerStyles = style({
68-
display: 'flex',
69-
alignItems: 'center',
70-
justifyContent: 'space-between',
71-
width: 'full'
72-
});
73-
7482
/**
7583
* RangeCalendars display a grid of days in one or more months and allow users to select a contiguous range of dates.
7684
*/
@@ -86,13 +94,14 @@ export const RangeCalendar = /*#__PURE__*/ (forwardRef as forwardRefType)(functi
8694
} = props;
8795
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
8896

97+
let isMultiMonth = visibleMonths > 1;
8998
return (
9099
<AriaRangeCalendar
91100
{...otherProps}
92101
ref={ref}
93102
visibleDuration={{months: visibleMonths}}
94-
style={UNSAFE_style}
95-
className={(UNSAFE_className || '') + calendarStyles(null, styles)}>
103+
style={{...UNSAFE_style, '--num-calendars': visibleMonths} as CSSProperties}
104+
className={(UNSAFE_className || '') + calendarStyles({isMultiMonth}, styles)}>
96105
{({isInvalid, isDisabled}) => {
97106
return (
98107
<>
@@ -101,38 +110,17 @@ export const RangeCalendar = /*#__PURE__*/ (forwardRef as forwardRefType)(functi
101110
[HeaderContext, null],
102111
[HeadingContext, null]
103112
]}>
104-
<Header styles={headerStyles}>
105-
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
106-
<CalendarHeading />
107-
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
108-
</Header>
113+
<CalendarHeader />
109114
</Provider>
110115
<div
111116
className={style({
112117
display: 'flex',
113118
flexDirection: 'row',
114119
gap: 24,
115-
width: 'full',
116120
alignItems: 'start'
117121
})}>
118122
{Array.from({length: visibleMonths}).map((_, i) => (
119-
<div
120-
key={i}
121-
style={{'--visible-months': visibleMonths} as CSSProperties}
122-
className={style({
123-
containerType: 'inline-size',
124-
flexGrow: 1,
125-
flexShrink: 0,
126-
flexBasis: '0%',
127-
minWidth: 0,
128-
width: 'calc(7 * var(--cell-min-width) + var(--cell-gap) * 12)',
129-
maxWidth: {
130-
default: 'calc(100vw / var(--visible-months))',
131-
'@media (max-width: 375px)': '100%'
132-
}
133-
})}>
134-
<CalendarGrid months={i} />
135-
</div>
123+
<CalendarGrid key={i} months={i} />
136124
))}
137125
</div>
138126
{isInvalid && (

0 commit comments

Comments
 (0)