@lotics/ui 13.7.1 → 13.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/catalog.md +6 -2
- package/docs/data_entry.md +3 -2
- package/package.json +1 -1
- package/src/date_calendar.tsx +4 -6
- package/src/date_field.tsx +5 -2
- package/src/date_filter.tsx +7 -5
- package/src/date_picker.tsx +4 -2
- package/src/date_range_filter_field.tsx +6 -5
- package/src/date_segments.test.ts +36 -0
- package/src/inline_date_picker.tsx +9 -5
- package/src/locale.tsx +33 -1
- package/src/vite.mjs +6 -0
package/docs/catalog.md
CHANGED
|
@@ -295,8 +295,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
295
295
|
brand accent (OKLCH blue by default) that chart fills, hero CTAs, and focus rings read.
|
|
296
296
|
- **`locale`** — `LoticsLocaleProvider` / `useLoticsLocale` + the shipped `en` (default) and
|
|
297
297
|
`vi` packs. Set the language ONCE at the root; wired components resolve **prop → provider
|
|
298
|
-
locale → English default**.
|
|
299
|
-
|
|
298
|
+
locale → English default**. Each pack also carries a **`bcp47`** tag (`en`→`en-US`,
|
|
299
|
+
`vi`→`vi-VN`); date/time controls read it via **`useLocaleTag`** (`resolveLocaleTag` is its
|
|
300
|
+
pure core) so the segmented field's part ORDER (dd/MM vs MM/dd), the picker calendar's
|
|
301
|
+
weekday/month names, and `formatDate` display all follow the provider with no per-instance
|
|
302
|
+
`locale` prop — an explicit `locale` still overrides. **Limitation:** the calendar/gantt
|
|
303
|
+
views and the comment labels are not yet provider-wired — pass their `labels` props directly.
|
|
300
304
|
- **`colors`** — the palette + `withAlpha` · `solid` · `tint` · `ramp` · `ColorName` ·
|
|
301
305
|
`isColorName` · `asColorName` (coerce a stored option/status token to a `ColorName`,
|
|
302
306
|
neutral fallback).
|
package/docs/data_entry.md
CHANGED
|
@@ -65,8 +65,9 @@ restore is gated on modality via `shouldRestoreFocusOnClose`).
|
|
|
65
65
|
|
|
66
66
|
**Typed dates.** `InlineDatePicker`'s keyboard mode is an internal segmented date field
|
|
67
67
|
(`DateField` — not exported; reached only through `InlineDatePicker`): type
|
|
68
|
-
the date in the locale's own field order
|
|
69
|
-
`
|
|
68
|
+
the date in the locale's own field order — dd/MM/yyyy under the `vi` provider, MM/dd/yyyy
|
|
69
|
+
under `en`, derived from the active `LoticsLocaleProvider` (an explicit `locale` prop still
|
|
70
|
+
overrides) — digits auto-advance, and a typed separator (`/` `.` `-`) advances a single-digit
|
|
70
71
|
day/month; Enter or blur commits, Escape reverts, Alt+ArrowDown floats the calendar. The
|
|
71
72
|
calendar popover stays the pointer path (click the resting value, as ever) — it is no longer
|
|
72
73
|
the only path. A PARTIAL entry never commits and never clears the stored value: the field
|
package/package.json
CHANGED
package/src/date_calendar.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { Picker, PickerOption } from "./picker";
|
|
|
7
7
|
import { IconButton } from "./icon_button";
|
|
8
8
|
import { FOCUS_RING } from "./control_surface";
|
|
9
9
|
import { useFocusRing } from "./use_focus_ring";
|
|
10
|
-
import { useLoticsLocale } from "./locale";
|
|
10
|
+
import { useLoticsLocale, useLocaleTag } from "./locale";
|
|
11
11
|
|
|
12
12
|
/** Accessible names for the calendar's month-navigation arrows. Backs the
|
|
13
13
|
* `calendar` locale slice. (The month/weekday NAMES come from `Intl` + the
|
|
@@ -76,7 +76,8 @@ export interface CalendarRef {
|
|
|
76
76
|
interface CalendarBaseProps {
|
|
77
77
|
/** First day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday). Default: 1 (Monday) */
|
|
78
78
|
firstDayOfWeek?: DayOfWeek;
|
|
79
|
-
/** BCP-47 locale for weekday/month names.
|
|
79
|
+
/** BCP-47 locale for weekday/month names. Defaults to the active
|
|
80
|
+
* `LoticsLocaleProvider` locale. */
|
|
80
81
|
locale?: string;
|
|
81
82
|
/** Ref to access calendar methods like navigateToMonth */
|
|
82
83
|
ref?: React.Ref<CalendarRef>;
|
|
@@ -130,9 +131,6 @@ interface CalendarMonthProps {
|
|
|
130
131
|
// Helper Functions
|
|
131
132
|
// =============================================================================
|
|
132
133
|
|
|
133
|
-
/** BCP-47 locale used when a caller does not supply one. */
|
|
134
|
-
const DEFAULT_LOCALE = "en-US";
|
|
135
|
-
|
|
136
134
|
interface LocalizedNames {
|
|
137
135
|
/** Weekday short names, index 0 = Sunday. */
|
|
138
136
|
weekdays: string[];
|
|
@@ -425,7 +423,7 @@ export function Calendar<TMode extends "single" | "range">(
|
|
|
425
423
|
): React.ReactElement {
|
|
426
424
|
const screenSize = useScreenSize();
|
|
427
425
|
const firstDayOfWeek = props.firstDayOfWeek ?? 1; // Default to Monday
|
|
428
|
-
const locale = props.locale
|
|
426
|
+
const locale = useLocaleTag(props.locale);
|
|
429
427
|
|
|
430
428
|
if (props.mode === "single") {
|
|
431
429
|
return (
|
package/src/date_field.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { useHover } from "./use_hover";
|
|
|
6
6
|
import { Text } from "./text";
|
|
7
7
|
import { DateSegments } from "./date_segments_field";
|
|
8
8
|
import { SegmentLabels, dateSegmentsConfig } from "./date_segments";
|
|
9
|
+
import { useLocaleTag } from "./locale";
|
|
9
10
|
|
|
10
11
|
export interface DateFieldProps {
|
|
11
12
|
/** Canonical ISO value parts: one (single) or two (range, "start"/"end"). */
|
|
@@ -13,7 +14,8 @@ export interface DateFieldProps {
|
|
|
13
14
|
onPartChange: (index: number, value: string) => void;
|
|
14
15
|
hasTime: boolean;
|
|
15
16
|
segmentLabels: SegmentLabels;
|
|
16
|
-
/** BCP-47 locale driving segment order + 12/24h. Defaults to
|
|
17
|
+
/** BCP-47 locale driving segment order + 12/24h. Defaults to the active
|
|
18
|
+
* `LoticsLocaleProvider` locale (`vi` → dd/MM/yyyy, `en` → MM/dd/yyyy). */
|
|
17
19
|
locale?: string;
|
|
18
20
|
disabled?: boolean;
|
|
19
21
|
/** Shown when every part is empty and the field is unfocused. */
|
|
@@ -111,7 +113,8 @@ export function DateField(props: DateFieldProps) {
|
|
|
111
113
|
[onIncompleteChange],
|
|
112
114
|
);
|
|
113
115
|
|
|
114
|
-
const
|
|
116
|
+
const localeTag = useLocaleTag(locale);
|
|
117
|
+
const config = useMemo(() => dateSegmentsConfig(localeTag, hasTime), [localeTag, hasTime]);
|
|
115
118
|
|
|
116
119
|
const isEmpty = parts.every((part) => !part);
|
|
117
120
|
const showPlaceholder = !!placeholder && isEmpty && !focused;
|
package/src/date_filter.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { useScreenSize } from "./use_screen_size";
|
|
|
11
11
|
import { SegmentLabels } from "./date_segments";
|
|
12
12
|
import { PresetId, PRESET_IDS, getPresetValue } from "./date_filter_presets";
|
|
13
13
|
import { formatDate } from "./format_date";
|
|
14
|
-
import { useLoticsLocale } from "./locale";
|
|
14
|
+
import { useLoticsLocale, useLocaleTag } from "./locale";
|
|
15
15
|
|
|
16
16
|
type SelectionMode = "single" | "range";
|
|
17
17
|
|
|
@@ -50,7 +50,8 @@ export interface DateFilterProps {
|
|
|
50
50
|
includeTime?: boolean;
|
|
51
51
|
/** Translated labels. Defaults to English. */
|
|
52
52
|
labels?: Partial<DateFilterLabels>;
|
|
53
|
-
/** BCP-47 locale for the calendar and date display. Defaults to
|
|
53
|
+
/** BCP-47 locale for the calendar and date display. Defaults to the active
|
|
54
|
+
* `LoticsLocaleProvider` locale. */
|
|
54
55
|
locale?: string;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -88,6 +89,7 @@ function formatDateDisplay(date: Date | null, locale: string | undefined): strin
|
|
|
88
89
|
|
|
89
90
|
export function DateFilter(props: DateFilterProps) {
|
|
90
91
|
const { value, onValueChange, includeTime = false, locale } = props;
|
|
92
|
+
const localeTag = useLocaleTag(locale);
|
|
91
93
|
const loc = useLoticsLocale().dateRange;
|
|
92
94
|
const labels = useMemo<DateFilterLabels>(
|
|
93
95
|
() => ({ ...loc, ...props.labels }),
|
|
@@ -238,7 +240,7 @@ export function DateFilter(props: DateFilterProps) {
|
|
|
238
240
|
mode="range"
|
|
239
241
|
value={calendarValue}
|
|
240
242
|
onValueChange={handleCalendarChange}
|
|
241
|
-
locale={
|
|
243
|
+
locale={localeTag}
|
|
242
244
|
/>
|
|
243
245
|
|
|
244
246
|
{selectionMode === "range" ? (
|
|
@@ -249,7 +251,7 @@ export function DateFilter(props: DateFilterProps) {
|
|
|
249
251
|
{labels.from}
|
|
250
252
|
</Text>
|
|
251
253
|
<Text size="sm" weight="medium">
|
|
252
|
-
{formatDateDisplay(value.start.date,
|
|
254
|
+
{formatDateDisplay(value.start.date, localeTag) || labels.selectDate}
|
|
253
255
|
</Text>
|
|
254
256
|
{includeTime && (
|
|
255
257
|
<View style={{ marginTop: 4 }}>
|
|
@@ -269,7 +271,7 @@ export function DateFilter(props: DateFilterProps) {
|
|
|
269
271
|
{labels.to}
|
|
270
272
|
</Text>
|
|
271
273
|
<Text size="sm" weight="medium">
|
|
272
|
-
{formatDateDisplay(value.end.date,
|
|
274
|
+
{formatDateDisplay(value.end.date, localeTag) || labels.selectDate}
|
|
273
275
|
</Text>
|
|
274
276
|
{includeTime && value.end.date && (
|
|
275
277
|
<View style={{ marginTop: 4 }}>
|
package/src/date_picker.tsx
CHANGED
|
@@ -74,7 +74,8 @@ export interface DatePickerProps {
|
|
|
74
74
|
* omitted resolves from the `datePicker` locale slice — English by default, or
|
|
75
75
|
* the active `LoticsLocaleProvider` pack. */
|
|
76
76
|
labels?: Partial<DatePickerLabels>;
|
|
77
|
-
/** BCP-47 locale for the calendar's weekday/month names.
|
|
77
|
+
/** BCP-47 locale for the segment order + the calendar's weekday/month names.
|
|
78
|
+
* Defaults to the active `LoticsLocaleProvider` locale. */
|
|
78
79
|
locale?: string;
|
|
79
80
|
/** Shown in the field when nothing is selected yet. */
|
|
80
81
|
placeholder?: string;
|
|
@@ -94,7 +95,8 @@ export interface DatePickerPanelProps {
|
|
|
94
95
|
/** Single `date` formats only: time is optional and driven by the value's shape. */
|
|
95
96
|
optionalTime?: boolean;
|
|
96
97
|
labels?: Partial<DatePickerLabels>;
|
|
97
|
-
/** BCP-47 locale for the calendar's weekday/month names.
|
|
98
|
+
/** BCP-47 locale for the segment order + the calendar's weekday/month names.
|
|
99
|
+
* Defaults to the active `LoticsLocaleProvider` locale. */
|
|
98
100
|
locale?: string;
|
|
99
101
|
/** Called when a selection completes and the surrounding surface should dismiss. */
|
|
100
102
|
onRequestClose?: () => void;
|
|
@@ -9,7 +9,7 @@ import { FocusRingPressable } from "./focus_ring_pressable";
|
|
|
9
9
|
import { Popover, PopoverTrigger, PopoverContent, PopoverFooter } from "./popover";
|
|
10
10
|
import { DateFilter, DateFilterValue, DateFilterLabels } from "./date_filter";
|
|
11
11
|
import { formatDate } from "./format_date";
|
|
12
|
-
import { useLoticsLocale } from "./locale";
|
|
12
|
+
import { useLoticsLocale, useLocaleTag } from "./locale";
|
|
13
13
|
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// DateRangeFilterField — the common filter composition over DateFilter:
|
|
@@ -35,8 +35,8 @@ export interface DateRangeFilterFieldProps {
|
|
|
35
35
|
includeTime?: boolean;
|
|
36
36
|
/** Translated labels (presets + footer + placeholder). Defaults to English. */
|
|
37
37
|
labels?: Partial<DateRangeFilterFieldLabels>;
|
|
38
|
-
/** BCP-47 locale for the calendar + trigger date display.
|
|
39
|
-
*
|
|
38
|
+
/** BCP-47 locale for the calendar + trigger date display. Defaults to the active
|
|
39
|
+
* `LoticsLocaleProvider` locale. */
|
|
40
40
|
locale?: string;
|
|
41
41
|
testID?: string;
|
|
42
42
|
}
|
|
@@ -110,12 +110,13 @@ function formatTrigger(value: DateFilterValue, includeTime: boolean, locale: str
|
|
|
110
110
|
|
|
111
111
|
export function DateRangeFilterField(props: DateRangeFilterFieldProps) {
|
|
112
112
|
const { value, onValueChange, includeTime, locale, testID } = props;
|
|
113
|
+
const localeTag = useLocaleTag(locale);
|
|
113
114
|
const loc = useLoticsLocale().dateRange;
|
|
114
115
|
const labels = useMemo(() => ({ ...loc, ...props.labels }), [loc, props.labels]);
|
|
115
116
|
const [open, setOpen] = useState(false);
|
|
116
117
|
|
|
117
118
|
const hasValue = Boolean(value.start.date || value.end.date);
|
|
118
|
-
const display = formatTrigger(value, Boolean(includeTime),
|
|
119
|
+
const display = formatTrigger(value, Boolean(includeTime), localeTag) || labels.placeholder;
|
|
119
120
|
|
|
120
121
|
return (
|
|
121
122
|
<Popover open={open} onOpenChange={setOpen} side="bottom" align="start">
|
|
@@ -150,7 +151,7 @@ export function DateRangeFilterField(props: DateRangeFilterFieldProps) {
|
|
|
150
151
|
onValueChange={onValueChange}
|
|
151
152
|
includeTime={includeTime}
|
|
152
153
|
labels={props.labels}
|
|
153
|
-
locale={
|
|
154
|
+
locale={localeTag}
|
|
154
155
|
/>
|
|
155
156
|
<PopoverFooter align="space-between">
|
|
156
157
|
{hasValue ? <Button title={labels.clear} onPress={() => onValueChange(EMPTY_VALUE)} /> : <View />}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveLocaleTag, en as enLocale, vi as viLocale } from "./locale";
|
|
2
3
|
import {
|
|
3
4
|
getDateLayout,
|
|
4
5
|
getTimeLayout,
|
|
@@ -241,6 +242,41 @@ describe("placeholderFor", () => {
|
|
|
241
242
|
});
|
|
242
243
|
});
|
|
243
244
|
|
|
245
|
+
describe("locale-driven default segment order (LoticsLocaleProvider)", () => {
|
|
246
|
+
// The provider pack carries a BCP-47 tag; a wired date field defaults its
|
|
247
|
+
// segment order to that tag when no explicit `locale` prop is given. These
|
|
248
|
+
// assert the end-to-end contract — provider pack → rendered part ORDER —
|
|
249
|
+
// through the same resolveLocaleTag + getDateLayout the components compose.
|
|
250
|
+
// Before this, the field hardcoded "en-US", so a vi-wrapped app showed
|
|
251
|
+
// MM/DD/YYYY unless every call site threaded a `locale` prop.
|
|
252
|
+
it("a vi provider yields dd/MM/yyyy by default (no explicit locale)", () => {
|
|
253
|
+
const tag = resolveLocaleTag(viLocale);
|
|
254
|
+
expect(tag).toBe("vi-VN");
|
|
255
|
+
expect(fieldOrder(getDateLayout(tag, false))).toEqual(["day", "month", "year"]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("an en provider yields MM/dd/yyyy by default", () => {
|
|
259
|
+
const tag = resolveLocaleTag(enLocale);
|
|
260
|
+
expect(tag).toBe("en-US");
|
|
261
|
+
expect(fieldOrder(getDateLayout(tag, false))).toEqual(["month", "day", "year"]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("an explicit locale prop overrides the provider pack, both directions", () => {
|
|
265
|
+
// Under a vi provider, an author passing en-US still gets MM/dd/yyyy…
|
|
266
|
+
expect(fieldOrder(getDateLayout(resolveLocaleTag(viLocale, "en-US"), false))).toEqual([
|
|
267
|
+
"month",
|
|
268
|
+
"day",
|
|
269
|
+
"year",
|
|
270
|
+
]);
|
|
271
|
+
// …and an en provider with an explicit vi-VN gets dd/MM/yyyy.
|
|
272
|
+
expect(fieldOrder(getDateLayout(resolveLocaleTag(enLocale, "vi-VN"), false))).toEqual([
|
|
273
|
+
"day",
|
|
274
|
+
"month",
|
|
275
|
+
"year",
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
244
280
|
describe("getTimeLayout", () => {
|
|
245
281
|
it("is hour/minute(+AM/PM) only, locale-driven", () => {
|
|
246
282
|
expect(fieldOrder(getTimeLayout("en-US"))).toEqual(["hour", "minute", "dayPeriod"]);
|
|
@@ -11,7 +11,7 @@ import { formatDate } from "./format_date";
|
|
|
11
11
|
import { ActivityIndicator } from "./activity_indicator";
|
|
12
12
|
import { type InlineEditVariant, InlineEditView } from "./inline_edit";
|
|
13
13
|
import { type TextColor } from "./text_utils";
|
|
14
|
-
import { useLoticsLocale } from "./locale";
|
|
14
|
+
import { useLoticsLocale, useLocaleTag } from "./locale";
|
|
15
15
|
import { getInteractionModality } from "./interaction_modality";
|
|
16
16
|
import { shouldOpenOnFocus, shouldRestoreFocusOnClose } from "./inline_focus";
|
|
17
17
|
|
|
@@ -31,7 +31,8 @@ export interface InlineDatePickerProps {
|
|
|
31
31
|
* until a time is added). Ignored when `format` is "datetime" (time always on). */
|
|
32
32
|
optionalTime?: boolean;
|
|
33
33
|
placeholder?: string;
|
|
34
|
-
/** BCP-47 locale for the calendar, the displayed date, and the typed segment
|
|
34
|
+
/** BCP-47 locale for the calendar, the displayed date, and the typed segment
|
|
35
|
+
* order. Defaults to the active `LoticsLocaleProvider` locale. */
|
|
35
36
|
locale?: string;
|
|
36
37
|
disabled?: boolean;
|
|
37
38
|
accessibilityLabel?: string;
|
|
@@ -62,6 +63,9 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
|
|
|
62
63
|
const locales = useLoticsLocale();
|
|
63
64
|
const inlineLabels = locales.inline;
|
|
64
65
|
const dateLabels = locales.datePicker;
|
|
66
|
+
// The calendar, the resting display, and the typed segments must all agree —
|
|
67
|
+
// resolve one tag (explicit prop, else the provider) and thread it to all three.
|
|
68
|
+
const localeTag = useLocaleTag(locale);
|
|
65
69
|
|
|
66
70
|
const [editing, setEditing] = useState(false);
|
|
67
71
|
const [open, setOpen] = useState(false);
|
|
@@ -224,7 +228,7 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
|
|
|
224
228
|
// resting view follows the stored value, the segments follow the session draft
|
|
225
229
|
// (the panel's "Add time" grows the segments live).
|
|
226
230
|
const showTime = optionalTime ? isoHasTime(value) : format === "datetime";
|
|
227
|
-
const display = formatDate(value, { time: showTime, locale, emptyLabel: "" });
|
|
231
|
+
const display = formatDate(value, { time: showTime, locale: localeTag, emptyLabel: "" });
|
|
228
232
|
const fieldHasTime = optionalTime ? isoHasTime(draft) : format === "datetime";
|
|
229
233
|
|
|
230
234
|
const trailing = saving ? (
|
|
@@ -248,7 +252,7 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
|
|
|
248
252
|
onPartChange={(_, next) => updateDraft(next)}
|
|
249
253
|
hasTime={fieldHasTime}
|
|
250
254
|
segmentLabels={dateLabels}
|
|
251
|
-
locale={
|
|
255
|
+
locale={localeTag}
|
|
252
256
|
disabled={disabled}
|
|
253
257
|
placeholder={placeholder}
|
|
254
258
|
partLabels={accessibilityLabel ? [accessibilityLabel] : undefined}
|
|
@@ -284,7 +288,7 @@ export function InlineDatePicker(props: InlineDatePickerProps) {
|
|
|
284
288
|
onValueChange={updateDraft}
|
|
285
289
|
format={format}
|
|
286
290
|
optionalTime={optionalTime}
|
|
287
|
-
locale={
|
|
291
|
+
locale={localeTag}
|
|
288
292
|
// Commit on panel close. A single-date pick and the "Today" button
|
|
289
293
|
// auto-close through here — a bare setOpen(false) would be a
|
|
290
294
|
// controlled close the Popover never reports, silently dropping it.
|
package/src/locale.tsx
CHANGED
|
@@ -20,9 +20,18 @@ import { type FindingLabels } from "./finding";
|
|
|
20
20
|
*
|
|
21
21
|
* Resolution order in every wired component: an explicit per-instance prop wins,
|
|
22
22
|
* else this locale (from `LoticsLocaleProvider`, default `en`), else nothing —
|
|
23
|
-
* the locale is always complete, so there is no third fallback to forget.
|
|
23
|
+
* the locale is always complete, so there is no third fallback to forget. The
|
|
24
|
+
* same order governs the {@link LoticsLocale.bcp47} tag via {@link useLocaleTag}.
|
|
24
25
|
*/
|
|
25
26
|
export interface LoticsLocale {
|
|
27
|
+
/** The BCP-47 language tag this pack renders in (`en` → `"en-US"`, `vi` →
|
|
28
|
+
* `"vi-VN"`). This is what makes a date control follow the active locale
|
|
29
|
+
* WITHOUT the author threading a `locale` prop: it drives every `Intl`-derived
|
|
30
|
+
* rendering the kit does — the segmented date field's part ORDER (dd/MM/yyyy
|
|
31
|
+
* vs MM/dd/yyyy) and 12/24h, the calendar's weekday/month names, and
|
|
32
|
+
* `formatDate` display. A per-instance `locale` prop still overrides it
|
|
33
|
+
* (resolved through {@link useLocaleTag} / {@link resolveLocaleTag}). */
|
|
34
|
+
bcp47: string;
|
|
26
35
|
/** `Pagination` range + page summaries and the prev/next tooltips. */
|
|
27
36
|
pagination: Required<PaginationLabels>;
|
|
28
37
|
/** `SortHeader` a11y prefix + asc/desc suffixes. */
|
|
@@ -127,6 +136,7 @@ export interface LoticsLocale {
|
|
|
127
136
|
/** The platform default — English. Every component's hardcoded default lives
|
|
128
137
|
* HERE now, so the strings are documented in one place and overridable. */
|
|
129
138
|
export const en: LoticsLocale = {
|
|
139
|
+
bcp47: "en-US",
|
|
130
140
|
pagination: {
|
|
131
141
|
rangeWithTotal: (start, end, total) => `${start}–${end} of ${total.toLocaleString()}`,
|
|
132
142
|
range: (start, end) => `${start}–${end}`,
|
|
@@ -221,6 +231,7 @@ export const en: LoticsLocale = {
|
|
|
221
231
|
/** Vietnamese. Maintained once here so every app (and the frontend) shares one
|
|
222
232
|
* canonical translation — no per-app drift ("Chọn tất cả" vs "Chọn hết"). */
|
|
223
233
|
export const vi: LoticsLocale = {
|
|
234
|
+
bcp47: "vi-VN",
|
|
224
235
|
pagination: {
|
|
225
236
|
rangeWithTotal: (start, end, total) => `${start}–${end} trên ${total.toLocaleString("vi-VN")}`,
|
|
226
237
|
range: (start, end) => `${start}–${end}`,
|
|
@@ -344,3 +355,24 @@ export function LoticsLocaleProvider(props: LoticsLocaleProviderProps) {
|
|
|
344
355
|
export function useLoticsLocale(): LoticsLocale {
|
|
345
356
|
return useContext(LoticsLocaleContext);
|
|
346
357
|
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Resolve the effective BCP-47 tag for a locale-aware control: an explicit
|
|
361
|
+
* per-instance `locale` wins, else the pack's own {@link LoticsLocale.bcp47}.
|
|
362
|
+
* The pure core of {@link useLocaleTag}, kept RN-free so the resolution — and
|
|
363
|
+
* the segment ORDER it drives — is unit-testable without rendering a component.
|
|
364
|
+
*/
|
|
365
|
+
export function resolveLocaleTag(locale: LoticsLocale, explicit?: string): string {
|
|
366
|
+
return explicit ?? locale.bcp47;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The BCP-47 tag a date / time control should render in: the explicit `locale`
|
|
371
|
+
* prop when given, else the active `LoticsLocaleProvider` pack's tag. Wired date
|
|
372
|
+
* controls default their `locale` through this, so wrapping an app in the `vi`
|
|
373
|
+
* pack yields dd/MM/yyyy segment order, Vietnamese calendar names, and Vietnamese
|
|
374
|
+
* display with no per-instance prop — while an explicit `locale` still wins.
|
|
375
|
+
*/
|
|
376
|
+
export function useLocaleTag(explicit?: string): string {
|
|
377
|
+
return resolveLocaleTag(useContext(LoticsLocaleContext), explicit);
|
|
378
|
+
}
|
package/src/vite.mjs
CHANGED
|
@@ -64,4 +64,10 @@ export const loticsOptimizeDeps = [
|
|
|
64
64
|
"react-dom",
|
|
65
65
|
"react-dom/client",
|
|
66
66
|
"nullthrows",
|
|
67
|
+
// FilePreview's Excel/CSV engine (@lotics/xlsx) lazy-imports these CJS formula
|
|
68
|
+
// libraries; without pre-bundling, the dev optimizer can't resolve their CJS→ESM
|
|
69
|
+
// default export and the in-app spreadsheet preview fails (dev-server only — the
|
|
70
|
+
// rollup prod build handles the CJS interop).
|
|
71
|
+
"fast-formula-parser",
|
|
72
|
+
"@formulajs/formulajs",
|
|
67
73
|
];
|