@lotics/ui 6.0.0 → 6.1.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/AGENTS.md +11 -3
- package/package.json +2 -1
- package/src/column_filter.tsx +3 -2
- package/src/combobox.tsx +4 -2
- package/src/drawer.tsx +6 -4
- package/src/filter_chip.tsx +3 -1
- package/src/locale.tsx +107 -0
- package/src/option_list.tsx +6 -11
- package/src/pagination.tsx +8 -12
- package/src/sort_header.tsx +4 -6
package/AGENTS.md
CHANGED
|
@@ -12,9 +12,17 @@ typeahead, async search, virtualization, and a11y the primitive already ships.
|
|
|
12
12
|
`PortalHost` at the app root.
|
|
13
13
|
- RN-Web only: `View`/`ScrollView` from `react-native`, the `Text` primitive (no raw
|
|
14
14
|
`div`/`span`, no raw `fontSize`/`fontWeight`); styles are RN objects.
|
|
15
|
-
- i18n: most components are string-free
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
- i18n: most components are string-free. For the rest, set the language ONCE at the root with
|
|
16
|
+
**`LoticsLocaleProvider`** (`@lotics/ui/locale`, the sibling of `LoticsThemeProvider`) — wrap
|
|
17
|
+
`<LoticsLocaleProvider locale={vi}>` and every wired component picks up the pack, no per-instance
|
|
18
|
+
props. Shipped packs: `en` (default) and `vi` (one canonical Vietnamese translation, so apps don't
|
|
19
|
+
redefine — or drift on — strings). Resolution is **prop → provider locale → English default**, so a
|
|
20
|
+
per-instance `labels`/`clearLabel`/`selectAllLabel` still overrides for one-offs, and an un-wrapped
|
|
21
|
+
app stays English. Wired so far: `Pagination`, `SortHeader`/`Table.sortLabels`, `OptionList`
|
|
22
|
+
(+ `Select`/`Combobox` built on it), `FilterChip`, `Drawer`. Adding a string to a wired component's
|
|
23
|
+
`*Labels` forces both packs in `locale.tsx` to fill it (compile error) — that's how the kit avoids a
|
|
24
|
+
silent English leak. Not yet on the provider (pass `labels` for now): `DateRangeFilterField`,
|
|
25
|
+
`RemainderMeter`, `ChangeReview`, calendar/gantt.
|
|
18
26
|
|
|
19
27
|
---
|
|
20
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./tokens": "./src/tokens.ts",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"./status_grid": "./src/status_grid.tsx",
|
|
57
57
|
"./heatmap": "./src/heatmap.tsx",
|
|
58
58
|
"./trend_footer": "./src/trend_footer.tsx",
|
|
59
|
+
"./locale": "./src/locale.tsx",
|
|
59
60
|
"./spacing": "./src/spacing.ts",
|
|
60
61
|
"./theme": "./src/theme.tsx",
|
|
61
62
|
"./progress_bar": "./src/progress_bar.tsx",
|
package/src/column_filter.tsx
CHANGED
|
@@ -92,7 +92,8 @@ export interface ColumnFilterProps {
|
|
|
92
92
|
column: FilterableColumn;
|
|
93
93
|
value: ColumnFilterValue | undefined;
|
|
94
94
|
onChange: (value: ColumnFilterValue | undefined) => void;
|
|
95
|
-
/** Accessible name for the clear (X) control.
|
|
95
|
+
/** Accessible name for the clear (X) control. Omit to use the locale's
|
|
96
|
+
* `filterChip.clear` (English "Clear" by default). */
|
|
96
97
|
clearLabel?: string;
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -105,7 +106,7 @@ export interface ColumnFilterProps {
|
|
|
105
106
|
* `columnFilterToConditions`. Pure UI; no data layer.
|
|
106
107
|
*/
|
|
107
108
|
export function ColumnFilter(props: ColumnFilterProps) {
|
|
108
|
-
const { column, value, onChange, clearLabel
|
|
109
|
+
const { column, value, onChange, clearLabel } = props;
|
|
109
110
|
const active = isColumnFilterActive(value);
|
|
110
111
|
|
|
111
112
|
return (
|
package/src/combobox.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { useCallback, useRef, useState, type ReactNode } from "react";
|
|
10
10
|
import { colors } from "./colors";
|
|
11
11
|
import { FOCUS_RING } from "./control_surface";
|
|
12
|
+
import { useLoticsLocale } from "./locale";
|
|
12
13
|
import { Text } from "./text";
|
|
13
14
|
import { Icon, type IconName } from "./icon";
|
|
14
15
|
import { TextInputField } from "./text_input_field";
|
|
@@ -110,8 +111,6 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
|
|
|
110
111
|
searchDebounceMs = 200,
|
|
111
112
|
icon,
|
|
112
113
|
placeholder,
|
|
113
|
-
recentsLabel = "Recent",
|
|
114
|
-
emptyText = "No results",
|
|
115
114
|
accessibilityLabel = "Results",
|
|
116
115
|
disabled = false,
|
|
117
116
|
clearable = false,
|
|
@@ -121,6 +120,9 @@ export function Combobox<T extends string = string, D = unknown>(props: Combobox
|
|
|
121
120
|
testID,
|
|
122
121
|
style,
|
|
123
122
|
} = props;
|
|
123
|
+
const loc = useLoticsLocale().optionList;
|
|
124
|
+
const emptyText = props.emptyText ?? loc.noResults;
|
|
125
|
+
const recentsLabel = props.recentsLabel ?? loc.recent;
|
|
124
126
|
|
|
125
127
|
const single = value ?? null;
|
|
126
128
|
const reflectedText = single ? (single.label ?? single.value) : "";
|
package/src/drawer.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { colors } from "@lotics/ui/colors";
|
|
|
6
6
|
import { IconButton } from "@lotics/ui/icon_button";
|
|
7
7
|
import { Text } from "@lotics/ui/text";
|
|
8
8
|
import { useOverlayScope } from "@lotics/ui/overlay_scope";
|
|
9
|
+
import { useLoticsLocale } from "@lotics/ui/locale";
|
|
9
10
|
|
|
10
11
|
export interface DrawerProps {
|
|
11
12
|
open: boolean;
|
|
@@ -44,6 +45,7 @@ export interface DrawerProps {
|
|
|
44
45
|
*/
|
|
45
46
|
export function Drawer(props: DrawerProps) {
|
|
46
47
|
const { open, onOpenChange, title, width = 420, onPrev, onNext, position, children, testID } = props;
|
|
48
|
+
const loc = useLoticsLocale().drawer;
|
|
47
49
|
const screenSize = useScreenSize();
|
|
48
50
|
useOverlayScope(open);
|
|
49
51
|
const handleClose = () => onOpenChange(false);
|
|
@@ -79,7 +81,7 @@ export function Drawer(props: DrawerProps) {
|
|
|
79
81
|
<Modal visible={open} onRequestClose={handleClose} transparent>
|
|
80
82
|
<View style={styles.base}>
|
|
81
83
|
{/* Scrim is a sibling of the panel, so tapping the panel never closes. */}
|
|
82
|
-
<Pressable style={styles.scrim} onPress={handleClose} accessibilityLabel=
|
|
84
|
+
<Pressable style={styles.scrim} onPress={handleClose} accessibilityLabel={loc.close} tabIndex={-1} />
|
|
83
85
|
<View style={[styles.panel, { width: screenSize.small ? "100%" : width }]}>
|
|
84
86
|
<PortalHost>
|
|
85
87
|
<View style={styles.header}>
|
|
@@ -94,7 +96,7 @@ export function Drawer(props: DrawerProps) {
|
|
|
94
96
|
<View style={styles.nav}>
|
|
95
97
|
<IconButton
|
|
96
98
|
icon="chevron-left"
|
|
97
|
-
accessibilityLabel=
|
|
99
|
+
accessibilityLabel={loc.previous}
|
|
98
100
|
onPress={onPrev ?? (() => {})}
|
|
99
101
|
disabled={!onPrev}
|
|
100
102
|
/>
|
|
@@ -105,13 +107,13 @@ export function Drawer(props: DrawerProps) {
|
|
|
105
107
|
) : null}
|
|
106
108
|
<IconButton
|
|
107
109
|
icon="chevron-right"
|
|
108
|
-
accessibilityLabel=
|
|
110
|
+
accessibilityLabel={loc.next}
|
|
109
111
|
onPress={onNext ?? (() => {})}
|
|
110
112
|
disabled={!onNext}
|
|
111
113
|
/>
|
|
112
114
|
</View>
|
|
113
115
|
) : null}
|
|
114
|
-
<IconButton icon="x" size="lg" accessibilityLabel=
|
|
116
|
+
<IconButton icon="x" size="lg" accessibilityLabel={loc.close} onPress={handleClose} />
|
|
115
117
|
</View>
|
|
116
118
|
<View testID={testID} style={styles.body}>
|
|
117
119
|
{children}
|
package/src/filter_chip.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { TextLink } from "./text_link";
|
|
|
7
7
|
import { Chip } from "./chip";
|
|
8
8
|
import { Popover, PopoverTrigger, PopoverContent, PopoverFooter } from "./popover";
|
|
9
9
|
import type { PopoverSide, PopoverAlign } from "./popover";
|
|
10
|
+
import { useLoticsLocale } from "./locale";
|
|
10
11
|
|
|
11
12
|
export interface FilterChipProps {
|
|
12
13
|
/** The dimension name — shown alone when inactive ("Owner"), prefixed when
|
|
@@ -69,7 +70,8 @@ export function selectSummary(
|
|
|
69
70
|
* toolbar's `ColumnFilter` is this pill plus its query-condition mapping.
|
|
70
71
|
*/
|
|
71
72
|
export function FilterChip(props: FilterChipProps) {
|
|
72
|
-
const { label, summary, onClear,
|
|
73
|
+
const { label, summary, onClear, children, side = "bottom", align = "start", open, onOpenChange, footer } = props;
|
|
74
|
+
const clearLabel = props.clearLabel ?? useLoticsLocale().filterChip.clear;
|
|
73
75
|
const active = summary != null && (typeof summary !== "string" || summary.length > 0);
|
|
74
76
|
// The clear × / Clear footer only when there's a clearable selection AND no
|
|
75
77
|
// custom footer — a valued, non-clearable pill ("Target: 20") keeps its chevron.
|
package/src/locale.tsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import { type PaginationLabels } from "./pagination";
|
|
3
|
+
import { type SortHeaderLabels } from "./sort_header";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The kit's localizable strings, one slice per string-bearing component. A
|
|
7
|
+
* `LoticsLocale` is plain DATA (like a date-fns locale) — it carries no i18n
|
|
8
|
+
* LOGIC, so the kit stays translation-agnostic while an app supplies the pack
|
|
9
|
+
* once at its root. Each slice REUSES the component's own `*Labels` type, so
|
|
10
|
+
* adding a localizable string to a component (a new key on its `Labels`) forces
|
|
11
|
+
* BOTH packs below to fill it — a missing translation is a compile error here,
|
|
12
|
+
* never a silent English leak at a call site.
|
|
13
|
+
*
|
|
14
|
+
* Resolution order in every wired component: an explicit per-instance prop wins,
|
|
15
|
+
* else this locale (from `LoticsLocaleProvider`, default `en`), else nothing —
|
|
16
|
+
* the locale is always complete, so there is no third fallback to forget.
|
|
17
|
+
*/
|
|
18
|
+
export interface LoticsLocale {
|
|
19
|
+
/** `Pagination` range + page summaries and the prev/next tooltips. */
|
|
20
|
+
pagination: Required<PaginationLabels>;
|
|
21
|
+
/** `SortHeader` a11y prefix + asc/desc suffixes. */
|
|
22
|
+
sortHeader: Required<SortHeaderLabels>;
|
|
23
|
+
/** `OptionList` (and everything built on it — `Select`, `Combobox`, the
|
|
24
|
+
* in-cell editors): the select-all/deselect-all links, the empty state, and
|
|
25
|
+
* the `Combobox` recents header. */
|
|
26
|
+
optionList: { selectAll: string; deselectAll: string; noResults: string; recent: string };
|
|
27
|
+
/** `FilterChip` (and `ColumnFilter`): the generic clear affordance, used when
|
|
28
|
+
* a call site doesn't pass a dimension-specific `clearLabel`. */
|
|
29
|
+
filterChip: { clear: string };
|
|
30
|
+
/** `Drawer`: the record prev/next + close controls (screen-reader names). */
|
|
31
|
+
drawer: { previous: string; next: string; close: string };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The platform default — English. Every component's hardcoded default lives
|
|
35
|
+
* HERE now, so the strings are documented in one place and overridable. */
|
|
36
|
+
export const en: LoticsLocale = {
|
|
37
|
+
pagination: {
|
|
38
|
+
rangeWithTotal: (start, end, total) => `${start}–${end} of ${total.toLocaleString()}`,
|
|
39
|
+
range: (start, end) => `${start}–${end}`,
|
|
40
|
+
pageWithTotal: (page, pageCount) => `Page ${page} of ${pageCount}`,
|
|
41
|
+
page: (page) => `Page ${page}`,
|
|
42
|
+
previous: "Previous",
|
|
43
|
+
next: "Next",
|
|
44
|
+
},
|
|
45
|
+
sortHeader: {
|
|
46
|
+
sortBy: (label) => `Sort by ${label}`,
|
|
47
|
+
ascending: ", ascending",
|
|
48
|
+
descending: ", descending",
|
|
49
|
+
},
|
|
50
|
+
optionList: { selectAll: "Select all", deselectAll: "Deselect all", noResults: "No results", recent: "Recent" },
|
|
51
|
+
filterChip: { clear: "Clear" },
|
|
52
|
+
drawer: { previous: "Previous record", next: "Next record", close: "Close" },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Vietnamese. Maintained once here so every app (and the frontend) shares one
|
|
56
|
+
* canonical translation — no per-app drift ("Chọn tất cả" vs "Chọn hết"). */
|
|
57
|
+
export const vi: LoticsLocale = {
|
|
58
|
+
pagination: {
|
|
59
|
+
rangeWithTotal: (start, end, total) => `${start}–${end} trên ${total.toLocaleString("vi-VN")}`,
|
|
60
|
+
range: (start, end) => `${start}–${end}`,
|
|
61
|
+
pageWithTotal: (page, pageCount) => `Trang ${page} / ${pageCount}`,
|
|
62
|
+
page: (page) => `Trang ${page}`,
|
|
63
|
+
previous: "Trang trước",
|
|
64
|
+
next: "Trang sau",
|
|
65
|
+
},
|
|
66
|
+
sortHeader: {
|
|
67
|
+
sortBy: (label) => `Sắp xếp theo ${label}`,
|
|
68
|
+
ascending: " (tăng dần)",
|
|
69
|
+
descending: " (giảm dần)",
|
|
70
|
+
},
|
|
71
|
+
optionList: { selectAll: "Chọn tất cả", deselectAll: "Bỏ chọn tất cả", noResults: "Không có kết quả", recent: "Gần đây" },
|
|
72
|
+
filterChip: { clear: "Xóa" },
|
|
73
|
+
drawer: { previous: "Bản ghi trước", next: "Bản ghi sau", close: "Đóng" },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const LoticsLocaleContext = createContext<LoticsLocale>(en);
|
|
77
|
+
|
|
78
|
+
interface LoticsLocaleProviderProps {
|
|
79
|
+
/** The locale pack supplying every kit string. Import a shipped pack
|
|
80
|
+
* (`import { vi } from "@lotics/ui/locale"`) or pass your own `LoticsLocale`. */
|
|
81
|
+
locale: LoticsLocale;
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* App-root provider that supplies localized strings to @lotics/ui primitives —
|
|
87
|
+
* the sibling of `LoticsThemeProvider`. Wrap your top-level element once and
|
|
88
|
+
* every wired component picks up the pack; no per-instance label props:
|
|
89
|
+
*
|
|
90
|
+
* // src/main.tsx
|
|
91
|
+
* import { vi } from "@lotics/ui/locale";
|
|
92
|
+
* <LoticsLocaleProvider locale={vi}>
|
|
93
|
+
* <App />
|
|
94
|
+
* </LoticsLocaleProvider>
|
|
95
|
+
*
|
|
96
|
+
* Components read the locale via `useLoticsLocale()`. A per-instance label prop
|
|
97
|
+
* still overrides for one-offs; without a provider the English default applies,
|
|
98
|
+
* so an un-wrapped app behaves exactly as before.
|
|
99
|
+
*/
|
|
100
|
+
export function LoticsLocaleProvider(props: LoticsLocaleProviderProps) {
|
|
101
|
+
return <LoticsLocaleContext.Provider value={props.locale}>{props.children}</LoticsLocaleContext.Provider>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Read the current locale. Wired primitives call this; apps use the provider. */
|
|
105
|
+
export function useLoticsLocale(): LoticsLocale {
|
|
106
|
+
return useContext(LoticsLocaleContext);
|
|
107
|
+
}
|
package/src/option_list.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { ActivityIndicator } from "./activity_indicator";
|
|
|
10
10
|
import { TextInputField } from "./text_input_field";
|
|
11
11
|
import { useScreenSize } from "./use_screen_size";
|
|
12
12
|
import { useOptionList, type UseOptionListParams } from "./use_option_list";
|
|
13
|
+
import { useLoticsLocale } from "./locale";
|
|
13
14
|
import type { PickerOption } from "./picker";
|
|
14
15
|
|
|
15
16
|
export interface OptionListProps<T extends string = string, MULTI extends boolean = false, D = unknown>
|
|
@@ -38,17 +39,11 @@ export interface OptionListProps<T extends string = string, MULTI extends boolea
|
|
|
38
39
|
export function OptionList<T extends string, MULTI extends boolean = false, D = unknown>(
|
|
39
40
|
props: OptionListProps<T, MULTI, D>,
|
|
40
41
|
) {
|
|
41
|
-
const {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
emptyText = "No results",
|
|
47
|
-
accessibilityLabel,
|
|
48
|
-
selectAllLabel = "Select all",
|
|
49
|
-
deselectAllLabel = "Deselect all",
|
|
50
|
-
search,
|
|
51
|
-
} = props;
|
|
42
|
+
const { testID, renderOptionContent, getOptionDescription, loading = false, accessibilityLabel, search } = props;
|
|
43
|
+
const loc = useLoticsLocale().optionList;
|
|
44
|
+
const emptyText = props.emptyText ?? loc.noResults;
|
|
45
|
+
const selectAllLabel = props.selectAllLabel ?? loc.selectAll;
|
|
46
|
+
const deselectAllLabel = props.deselectAllLabel ?? loc.deselectAll;
|
|
52
47
|
const list = useOptionList(props);
|
|
53
48
|
const { small } = useScreenSize();
|
|
54
49
|
|
package/src/pagination.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { IconButton } from "./icon_button";
|
|
4
4
|
import { Text } from "./text";
|
|
5
|
+
import { useLoticsLocale } from "./locale";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Localizable strings for `Pagination`. Every field is optional; an omitted
|
|
@@ -25,12 +26,6 @@ export interface PaginationLabels {
|
|
|
25
26
|
next?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const defaultRangeWithTotal = (start: number, end: number, total: number): string =>
|
|
29
|
-
`${start}–${end} of ${total.toLocaleString()}`;
|
|
30
|
-
const defaultRange = (start: number, end: number): string => `${start}–${end}`;
|
|
31
|
-
const defaultPageWithTotal = (page: number, pageCount: number): string => `Page ${page} of ${pageCount}`;
|
|
32
|
-
const defaultPage = (page: number): string => `Page ${page}`;
|
|
33
|
-
|
|
34
29
|
export interface PaginationProps {
|
|
35
30
|
/** 0-indexed. */
|
|
36
31
|
page: number;
|
|
@@ -59,20 +54,21 @@ export interface PaginationProps {
|
|
|
59
54
|
*/
|
|
60
55
|
export function Pagination(props: PaginationProps): React.ReactNode {
|
|
61
56
|
const { page, pageSize, rowCount, hasMore, total, loading, onPageChange, labels } = props;
|
|
57
|
+
const loc = useLoticsLocale().pagination;
|
|
62
58
|
const start = page * pageSize + 1;
|
|
63
59
|
const end = page * pageSize + rowCount;
|
|
64
60
|
const showRange = !loading && rowCount > 0;
|
|
65
61
|
|
|
66
62
|
const summary = showRange
|
|
67
63
|
? total !== undefined
|
|
68
|
-
? (labels?.rangeWithTotal ??
|
|
69
|
-
: (labels?.range ??
|
|
64
|
+
? (labels?.rangeWithTotal ?? loc.rangeWithTotal)(start, end, total)
|
|
65
|
+
: (labels?.range ?? loc.range)(start, end)
|
|
70
66
|
: "";
|
|
71
67
|
|
|
72
68
|
const pageLabel =
|
|
73
69
|
total !== undefined
|
|
74
|
-
? (labels?.pageWithTotal ??
|
|
75
|
-
: (labels?.page ??
|
|
70
|
+
? (labels?.pageWithTotal ?? loc.pageWithTotal)(page + 1, Math.max(1, Math.ceil(total / pageSize)))
|
|
71
|
+
: (labels?.page ?? loc.page)(page + 1);
|
|
76
72
|
|
|
77
73
|
return (
|
|
78
74
|
<View style={styles.container}>
|
|
@@ -90,14 +86,14 @@ export function Pagination(props: PaginationProps): React.ReactNode {
|
|
|
90
86
|
color="secondary"
|
|
91
87
|
onPress={() => onPageChange(Math.max(0, page - 1))}
|
|
92
88
|
disabled={page === 0 || !!loading}
|
|
93
|
-
tooltip={labels?.previous ??
|
|
89
|
+
tooltip={labels?.previous ?? loc.previous}
|
|
94
90
|
/>
|
|
95
91
|
<IconButton
|
|
96
92
|
icon="chevron-right"
|
|
97
93
|
color="secondary"
|
|
98
94
|
onPress={() => onPageChange(page + 1)}
|
|
99
95
|
disabled={!hasMore || !!loading}
|
|
100
|
-
tooltip={labels?.next ??
|
|
96
|
+
tooltip={labels?.next ?? loc.next}
|
|
101
97
|
/>
|
|
102
98
|
</View>
|
|
103
99
|
</View>
|
package/src/sort_header.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Text } from "./text";
|
|
|
3
3
|
import { Icon } from "./icon";
|
|
4
4
|
import { colors } from "./colors";
|
|
5
5
|
import { PressableHighlight } from "./pressable_highlight";
|
|
6
|
+
import { useLoticsLocale } from "./locale";
|
|
6
7
|
|
|
7
8
|
export type SortDir = "asc" | "desc";
|
|
8
9
|
export interface SortState {
|
|
@@ -74,14 +75,11 @@ export interface SortHeaderProps {
|
|
|
74
75
|
*/
|
|
75
76
|
export function SortHeader(props: SortHeaderProps) {
|
|
76
77
|
const { label, sortKey, sort, onSort, align = "left", style, labels } = props;
|
|
78
|
+
const loc = useLoticsLocale().sortHeader;
|
|
77
79
|
const active = sort?.key === sortKey;
|
|
78
80
|
const arrow = active ? (sort.dir === "asc" ? "chevron-up" : "chevron-down") : undefined;
|
|
79
|
-
const dirText = active
|
|
80
|
-
|
|
81
|
-
? (labels?.ascending ?? ", ascending")
|
|
82
|
-
: (labels?.descending ?? ", descending")
|
|
83
|
-
: "";
|
|
84
|
-
const sortByLabel = (labels?.sortBy ?? ((l: string) => `Sort by ${l}`))(label);
|
|
81
|
+
const dirText = active ? (sort.dir === "asc" ? (labels?.ascending ?? loc.ascending) : (labels?.descending ?? loc.descending)) : "";
|
|
82
|
+
const sortByLabel = (labels?.sortBy ?? loc.sortBy)(label);
|
|
85
83
|
|
|
86
84
|
return (
|
|
87
85
|
<PressableHighlight
|