Skip to content

Commit

Permalink
chore: follow-up for rtl date/time format (#7685)
Browse files Browse the repository at this point in the history
* add comments to explain unicode stuff

* add blurb about the css changes in rac docs

* add spacing to showFormatHelpText
  • Loading branch information
yihuiliao authored Jan 31, 2025
1 parent 1ba8f01 commit 41ef71d
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 2 deletions.
5 changes: 5 additions & 0 deletions packages/@react-aria/datepicker/src/useDateSegment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,14 @@ export function useDateSegment(segment: DateSegment, state: DateFieldState, ref:
let dateSegments = ['day', 'month', 'year'];
let segmentStyle : CSSProperties = {caretColor: 'transparent'};
if (direction === 'rtl') {
// While the bidirectional algorithm seems to work properly on inline elements with actual values, it returns different results for placeholder strings.
// To ensure placeholder render in correct format, we apply the CSS equivalent of LRE (left-to-right embedding). See https://www.unicode.org/reports/tr9/#Explicit_Directional_Embeddings.
// However, we apply this to both placeholders and date segments with an actual value because the date segments will shift around when deleting otherwise.
if (dateSegments.includes(segment.type)) {
segmentStyle = {caretColor: 'transparent', direction: 'ltr', unicodeBidi: 'embed'};
} else if (segment.type === 'timeZoneName') {
// This is needed so that the time zone renders on the left side of the time segments (hour:minute).
// Otherwise, it will render on the right side which is incorrect.
segmentStyle = {caretColor: 'transparent', unicodeBidi: 'embed'};
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/datepicker/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useFormatHelpText(props: Pick<SpectrumDatePickerBase<any>, 'desc
return (
formatter.formatToParts(new Date()).map((s, i) => {
if (s.type === 'literal') {
return <span key={i}>{s.value}</span>;
return <span key={i}>{` ${s.value} `}</span>;
}

return <span key={i} style={{unicodeBidi: 'embed', direction: 'ltr'}}>{displayNames.of(s.type)}</span>;
Expand Down
9 changes: 8 additions & 1 deletion packages/@react-stately/datepicker/src/useDateFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,11 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
isEditable
} as DateSegment;

// There is an issue in RTL languages where time fields render (minute:hour) instead of (hour:minute).
// To force an LTR direction on the time field since, we wrap the time segments in LRI (left-to-right) isolate unicode. See https://www.w3.org/International/questions/qa-bidi-unicode-controls.
// These unicode characters will be added to the array of processed segments as literals and will mark the start and end of the embedded direction change.
if (segment.type === 'hour') {
// This marks the start of the embedded direction change.
processedSegments.push({
type: 'literal',
text: '\u2066',
Expand All @@ -440,6 +444,7 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
isEditable: false
});
processedSegments.push(dateSegment);
// This marks the end of the embedded direction change in the case that the granularity it set to "hour".
if (segment.type === granularity) {
processedSegments.push({
type: 'literal',
Expand All @@ -450,8 +455,9 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
isEditable: false
});
}
} else if (timeValue.includes(granularity) && segment.type === granularity) {
} else if (timeValue.includes(segment.type) && segment.type === granularity) {
processedSegments.push(dateSegment);
// This marks the end of the embedded direction change.
processedSegments.push({
type: 'literal',
text: '\u2069',
Expand All @@ -461,6 +467,7 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption
isEditable: false
});
} else {
// We only want to "wrap" the unicode around segments that are hour, minute, or second. If they aren't, just process as normal.
processedSegments.push(dateSegment);
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react-aria-components/docs/DateField.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ If the date field does not have a visible label, an `aria-label` or `aria-labell

Note that most of this anatomy is shared with [TimeField](TimeField.html), so you can reuse many components between them if you have both.

### Internationalization

To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`.

### Concepts

`DateField` makes use of the following concepts:
Expand Down
4 changes: 4 additions & 0 deletions packages/react-aria-components/docs/DatePicker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ If the date picker does not have a visible label, an `aria-label` or `aria-label

Note that most of this anatomy is shared with [DateRangePicker](DateRangePicker.html), so you can reuse many components between them if you have both.

### Internationalization

To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`.

### Concepts

`DatePicker` makes use of the following concepts:
Expand Down
4 changes: 4 additions & 0 deletions packages/react-aria-components/docs/DateRangePicker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ If the date range picker does not have a visible label, an `aria-label` or `aria

Note that most of this anatomy is shared with [DatePicker](DatePicker.html), so you can reuse many components between them if you have both.

### Internationalization

To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`.

### Concepts

`DateRangePicker` makes use of the following concepts:
Expand Down

1 comment on commit 41ef71d

@rspbot
Copy link

@rspbot rspbot commented on 41ef71d Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.