Skip to content

Commit acb7352

Browse files
sookmaxLFDanLusnowystinger
authored
Add --trigger-width support for DatePicker & DateRangePicker (#5895)
* add --trigger-width support for DatePicker & DateRangePicker * fix comment typos * simplify width calculations * Also apply it to the range picker --------- Co-authored-by: Daniel Lu <dl1644@gmail.com> Co-authored-by: Robert Snow <rsnow@adobe.com>
1 parent 8b7fb3e commit acb7352

File tree

4 files changed

+120
-8
lines changed

4 files changed

+120
-8
lines changed

packages/react-aria-components/docs/DatePicker.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -954,11 +954,11 @@ The [Popover](Popover.html) component can be targeted with the `.react-aria-Popo
954954

955955
<StateTable properties={docs.exports.PopoverRenderProps.properties} />
956956

957-
Within a DatePicker, the popover will have the `data-trigger="DatePicker"` attribute, which can be used to define date picker-specific styles.
957+
Within a DatePicker, the popover will have the `data-trigger="DatePicker"` attribute, which can be used to define date picker-specific styles. In addition, the `--trigger-width` CSS custom property will be set on the popover, which you can use to make the popover match the width of the input group.
958958

959959
```css render=false
960960
.react-aria-Popover[data-trigger=DatePicker] {
961-
/* ... */
961+
width: var(--trigger-width);
962962
}
963963
```
964964

packages/react-aria-components/docs/DateRangePicker.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,11 +1043,11 @@ The [Popover](Popover.html) component can be targeted with the `.react-aria-Popo
10431043

10441044
<StateTable properties={docs.exports.PopoverRenderProps.properties} />
10451045

1046-
Within a DateRangePicker, the popover will have the `data-trigger="DateRangePicker"` attribute, which can be used to define date range picker-specific styles.
1046+
Within a DateRangePicker, the popover will have the `data-trigger="DateRangePicker"` attribute, which can be used to define date range picker-specific styles. In addition, the `--trigger-width` CSS custom property will be set on the popover, which you can use to make the popover match the width of the input group.
10471047

10481048
```css render=false
10491049
.react-aria-Popover[data-trigger=DateRangePicker] {
1050-
/* ... */
1050+
width: var(--trigger-width);
10511051
}
10521052
```
10531053

packages/react-aria-components/src/DatePicker.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import {DateFieldContext} from './DateField';
1717
import {DatePickerState, DatePickerStateOptions, DateRangePickerState, DateRangePickerStateOptions, useDatePickerState, useDateRangePickerState} from 'react-stately';
1818
import {DialogContext, OverlayTriggerStateContext} from './Dialog';
1919
import {FieldErrorContext} from './FieldError';
20-
import {filterDOMProps} from '@react-aria/utils';
20+
import {filterDOMProps, useResizeObserver} from '@react-aria/utils';
2121
import {GroupContext} from './Group';
2222
import {LabelContext} from './Label';
2323
import {PopoverContext} from './Popover';
24-
import React, {createContext, ForwardedRef, forwardRef, useRef} from 'react';
24+
import React, {createContext, ForwardedRef, forwardRef, useCallback, useRef, useState} from 'react';
2525
import {TextContext} from './Text';
2626

2727
export interface DatePickerRenderProps {
@@ -95,6 +95,19 @@ function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: Forward
9595
validationBehavior: props.validationBehavior ?? 'native'
9696
}, state, groupRef);
9797

98+
// Allows calendar width to match input group
99+
let [groupWidth, setGroupWidth] = useState<string | null>(null);
100+
let onResize = useCallback(() => {
101+
if (groupRef.current) {
102+
setGroupWidth(groupRef.current.offsetWidth + 'px');
103+
}
104+
}, []);
105+
106+
useResizeObserver({
107+
ref: groupRef,
108+
onResize: onResize
109+
});
110+
98111
let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
99112
let renderProps = useRenderProps({
100113
...props,
@@ -122,7 +135,12 @@ function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: Forward
122135
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
123136
[CalendarContext, calendarProps],
124137
[OverlayTriggerStateContext, state],
125-
[PopoverContext, {trigger: 'DatePicker', triggerRef: groupRef, placement: 'bottom start'}],
138+
[PopoverContext, {
139+
trigger: 'DatePicker',
140+
triggerRef: groupRef,
141+
placement: 'bottom start',
142+
style: {'--trigger-width': groupWidth} as React.CSSProperties
143+
}],
126144
[DialogContext, dialogProps],
127145
[TextContext, {
128146
slots: {
@@ -179,6 +197,19 @@ function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, re
179197
validationBehavior: props.validationBehavior ?? 'native'
180198
}, state, groupRef);
181199

200+
// Allows calendar width to match input group
201+
let [groupWidth, setGroupWidth] = useState<string | null>(null);
202+
let onResize = useCallback(() => {
203+
if (groupRef.current) {
204+
setGroupWidth(groupRef.current.offsetWidth + 'px');
205+
}
206+
}, []);
207+
208+
useResizeObserver({
209+
ref: groupRef,
210+
onResize: onResize
211+
});
212+
182213
let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
183214
let renderProps = useRenderProps({
184215
...props,
@@ -205,7 +236,12 @@ function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, re
205236
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
206237
[RangeCalendarContext, calendarProps],
207238
[OverlayTriggerStateContext, state],
208-
[PopoverContext, {trigger: 'DateRangePicker', triggerRef: groupRef, placement: 'bottom start'}],
239+
[PopoverContext, {
240+
trigger: 'DateRangePicker',
241+
triggerRef: groupRef,
242+
placement: 'bottom start',
243+
style: {'--trigger-width': groupWidth} as React.CSSProperties
244+
}],
209245
[DialogContext, dialogProps],
210246
[DateFieldContext, {
211247
slots: {

packages/react-aria-components/stories/DatePicker.stories.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,41 @@ export const DatePickerExample = () => (
5252
</DatePicker>
5353
);
5454

55+
export const DatePickerTriggerWidthExample = () => (
56+
<DatePicker data-testid="date-picker-example">
57+
<Label style={{display: 'block'}}>Date</Label>
58+
<Group style={{display: 'inline-flex', width: 300}}>
59+
<DateInput className={styles.field} style={{flex: 1}}>
60+
{segment => <DateSegment segment={segment} className={clsx(styles.segment, {[styles.placeholder]: segment.isPlaceholder})} />}
61+
</DateInput>
62+
<Button>🗓</Button>
63+
</Group>
64+
<Popover
65+
placement="bottom start"
66+
style={{
67+
background: 'Canvas',
68+
color: 'CanvasText',
69+
border: '1px solid gray',
70+
padding: 20,
71+
boxSizing: 'border-box',
72+
width: 'var(--trigger-width)'
73+
}}>
74+
<Dialog>
75+
<Calendar>
76+
<div style={{display: 'flex', alignItems: 'center'}}>
77+
<Button slot="previous">&lt;</Button>
78+
<Heading style={{flex: 1, textAlign: 'center'}} />
79+
<Button slot="next">&gt;</Button>
80+
</div>
81+
<CalendarGrid style={{width: '100%'}}>
82+
{date => <CalendarCell date={date} style={({isSelected, isOutsideMonth}) => ({display: isOutsideMonth ? 'none' : '', textAlign: 'center', cursor: 'default', background: isSelected ? 'blue' : ''})} />}
83+
</CalendarGrid>
84+
</Calendar>
85+
</Dialog>
86+
</Popover>
87+
</DatePicker>
88+
);
89+
5590
export const DateRangePickerExample = () => (
5691
<DateRangePicker data-testid="date-range-picker-example">
5792
<Label style={{display: 'block'}}>Date</Label>
@@ -90,3 +125,44 @@ export const DateRangePickerExample = () => (
90125
</Popover>
91126
</DateRangePicker>
92127
);
128+
129+
export const DateRangePickerTriggerWidthExample = () => (
130+
<DateRangePicker data-testid="date-range-picker-example">
131+
<Label style={{display: 'block'}}>Date</Label>
132+
<Group style={{display: 'inline-flex', width: 300}}>
133+
<div className={styles.field} style={{flex: 1}}>
134+
<DateInput data-testid="date-range-picker-date-input" slot="start" style={{display: 'inline-flex'}}>
135+
{segment => <DateSegment segment={segment} className={clsx(styles.segment, {[styles.placeholder]: segment.isPlaceholder})} />}
136+
</DateInput>
137+
<span aria-hidden="true" style={{padding: '0 4px'}}></span>
138+
<DateInput slot="end" style={{display: 'inline-flex'}}>
139+
{segment => <DateSegment segment={segment} className={clsx(styles.segment, {[styles.placeholder]: segment.isPlaceholder})} />}
140+
</DateInput>
141+
</div>
142+
<Button>🗓</Button>
143+
</Group>
144+
<Popover
145+
placement="bottom start"
146+
style={{
147+
background: 'Canvas',
148+
color: 'CanvasText',
149+
border: '1px solid gray',
150+
padding: 20,
151+
boxSizing: 'border-box',
152+
width: 'var(--trigger-width)'
153+
}}>
154+
<Dialog>
155+
<RangeCalendar>
156+
<div style={{display: 'flex', alignItems: 'center'}}>
157+
<Button slot="previous">&lt;</Button>
158+
<Heading style={{flex: 1, textAlign: 'center'}} />
159+
<Button slot="next">&gt;</Button>
160+
</div>
161+
<CalendarGrid style={{width: '100%'}}>
162+
{date => <CalendarCell date={date} style={({isSelected, isOutsideMonth}) => ({display: isOutsideMonth ? 'none' : '', textAlign: 'center', cursor: 'default', background: isSelected ? 'blue' : ''})} />}
163+
</CalendarGrid>
164+
</RangeCalendar>
165+
</Dialog>
166+
</Popover>
167+
</DateRangePicker>
168+
);

0 commit comments

Comments
 (0)