Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 56 additions & 31 deletions packages/@react-spectrum/s2/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,59 @@ export interface CalendarProps<T extends DateValue>

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

const calendarStyles = style({
const calendarStyles = style<{isMultiMonth?: boolean}>({
display: 'flex',
containerType: {
default: 'inline-size',
isMultiMonth: 'unset'
},
flexDirection: 'column',
gap: 24,
width: 'fit',
disableTapHighlight: true
disableTapHighlight: true,
'--cell-gap': {
type: 'paddingStart',
value: 4
},
'--cell-max-width': {
type: 'width',
value: 32
},
'--cell-responsive-size': {
type: 'width',
value: {
default: '[min(var(--cell-max-width), (100cqw - (var(--cell-gap) * 12)) / 7)]',
isMultiMonth: '--cell-max-width'
}
},
width: {
default: 'calc(7 * var(--cell-max-width) + var(--cell-gap) * 12)',
isMultiMonth: 'fit'
},
maxWidth: {
default: 'full',
isMultiMonth: 'unset'
}
}, getAllowedOverrides());

const headerStyles = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: 'full'
justifyContent: 'space-between'
});

const headingStyles = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
margin: 0,
width: 'full'
flexGrow: 1
});

const titleStyles = style({
font: 'title-lg',
textAlign: 'center',
flexGrow: 1,
flexShrink: 0,
flexBasis: '0%',
minWidth: 0
flexShrink: 0
});

const headerCellStyles = style({
Expand All @@ -121,10 +144,7 @@ const headerCellStyles = style({

const cellStyles = style({
outlineStyle: 'none',
'--cell-gap': {
type: 'paddingStart',
value: 4
},
boxSizing: 'content-box',
paddingStart: {
default: 4,
isFirstChild: 0
Expand All @@ -142,15 +162,16 @@ const cellStyles = style({
isLastWeek: 0
},
position: 'relative',
width: 32,
height: 32,
display: {
default: 'flex',
isOutsideMonth: 'none'
},
alignItems: 'center',
justifyContent: 'center',
disableTapHighlight: true
disableTapHighlight: true,
width: '--cell-responsive-size',
aspectRatio: 'square',
height: 'auto'
});

const cellInnerStyles = style<CalendarCellRenderProps & {selectionMode: 'single' | 'range'}>({
Expand All @@ -174,7 +195,7 @@ const cellInnerStyles = style<CalendarCellRenderProps & {selectionMode: 'single'
font: 'body-sm',
cursor: 'default',
width: 'full',
height: 32,
height: 'full',
borderRadius: 'full',
display: 'flex',
alignItems: 'center',
Expand Down Expand Up @@ -291,7 +312,7 @@ const cellInnerStyles = style<CalendarCellRenderProps & {selectionMode: 'single'

const todayStyles = style({
position: 'absolute',
bottom: 4,
bottom: '12.5%',
left: '50%',
transform: 'translateX(-50%)',
width: 4,
Expand Down Expand Up @@ -420,13 +441,14 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
...otherProps
} = props;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
let isMultiMonth = visibleMonths > 1;
return (
<AriaCalendar
{...otherProps}
ref={ref}
visibleDuration={{months: visibleMonths}}
style={UNSAFE_style}
className={(UNSAFE_className || '') + calendarStyles(null, styles)}>
className={(UNSAFE_className || '') + calendarStyles({isMultiMonth}, styles)}>
{({isInvalid, isDisabled}) => {
return (
<>
Expand All @@ -435,11 +457,7 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
[HeaderContext, null],
[HeadingContext, null]
]}>
<Header styles={headerStyles}>
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
<CalendarHeading />
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
</Header>
<CalendarHeader />
</Provider>
<div
className={style({
Expand All @@ -450,7 +468,7 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
alignItems: 'start'
})}>
{Array.from({length: visibleMonths}).map((_, i) => (
<CalendarGrid months={i} key={i} />
<CalendarGrid key={i} months={i} />
))}
</div>
{isInvalid && (
Expand All @@ -465,6 +483,16 @@ export const Calendar = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
);
});

export const CalendarHeader = (): ReactElement => {
return (
<Header styles={headerStyles}>
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
<CalendarHeading />
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
</Header>
);
};

export const CalendarGrid = (props: Omit<AriaCalendarGridProps, 'children'> & PropsWithChildren & {months: number}): ReactElement => {
let rangeCalendarProps = useSlottedContext(RangeCalendarContext);
let calendarProps = useSlottedContext(AriaCalendarContext);
Expand Down Expand Up @@ -497,7 +525,7 @@ export const CalendarGrid = (props: Omit<AriaCalendarGridProps, 'children'> & Pr

// Ordinarily the heading is a formatted date range, ie January 2025 - February 2025.
// However, we want to show each month individually.
export const CalendarHeading = (): ReactElement => {
const CalendarHeading = (): ReactElement => {
let calendarStateContext = useContext(CalendarStateContext);
let rangeCalendarStateContext = useContext(RangeCalendarStateContext);
let {visibleRange, timeZone} = calendarStateContext ?? rangeCalendarStateContext ?? {};
Expand Down Expand Up @@ -648,11 +676,8 @@ const CalendarCellInner = (props: Omit<CalendarCellProps, 'children'> & {isRange
<div
className={style({
position: 'relative',
width: 32,
'--cell-width': {
type: 'width',
value: '[self(width)]'
}
width: 'full',
height: 'full'
})}>
<div
ref={ref}
Expand Down
62 changes: 37 additions & 25 deletions packages/@react-spectrum/s2/src/RangeCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ import {
RangeCalendarProps as AriaRangeCalendarProps,
DateValue
} from 'react-aria-components/RangeCalendar';
import {CalendarButton, CalendarGrid, CalendarHeading} from './Calendar';
import ChevronLeftIcon from '../s2wf-icons/S2_Icon_ChevronLeft_20_N.svg';
import ChevronRightIcon from '../s2wf-icons/S2_Icon_ChevronRight_20_N.svg';
import {CalendarGrid, CalendarHeader} from './Calendar';
import {ContextValue, Provider} from 'react-aria-components/slots';
import {createContext, ForwardedRef, forwardRef, ReactNode} from 'react';
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode} from 'react';
import {forwardRefType, GlobalDOMAttributes} from '@react-types/shared';
import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {Header, HeaderContext, HeadingContext} from './Content';
import {HeaderContext, HeadingContext} from './Content';
import {helpTextStyles} from './Field';
// @ts-ignore
import intlMessages from '../intl/*.json';
Expand All @@ -31,7 +29,6 @@ import {Text} from 'react-aria-components/Text';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
import {useSpectrumContextProps} from './useSpectrumContextProps';


export interface RangeCalendarProps<T extends DateValue>
extends Omit<AriaRangeCalendarProps<T>, 'visibleDuration' | 'style' | 'className' | 'render' | 'children' | 'styles' | keyof GlobalDOMAttributes>,
StyleProps {
Expand All @@ -48,21 +45,40 @@ export interface RangeCalendarProps<T extends DateValue>

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


const calendarStyles = style({
const calendarStyles = style<{isMultiMonth?: boolean}>({
display: 'flex',
containerType: {
default: 'inline-size',
isMultiMonth: 'unset'
},
flexDirection: 'column',
gap: 24,
width: 'fit'
disableTapHighlight: true,
'--cell-gap': {
type: 'paddingStart',
value: 4
},
'--cell-max-width': {
type: 'width',
value: 32
},
'--cell-responsive-size': {
type: 'width',
value: {
default: '[min(var(--cell-max-width), (100cqw - (var(--cell-gap) * 12)) / 7)]',
isMultiMonth: '--cell-max-width'
}
},
width: {
default: 'calc(7 * var(--cell-max-width) + var(--cell-gap) * 12)',
isMultiMonth: 'fit'
},
maxWidth: {
default: 'full',
isMultiMonth: 'unset'
}
}, getAllowedOverrides());

const headerStyles = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: 'full'
});

/**
* RangeCalendars display a grid of days in one or more months and allow users to select a contiguous range of dates.
*/
Expand All @@ -78,13 +94,14 @@ export const RangeCalendar = /*#__PURE__*/ (forwardRef as forwardRefType)(functi
} = props;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');

let isMultiMonth = visibleMonths > 1;
return (
<AriaRangeCalendar
{...otherProps}
ref={ref}
visibleDuration={{months: visibleMonths}}
style={UNSAFE_style}
className={(UNSAFE_className || '') + calendarStyles(null, styles)}>
style={{...UNSAFE_style, '--num-calendars': visibleMonths} as CSSProperties}
className={(UNSAFE_className || '') + calendarStyles({isMultiMonth}, styles)}>
{({isInvalid, isDisabled}) => {
return (
<>
Expand All @@ -93,22 +110,17 @@ export const RangeCalendar = /*#__PURE__*/ (forwardRef as forwardRefType)(functi
[HeaderContext, null],
[HeadingContext, null]
]}>
<Header styles={headerStyles}>
<CalendarButton slot="previous"><ChevronLeftIcon /></CalendarButton>
<CalendarHeading />
<CalendarButton slot="next"><ChevronRightIcon /></CalendarButton>
</Header>
<CalendarHeader />
</Provider>
<div
className={style({
display: 'flex',
flexDirection: 'row',
gap: 24,
width: 'full',
alignItems: 'start'
})}>
{Array.from({length: visibleMonths}).map((_, i) => (
<CalendarGrid months={i} key={i} />
<CalendarGrid key={i} months={i} />
))}
</div>
{isInvalid && (
Expand Down
Loading