@lotics/ui 6.1.0 → 6.2.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 +5 -4
- package/package.json +1 -1
- package/src/change_review.tsx +13 -10
- package/src/confidence.tsx +2 -2
- package/src/date_filter.tsx +4 -21
- package/src/date_range_filter_field.tsx +3 -2
- package/src/locale.tsx +83 -0
- package/src/remainder_meter.tsx +6 -9
package/AGENTS.md
CHANGED
|
@@ -19,10 +19,11 @@ typeahead, async search, virtualization, and a11y the primitive already ships.
|
|
|
19
19
|
redefine — or drift on — strings). Resolution is **prop → provider locale → English default**, so a
|
|
20
20
|
per-instance `labels`/`clearLabel`/`selectAllLabel` still overrides for one-offs, and an un-wrapped
|
|
21
21
|
app stays English. Wired so far: `Pagination`, `SortHeader`/`Table.sortLabels`, `OptionList`
|
|
22
|
-
(+ `Select`/`Combobox` built on it), `FilterChip`, `Drawer
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
(+ `Select`/`Combobox` built on it), `FilterChip`, `Drawer`, `Confidence`, `RemainderMeter`,
|
|
23
|
+
`ChangeReview`/`ChangeReviewActions`, and `DateRangeFilterField`/`DateFilter` (presets + footer +
|
|
24
|
+
time-segment a11y, one `dateRange` slice). Adding a string to a wired component's `*Labels` forces
|
|
25
|
+
both packs in `locale.tsx` to fill it (compile error) — that's how the kit avoids a silent English
|
|
26
|
+
leak. Not yet on the provider (pass `labels` for now): calendar/gantt, the file/preview/comment labels.
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
package/package.json
CHANGED
package/src/change_review.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { reviewCardStyle, reviewResolvedStyle } from "./control_surface";
|
|
|
5
5
|
import { Text } from "./text";
|
|
6
6
|
import { Icon } from "./icon";
|
|
7
7
|
import { Button } from "./button";
|
|
8
|
+
import { useLoticsLocale } from "./locale";
|
|
8
9
|
|
|
9
10
|
export type ChangeReviewItemStatus = "pending" | "accepted" | "rejected";
|
|
10
11
|
|
|
@@ -116,17 +117,18 @@ export function ChangeDiff({ before, after }: { before?: string; after: string }
|
|
|
116
117
|
* `ReviewCard` reviews a SINGLE proposal, this is the BATCH engine.
|
|
117
118
|
*/
|
|
118
119
|
export function ChangeReview<T>(props: ChangeReviewProps<T>) {
|
|
120
|
+
const loc = useLoticsLocale().changeReview;
|
|
119
121
|
const status = props.status ?? "open";
|
|
120
122
|
const perItem = props.onAcceptItem != null || props.onRejectItem != null;
|
|
121
123
|
const total = props.items.length;
|
|
122
124
|
const statusFor = (item: T, i: number) => props.statusOf?.(item, i) ?? "pending";
|
|
123
125
|
const keptCount = props.items.filter((it, i) => statusFor(it, i) === "accepted").length;
|
|
124
|
-
const title = props.title ??
|
|
126
|
+
const title = props.title ?? loc.title;
|
|
125
127
|
const L = {
|
|
126
|
-
undo:
|
|
127
|
-
applied:
|
|
128
|
-
discarded:
|
|
129
|
-
keptCount:
|
|
128
|
+
undo: loc.undo,
|
|
129
|
+
applied: loc.applied,
|
|
130
|
+
discarded: loc.discarded,
|
|
131
|
+
keptCount: loc.keptCount,
|
|
130
132
|
...props.labels,
|
|
131
133
|
};
|
|
132
134
|
|
|
@@ -173,8 +175,8 @@ export function ChangeReview<T>(props: ChangeReviewProps<T>) {
|
|
|
173
175
|
<View key={props.getKey(item, i)} style={styles.itemCard}>
|
|
174
176
|
{props.renderItem(item, i)}
|
|
175
177
|
<View style={styles.itemActions}>
|
|
176
|
-
<Button title={props.rejectLabel ??
|
|
177
|
-
<Button title={props.acceptLabel ??
|
|
178
|
+
<Button title={props.rejectLabel ?? loc.reject} color="muted" onPress={() => props.onRejectItem?.(i)} />
|
|
179
|
+
<Button title={props.acceptLabel ?? loc.accept} color="secondary" onPress={() => props.onAcceptItem?.(i)} />
|
|
178
180
|
</View>
|
|
179
181
|
</View>
|
|
180
182
|
);
|
|
@@ -233,6 +235,7 @@ export function ChangeReview<T>(props: ChangeReviewProps<T>) {
|
|
|
233
235
|
* inline wrapper provides the chrome); self-hides once `status` isn't open.
|
|
234
236
|
*/
|
|
235
237
|
export function ChangeReviewActions<T>(props: ChangeReviewActionsProps<T>) {
|
|
238
|
+
const loc = useLoticsLocale().changeReview;
|
|
236
239
|
if ((props.status ?? "open") !== "open") return null;
|
|
237
240
|
const perItem = props.onAcceptItem != null;
|
|
238
241
|
const statusFor = (item: T, i: number) => props.statusOf?.(item, i) ?? "pending";
|
|
@@ -243,12 +246,12 @@ export function ChangeReviewActions<T>(props: ChangeReviewActionsProps<T>) {
|
|
|
243
246
|
});
|
|
244
247
|
return (
|
|
245
248
|
<View style={styles.actionsRow}>
|
|
246
|
-
{perItem ? <Button title={props.acceptAllLabel ??
|
|
249
|
+
{perItem ? <Button title={props.acceptAllLabel ?? loc.acceptAll} color="muted" onPress={acceptAll} /> : null}
|
|
247
250
|
<View style={{ flex: 1 }} />
|
|
248
|
-
{props.onDiscard ? <Button title={props.discardLabel ??
|
|
251
|
+
{props.onDiscard ? <Button title={props.discardLabel ?? loc.discard} color="muted" onPress={props.onDiscard} /> : null}
|
|
249
252
|
{props.onApply ? (
|
|
250
253
|
<Button
|
|
251
|
-
title={props.applyLabel ?? (perItem ?
|
|
254
|
+
title={props.applyLabel ?? (perItem ? loc.applyKept : loc.apply)}
|
|
252
255
|
color="primary"
|
|
253
256
|
disabled={perItem && keptCount === 0}
|
|
254
257
|
onPress={props.onApply}
|
package/src/confidence.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StyleSheet, View } from "react-native";
|
|
2
2
|
import { colors, solid, type ColorName } from "./colors";
|
|
3
3
|
import { Text } from "./text";
|
|
4
|
+
import { useLoticsLocale } from "./locale";
|
|
4
5
|
|
|
5
6
|
export type ConfidenceLevel = "high" | "medium" | "low";
|
|
6
7
|
|
|
@@ -20,7 +21,6 @@ export interface ConfidenceProps {
|
|
|
20
21
|
labels?: Partial<ConfidenceLabels>;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
const DEFAULT_LABELS: ConfidenceLabels = { high: "High confidence", medium: "Medium confidence", low: "Low confidence" };
|
|
24
24
|
const FILLED: Record<ConfidenceLevel, number> = { high: 3, medium: 2, low: 1 };
|
|
25
25
|
// high emerald, medium amber, low zinc (unsure — not an error). One family per
|
|
26
26
|
// level; the fill count AND the colour both carry it.
|
|
@@ -40,7 +40,7 @@ export function levelFromScore(score: number): ConfidenceLevel {
|
|
|
40
40
|
*/
|
|
41
41
|
export function Confidence(props: ConfidenceProps) {
|
|
42
42
|
const level = props.level ?? (props.score != null ? levelFromScore(props.score) : "medium");
|
|
43
|
-
const l = { ...
|
|
43
|
+
const l = { ...useLoticsLocale().confidence, ...props.labels };
|
|
44
44
|
const filled = FILLED[level];
|
|
45
45
|
const fill = solid(COLOR[level]);
|
|
46
46
|
return (
|
package/src/date_filter.tsx
CHANGED
|
@@ -11,6 +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
15
|
|
|
15
16
|
type SelectionMode = "single" | "range";
|
|
16
17
|
|
|
@@ -42,25 +43,6 @@ export interface DateFilterLabels extends SegmentLabels {
|
|
|
42
43
|
selectDate: string;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const DEFAULT_LABELS: DateFilterLabels = {
|
|
46
|
-
year: "Year",
|
|
47
|
-
month: "Month",
|
|
48
|
-
day: "Day",
|
|
49
|
-
hour: "Hour",
|
|
50
|
-
minute: "Minute",
|
|
51
|
-
dayPeriod: "AM/PM",
|
|
52
|
-
today: "Today",
|
|
53
|
-
yesterday: "Yesterday",
|
|
54
|
-
tomorrow: "Tomorrow",
|
|
55
|
-
thisWeek: "This week",
|
|
56
|
-
thisMonth: "This month",
|
|
57
|
-
lastMonth: "Last month",
|
|
58
|
-
custom: "Custom",
|
|
59
|
-
from: "From",
|
|
60
|
-
to: "To",
|
|
61
|
-
selectDateRange: "Select date range",
|
|
62
|
-
selectDate: "Select date",
|
|
63
|
-
};
|
|
64
46
|
|
|
65
47
|
export interface DateFilterProps {
|
|
66
48
|
value: DateFilterValue;
|
|
@@ -106,9 +88,10 @@ function formatDateDisplay(date: Date | null, locale: string | undefined): strin
|
|
|
106
88
|
|
|
107
89
|
export function DateFilter(props: DateFilterProps) {
|
|
108
90
|
const { value, onValueChange, includeTime = false, locale } = props;
|
|
91
|
+
const loc = useLoticsLocale().dateRange;
|
|
109
92
|
const labels = useMemo<DateFilterLabels>(
|
|
110
|
-
() => ({ ...
|
|
111
|
-
[props.labels],
|
|
93
|
+
() => ({ ...loc, ...props.labels }),
|
|
94
|
+
[loc, props.labels],
|
|
112
95
|
);
|
|
113
96
|
const screenSize = useScreenSize();
|
|
114
97
|
const calendarRef = useRef<CalendarRef>(null);
|
|
@@ -9,6 +9,7 @@ import { PressableHighlight } from "./pressable_highlight";
|
|
|
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
13
|
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// DateRangeFilterField — the common filter composition over DateFilter:
|
|
@@ -27,7 +28,6 @@ export interface DateRangeFilterFieldLabels extends DateFilterLabels {
|
|
|
27
28
|
placeholder: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const DEFAULT_FIELD_LABELS = { clear: "Clear", done: "Done", placeholder: "All time" };
|
|
31
31
|
|
|
32
32
|
export interface DateRangeFilterFieldProps {
|
|
33
33
|
value: DateFilterValue;
|
|
@@ -110,7 +110,8 @@ 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
|
|
113
|
+
const loc = useLoticsLocale().dateRange;
|
|
114
|
+
const labels = useMemo(() => ({ ...loc, ...props.labels }), [loc, props.labels]);
|
|
114
115
|
const [open, setOpen] = useState(false);
|
|
115
116
|
|
|
116
117
|
const hasValue = Boolean(value.start.date || value.end.date);
|
package/src/locale.tsx
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createContext, useContext, type ReactNode } from "react";
|
|
2
2
|
import { type PaginationLabels } from "./pagination";
|
|
3
3
|
import { type SortHeaderLabels } from "./sort_header";
|
|
4
|
+
import { type ConfidenceLabels } from "./confidence";
|
|
5
|
+
import { type RemainderMeterLabels } from "./remainder_meter";
|
|
6
|
+
import { type DateRangeFilterFieldLabels } from "./date_range_filter_field";
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* The kit's localizable strings, one slice per string-bearing component. A
|
|
@@ -29,6 +32,30 @@ export interface LoticsLocale {
|
|
|
29
32
|
filterChip: { clear: string };
|
|
30
33
|
/** `Drawer`: the record prev/next + close controls (screen-reader names). */
|
|
31
34
|
drawer: { previous: string; next: string; close: string };
|
|
35
|
+
/** `Confidence`: the full level phrase ("High confidence" …). */
|
|
36
|
+
confidence: ConfidenceLabels;
|
|
37
|
+
/** `RemainderMeter`: the applied / remaining / over / exact captions. */
|
|
38
|
+
remainderMeter: Required<RemainderMeterLabels>;
|
|
39
|
+
/** `ChangeReview` (+ `ChangeReviewActions`): the review-card chrome — the
|
|
40
|
+
* Keep/Drop verdicts, Undo, the Applied/Discarded tags, the kept-counter,
|
|
41
|
+
* the title, and the commit-bar Accept-all / Discard. */
|
|
42
|
+
changeReview: {
|
|
43
|
+
title: string;
|
|
44
|
+
accept: string;
|
|
45
|
+
reject: string;
|
|
46
|
+
undo: string;
|
|
47
|
+
applied: string;
|
|
48
|
+
discarded: string;
|
|
49
|
+
keptCount: (kept: number, total: number) => string;
|
|
50
|
+
acceptAll: string;
|
|
51
|
+
discard: string;
|
|
52
|
+
apply: string;
|
|
53
|
+
applyKept: string;
|
|
54
|
+
};
|
|
55
|
+
/** `DateRangeFilterField` (and the `DateFilter` panel it wraps): presets,
|
|
56
|
+
* from/to, the footer Clear/Done, the trigger placeholder, and the time-field
|
|
57
|
+
* segment a11y names. */
|
|
58
|
+
dateRange: DateRangeFilterFieldLabels;
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
/** The platform default — English. Every component's hardcoded default lives
|
|
@@ -50,6 +77,34 @@ export const en: LoticsLocale = {
|
|
|
50
77
|
optionList: { selectAll: "Select all", deselectAll: "Deselect all", noResults: "No results", recent: "Recent" },
|
|
51
78
|
filterChip: { clear: "Clear" },
|
|
52
79
|
drawer: { previous: "Previous record", next: "Next record", close: "Close" },
|
|
80
|
+
confidence: { high: "High confidence", medium: "Medium confidence", low: "Low confidence" },
|
|
81
|
+
remainderMeter: {
|
|
82
|
+
applied: (allocated, total) => `${allocated} of ${total} applied`,
|
|
83
|
+
remaining: (remainder) => `${remainder} unapplied`,
|
|
84
|
+
over: (amount) => `Over by ${amount}`,
|
|
85
|
+
exact: "Fully applied",
|
|
86
|
+
},
|
|
87
|
+
changeReview: {
|
|
88
|
+
title: "Suggested edits",
|
|
89
|
+
accept: "Keep",
|
|
90
|
+
reject: "Drop",
|
|
91
|
+
undo: "Undo",
|
|
92
|
+
applied: "Applied",
|
|
93
|
+
discarded: "Discarded",
|
|
94
|
+
keptCount: (kept, total) => `${kept} of ${total} kept`,
|
|
95
|
+
acceptAll: "Accept all",
|
|
96
|
+
discard: "Discard",
|
|
97
|
+
apply: "Apply",
|
|
98
|
+
applyKept: "Apply kept",
|
|
99
|
+
},
|
|
100
|
+
dateRange: {
|
|
101
|
+
year: "Year", month: "Month", day: "Day", hour: "Hour", minute: "Minute", dayPeriod: "AM/PM",
|
|
102
|
+
today: "Today", yesterday: "Yesterday", tomorrow: "Tomorrow",
|
|
103
|
+
thisWeek: "This week", thisMonth: "This month", lastMonth: "Last month",
|
|
104
|
+
custom: "Custom", from: "From", to: "To",
|
|
105
|
+
selectDateRange: "Select date range", selectDate: "Select date",
|
|
106
|
+
clear: "Clear", done: "Done", placeholder: "All time",
|
|
107
|
+
},
|
|
53
108
|
};
|
|
54
109
|
|
|
55
110
|
/** Vietnamese. Maintained once here so every app (and the frontend) shares one
|
|
@@ -71,6 +126,34 @@ export const vi: LoticsLocale = {
|
|
|
71
126
|
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
127
|
filterChip: { clear: "Xóa" },
|
|
73
128
|
drawer: { previous: "Bản ghi trước", next: "Bản ghi sau", close: "Đóng" },
|
|
129
|
+
confidence: { high: "Độ tin cậy cao", medium: "Độ tin cậy trung bình", low: "Độ tin cậy thấp" },
|
|
130
|
+
remainderMeter: {
|
|
131
|
+
applied: (allocated, total) => `Đã phân bổ ${allocated}/${total}`,
|
|
132
|
+
remaining: (remainder) => `Còn ${remainder}`,
|
|
133
|
+
over: (amount) => `Vượt ${amount}`,
|
|
134
|
+
exact: "Đã phân bổ đủ",
|
|
135
|
+
},
|
|
136
|
+
changeReview: {
|
|
137
|
+
title: "Đề xuất chỉnh sửa",
|
|
138
|
+
accept: "Giữ",
|
|
139
|
+
reject: "Bỏ",
|
|
140
|
+
undo: "Hoàn tác",
|
|
141
|
+
applied: "Đã áp dụng",
|
|
142
|
+
discarded: "Đã bỏ",
|
|
143
|
+
keptCount: (kept, total) => `Giữ ${kept}/${total}`,
|
|
144
|
+
acceptAll: "Giữ tất cả",
|
|
145
|
+
discard: "Hủy",
|
|
146
|
+
apply: "Áp dụng",
|
|
147
|
+
applyKept: "Áp dụng mục đã giữ",
|
|
148
|
+
},
|
|
149
|
+
dateRange: {
|
|
150
|
+
year: "Năm", month: "Tháng", day: "Ngày", hour: "Giờ", minute: "Phút", dayPeriod: "SA/CH",
|
|
151
|
+
today: "Hôm nay", yesterday: "Hôm qua", tomorrow: "Ngày mai",
|
|
152
|
+
thisWeek: "Tuần này", thisMonth: "Tháng này", lastMonth: "Tháng trước",
|
|
153
|
+
custom: "Tùy chọn", from: "Từ", to: "Đến",
|
|
154
|
+
selectDateRange: "Chọn khoảng ngày", selectDate: "Chọn ngày",
|
|
155
|
+
clear: "Xóa", done: "Xong", placeholder: "Tất cả thời gian",
|
|
156
|
+
},
|
|
74
157
|
};
|
|
75
158
|
|
|
76
159
|
const LoticsLocaleContext = createContext<LoticsLocale>(en);
|
package/src/remainder_meter.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StyleSheet, View, type DimensionValue } from "react-native";
|
|
2
2
|
import { colors, solid } from "./colors";
|
|
3
3
|
import { Text } from "./text";
|
|
4
|
+
import { useLoticsLocale } from "./locale";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Override the meter's captions for localization. Each key is optional and
|
|
@@ -17,11 +18,6 @@ export interface RemainderMeterLabels {
|
|
|
17
18
|
exact?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const defaultApplied = (allocated: string, total: string) => `${allocated} of ${total} applied`;
|
|
21
|
-
const defaultRemaining = (remainder: string) => `${remainder} unapplied`;
|
|
22
|
-
const defaultOver = (amount: string) => `Over by ${amount}`;
|
|
23
|
-
const DEFAULT_EXACT = "Fully applied";
|
|
24
|
-
|
|
25
21
|
export interface RemainderMeterProps {
|
|
26
22
|
/** The source amount being distributed — the payment, the available stock. */
|
|
27
23
|
total: number;
|
|
@@ -43,20 +39,21 @@ export interface RemainderMeterProps {
|
|
|
43
39
|
*/
|
|
44
40
|
export function RemainderMeter(props: RemainderMeterProps) {
|
|
45
41
|
const { total, allocated, format = (n) => n.toLocaleString(), labels } = props;
|
|
42
|
+
const loc = useLoticsLocale().remainderMeter;
|
|
46
43
|
const remainder = total - allocated;
|
|
47
44
|
const state = remainder > 0 ? "under" : remainder < 0 ? "over" : "exact";
|
|
48
45
|
const pct: DimensionValue = `${total <= 0 ? 0 : Math.min(100, (allocated / total) * 100)}%`;
|
|
49
46
|
const barColor = state === "over" ? solid("red") : state === "exact" ? solid("emerald") : solid("blue");
|
|
50
47
|
const right =
|
|
51
48
|
state === "exact"
|
|
52
|
-
? (labels?.exact ??
|
|
49
|
+
? (labels?.exact ?? loc.exact)
|
|
53
50
|
: state === "over"
|
|
54
|
-
? (labels?.over ??
|
|
55
|
-
: (labels?.remaining ??
|
|
51
|
+
? (labels?.over ?? loc.over)(format(-remainder))
|
|
52
|
+
: (labels?.remaining ?? loc.remaining)(format(remainder));
|
|
56
53
|
return (
|
|
57
54
|
<View style={{ gap: 8 }}>
|
|
58
55
|
<View style={{ flexDirection: "row", alignItems: "baseline", gap: 8 }}>
|
|
59
|
-
<Text size="sm" color="muted" style={{ flex: 1 }}>{(labels?.applied ??
|
|
56
|
+
<Text size="sm" color="muted" style={{ flex: 1 }}>{(labels?.applied ?? loc.applied)(format(allocated), format(total))}</Text>
|
|
60
57
|
<Text size="sm" weight="medium" tabular color={state === "over" ? "danger" : state === "exact" ? "success" : "default"}>
|
|
61
58
|
{right}
|
|
62
59
|
</Text>
|