Skip to content

Commit cf89903

Browse files
authored
Removing date segment placeholder default width (#6562)
1 parent 505a6d9 commit cf89903

File tree

13 files changed

+89
-35
lines changed

13 files changed

+89
-35
lines changed

packages/@react-spectrum/datepicker/src/DateField.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {Input} from './Input';
2121
import React, {ReactElement, useRef} from 'react';
2222
import {useDateField} from '@react-aria/datepicker';
2323
import {useDateFieldState} from '@react-stately/datepicker';
24-
import {useFocusManagerRef, useFormatHelpText} from './utils';
24+
import {useFocusManagerRef, useFormatHelpText, useFormattedDateWidth} from './utils';
2525
import {useFormProps} from '@react-spectrum/form';
2626
import {useLocale} from '@react-aria/i18n';
2727
import {useProviderProps} from '@react-spectrum/provider';
@@ -61,6 +61,8 @@ function DateField<T extends DateValue>(props: SpectrumDateFieldProps<T>, ref: F
6161

6262
let validationState = state.validationState || (isInvalid ? 'invalid' : null);
6363

64+
let approximateWidth = useFormattedDateWidth(state) + 'ch';
65+
6466
return (
6567
<Field
6668
{...props}
@@ -82,6 +84,7 @@ function DateField<T extends DateValue>(props: SpectrumDateFieldProps<T>, ref: F
8284
isQuiet={isQuiet}
8385
autoFocus={autoFocus}
8486
validationState={validationState}
87+
minWidth={approximateWidth}
8588
className={classNames(datepickerStyles, 'react-spectrum-DateField')}>
8689
{state.segments.map((segment, i) =>
8790
(<DatePickerSegment

packages/@react-spectrum/datepicker/src/DatePicker.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
3131
import {TimeField} from './TimeField';
3232
import {useDatePicker} from '@react-aria/datepicker';
3333
import {useDatePickerState} from '@react-stately/datepicker';
34-
import {useFocusManagerRef, useFormatHelpText, useVisibleMonths} from './utils';
34+
import {useFocusManagerRef, useFormatHelpText, useFormattedDateWidth, useVisibleMonths} from './utils';
3535
import {useFocusRing} from '@react-aria/focus';
3636
import {useFormProps} from '@react-spectrum/form';
3737
import {useHover} from '@react-aria/interactions';
@@ -112,6 +112,8 @@ function DatePicker<T extends DateValue>(props: SpectrumDatePickerProps<T>, ref:
112112
let visibleMonths = useVisibleMonths(maxVisibleMonths);
113113
let validationState = state.validationState || (isInvalid ? 'invalid' : null);
114114

115+
let approximateWidth = useFormattedDateWidth(state) + 'ch';
116+
115117
return (
116118
<Field
117119
{...props}
@@ -136,7 +138,8 @@ function DatePicker<T extends DateValue>(props: SpectrumDatePickerProps<T>, ref:
136138
validationState={validationState}
137139
className={classNames(styles, 'spectrum-InputGroup-field')}
138140
inputClassName={fieldClassName}
139-
disableFocusRing>
141+
disableFocusRing
142+
minWidth={approximateWidth}>
140143
<DatePickerField
141144
{...fieldProps}
142145
data-testid="date-field"

packages/@react-spectrum/datepicker/src/DatePickerSegment.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function LiteralSegment({segment}: LiteralSegmentProps) {
5252
function EditableSegment({segment, state}: DatePickerSegmentProps) {
5353
let ref = useRef(undefined);
5454
let {segmentProps} = useDateSegment(segment, state, ref);
55+
5556
return (
5657
<div
5758
{...segmentProps}
@@ -60,13 +61,9 @@ function EditableSegment({segment, state}: DatePickerSegmentProps) {
6061
'is-placeholder': segment.isPlaceholder,
6162
'is-read-only': !segment.isEditable
6263
})}
63-
style={{
64-
...segmentProps.style,
65-
minWidth: segment.maxValue != null ? String(segment.maxValue).length + 'ch' : null
66-
}}
64+
style={segmentProps.style}
6765
data-testid={segment.type}>
68-
<span aria-hidden="true" className={classNames(styles, 'react-spectrum-DatePicker-placeholder')}>{segment.placeholder}</span>
69-
{segment.isPlaceholder ? '' : segment.text}
66+
{segment.isPlaceholder ? <span aria-hidden="true" className={classNames(styles, 'react-spectrum-DatePicker-placeholder')}>{segment.placeholder}</span> : segment.text}
7067
</div>
7168
);
7269
}

packages/@react-spectrum/datepicker/src/DateRangePicker.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
3131
import {TimeField} from './TimeField';
3232
import {useDateRangePicker} from '@react-aria/datepicker';
3333
import {useDateRangePickerState} from '@react-stately/datepicker';
34-
import {useFocusManagerRef, useFormatHelpText, useVisibleMonths} from './utils';
34+
import {useFocusManagerRef, useFormatHelpText, useFormattedDateWidth, useVisibleMonths} from './utils';
3535
import {useFocusRing} from '@react-aria/focus';
3636
import {useFormProps} from '@react-spectrum/form';
3737
import {useHover} from '@react-aria/interactions';
@@ -112,6 +112,9 @@ function DateRangePicker<T extends DateValue>(props: SpectrumDateRangePickerProp
112112
let visibleMonths = useVisibleMonths(maxVisibleMonths);
113113
let validationState = state.validationState || (isInvalid ? 'invalid' : null);
114114

115+
// Multiplying by two for the two dates, adding one character for the dash, and then the padding around the dash
116+
let approximateWidth = `calc(${useFormattedDateWidth(state) * 2 + 1}ch + 2 * var(--spectrum-global-dimension-size-100))`;
117+
115118
return (
116119
<Field
117120
{...props}
@@ -136,7 +139,8 @@ function DateRangePicker<T extends DateValue>(props: SpectrumDateRangePickerProp
136139
validationState={validationState}
137140
className={classNames(styles, 'spectrum-InputGroup-field')}
138141
inputClassName={fieldClassName}
139-
disableFocusRing>
142+
disableFocusRing
143+
minWidth={approximateWidth}>
140144
<DatePickerField
141145
{...startFieldProps}
142146
data-testid="start-date"

packages/@react-spectrum/datepicker/src/Input.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,16 @@ function Input(props, ref) {
124124
return (
125125
<div role="presentation" {...mergeProps(fieldProps, focusProps)} className={textfieldClass} style={style}>
126126
<div role="presentation" className={inputClass}>
127-
<div role="presentation" className={classNames(datepickerStyles, 'react-spectrum-Datepicker-inputContents')} ref={mergeRefs(ref, inputRef)}>
128-
{children}
127+
<div
128+
role="presentation"
129+
className={classNames(datepickerStyles, 'react-spectrum-Datepicker-inputContents')}
130+
ref={mergeRefs(ref, inputRef)}>
131+
<div
132+
role="presentation"
133+
className={classNames(datepickerStyles, 'react-spectrum-Datepicker-inputSized')}
134+
style={{minWidth: props.minWidth}}>
135+
{children}
136+
</div>
129137
</div>
130138
</div>
131139
{validationIcon}

packages/@react-spectrum/datepicker/src/TimeField.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {FocusableRef} from '@react-types/shared';
1818
import {Input} from './Input';
1919
import React, {ReactElement, useRef} from 'react';
2020
import {SpectrumTimeFieldProps, TimeValue} from '@react-types/datepicker';
21-
import {useFocusManagerRef} from './utils';
21+
import {useFocusManagerRef, useFormattedDateWidth} from './utils';
2222
import {useFormProps} from '@react-spectrum/form';
2323
import {useLocale} from '@react-aria/i18n';
2424
import {useProviderProps} from '@react-spectrum/provider';
@@ -52,6 +52,8 @@ function TimeField<T extends TimeValue>(props: SpectrumTimeFieldProps<T>, ref: F
5252

5353
let validationState = state.validationState || (isInvalid ? 'invalid' : null);
5454

55+
let approximateWidth = useFormattedDateWidth(state) + 'ch';
56+
5557
return (
5658
<Field
5759
{...props}
@@ -72,6 +74,7 @@ function TimeField<T extends TimeValue>(props: SpectrumTimeFieldProps<T>, ref: F
7274
isQuiet={isQuiet}
7375
autoFocus={autoFocus}
7476
validationState={validationState}
77+
minWidth={approximateWidth}
7578
className={classNames(datepickerStyles, 'react-spectrum-TimeField')}>
7679
{state.segments.map((segment, i) =>
7780
(<DatePickerSegment

packages/@react-spectrum/datepicker/src/styles.css

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@
7676
}
7777
}
7878

79+
.react-spectrum-Datepicker-inputSized {
80+
display: flex;
81+
height: 100%;
82+
align-items: center;
83+
}
84+
7985
.react-spectrum-Datepicker-rangeDash {
8086
&:before {
8187
content: '–';
@@ -111,22 +117,12 @@
111117
}
112118
}
113119

114-
.react-spectrum-DatePicker-placeholder {
115-
display: block;
116-
width: 100%;
117-
text-align: center;
118-
font-style: italic;
119-
visibility: hidden;
120-
height: 0;
121-
pointer-events: none;
122-
}
123-
124120
.react-spectrum-DatePicker-cell.is-placeholder {
125121
color: var(--spectrum-gray-600);
126122

127123
.react-spectrum-DatePicker-placeholder {
128-
visibility: visible;
129-
height: auto;
124+
text-align: center;
125+
pointer-events: none;
130126
}
131127
}
132128

packages/@react-spectrum/datepicker/src/utils.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {createDOMRef} from '@react-spectrum/utils';
1313
import {createFocusManager} from '@react-aria/focus';
1414
import {FocusableRef} from '@react-types/shared';
1515
import {SpectrumDatePickerBase} from '@react-types/datepicker';
16-
import {useDateFormatter} from '@react-aria/i18n';
16+
import {useDateFormatter, useLocale} from '@react-aria/i18n';
1717
import {useDisplayNames} from '@react-aria/datepicker';
1818
import {useImperativeHandle, useMemo, useRef, useState} from 'react';
1919
import {useLayoutEffect} from '@react-aria/utils';
@@ -77,3 +77,16 @@ export function useFocusManagerRef(ref: FocusableRef<HTMLElement>) {
7777
}));
7878
return domRef;
7979
}
80+
81+
export function useFormattedDateWidth(state) {
82+
let locale = useLocale()?.locale;
83+
let currentDate = new Date();
84+
let formatedDate = state.getDateFormatter(locale, {shouldForceLeadingZeros: true}).format(currentDate, locale);
85+
let totalCharacters = formatedDate.length;
86+
87+
// The max of two is for times with only hours.
88+
// As the length of a date grows we need to proportionally increase the width.
89+
// We use the character count with 'ch' units and add extra padding to accomate for
90+
// dates with months and time dashes, which are wider characters.
91+
return (totalCharacters + Math.max(Math.floor(totalCharacters / 5), 2));
92+
}

packages/@react-spectrum/datepicker/test/DatePicker.test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,12 @@ describe('DatePicker', function () {
15431543
await user.keyboard('{Backspace}');
15441544
expect(onChange).toHaveBeenCalledTimes(1);
15451545
expect(onChange).toHaveBeenCalledWith(newValue);
1546-
expect(segment.textContent).not.toBe(textContent);
1546+
if (label === 'AM/PM,') {
1547+
expect(segment).toHaveAttribute('data-placeholder', 'true');
1548+
expect(segment).toHaveAttribute('aria-valuetext', 'Empty');
1549+
} else {
1550+
expect(segment.textContent).not.toBe(textContent);
1551+
}
15471552
unmount();
15481553
}
15491554

packages/@react-stately/datepicker/src/useDateFieldState.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import {Calendar, DateFormatter, getMinimumDayInMonth, getMinimumMonthInYear, GregorianCalendar, toCalendar} from '@internationalized/date';
14-
import {convertValue, createPlaceholderDate, FieldOptions, getFormatOptions, getValidationResult, useDefaultProps} from './utils';
14+
import {convertValue, createPlaceholderDate, FieldOptions, FormatterOptions, getFormatOptions, getValidationResult, useDefaultProps} from './utils';
1515
import {DatePickerProps, DateValue, Granularity} from '@react-types/datepicker';
1616
import {FormValidationState, useFormValidationState} from '@react-stately/form';
1717
import {getPlaceholder} from './placeholders';
@@ -93,7 +93,9 @@ export interface DateFieldState extends FormValidationState {
9393
/** Clears the value of the given segment, reverting it to the placeholder. */
9494
clearSegment(type: SegmentType): void,
9595
/** Formats the current date value using the given options. */
96-
formatValue(fieldOptions: FieldOptions): string
96+
formatValue(fieldOptions: FieldOptions): string,
97+
/** Gets a formatter based on state's props. */
98+
getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter
9799
}
98100

99101
const EDITABLE_SEGMENTS = {
@@ -412,6 +414,11 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
412414
let formatOptions = getFormatOptions(fieldOptions, formatOpts);
413415
let formatter = new DateFormatter(locale, formatOptions);
414416
return formatter.format(dateValue);
417+
},
418+
getDateFormatter(locale, formatOptions: FormatterOptions) {
419+
let newOptions = {...formatOpts, ...formatOptions};
420+
let newFormatOptions = getFormatOptions({}, newOptions);
421+
return new DateFormatter(locale, newFormatOptions);
415422
}
416423
};
417424
}

0 commit comments

Comments
 (0)