@lotics/ui 3.2.0 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -3,6 +3,34 @@ import { View, StyleSheet } from "react-native";
3
3
  import { IconButton } from "./icon_button";
4
4
  import { Text } from "./text";
5
5
 
6
+ /**
7
+ * Localizable strings for `Pagination`. Every field is optional; an omitted
8
+ * field keeps the English default, so an app that needs another language passes
9
+ * only what it overrides (mirrors `DateRangeFilterField`'s `labels`). The
10
+ * formatters receive 1-indexed display numbers and the raw total, so a caller
11
+ * controls its own locale (e.g. `total.toLocaleString("vi-VN")`).
12
+ */
13
+ export interface PaginationLabels {
14
+ /** Range summary with a known total. Default `${start}–${end} of ${total}`. */
15
+ rangeWithTotal?: (start: number, end: number, total: number) => string;
16
+ /** Range summary without a total. Default `${start}–${end}`. */
17
+ range?: (start: number, end: number) => string;
18
+ /** Page indicator with a known total. Default `Page ${page} of ${pageCount}`. */
19
+ pageWithTotal?: (page: number, pageCount: number) => string;
20
+ /** Page indicator without a total. Default `Page ${page}`. */
21
+ page?: (page: number) => string;
22
+ /** Previous-page button tooltip. Default "Previous". */
23
+ previous?: string;
24
+ /** Next-page button tooltip. Default "Next". */
25
+ next?: string;
26
+ }
27
+
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
+
6
34
  export interface PaginationProps {
7
35
  /** 0-indexed. */
8
36
  page: number;
@@ -18,6 +46,8 @@ export interface PaginationProps {
18
46
  total?: number;
19
47
  loading?: boolean;
20
48
  onPageChange: (page: number) => void;
49
+ /** Override the English strings for localization. Omit to keep defaults. */
50
+ labels?: PaginationLabels;
21
51
  }
22
52
 
23
53
  /**
@@ -28,21 +58,21 @@ export interface PaginationProps {
28
58
  * doubling up a border.
29
59
  */
30
60
  export function Pagination(props: PaginationProps): React.ReactNode {
31
- const { page, pageSize, rowCount, hasMore, total, loading, onPageChange } = props;
61
+ const { page, pageSize, rowCount, hasMore, total, loading, onPageChange, labels } = props;
32
62
  const start = page * pageSize + 1;
33
63
  const end = page * pageSize + rowCount;
34
64
  const showRange = !loading && rowCount > 0;
35
65
 
36
66
  const summary = showRange
37
67
  ? total !== undefined
38
- ? `${start}–${end} of ${total.toLocaleString()}`
39
- : `${start}–${end}`
68
+ ? (labels?.rangeWithTotal ?? defaultRangeWithTotal)(start, end, total)
69
+ : (labels?.range ?? defaultRange)(start, end)
40
70
  : "";
41
71
 
42
72
  const pageLabel =
43
73
  total !== undefined
44
- ? `Page ${page + 1} of ${Math.max(1, Math.ceil(total / pageSize))}`
45
- : `Page ${page + 1}`;
74
+ ? (labels?.pageWithTotal ?? defaultPageWithTotal)(page + 1, Math.max(1, Math.ceil(total / pageSize)))
75
+ : (labels?.page ?? defaultPage)(page + 1);
46
76
 
47
77
  return (
48
78
  <View style={styles.container}>
@@ -60,14 +90,14 @@ export function Pagination(props: PaginationProps): React.ReactNode {
60
90
  color="secondary"
61
91
  onPress={() => onPageChange(Math.max(0, page - 1))}
62
92
  disabled={page === 0 || !!loading}
63
- tooltip="Previous"
93
+ tooltip={labels?.previous ?? "Previous"}
64
94
  />
65
95
  <IconButton
66
96
  icon="chevron-right"
67
97
  color="secondary"
68
98
  onPress={() => onPageChange(page + 1)}
69
99
  disabled={!hasMore || !!loading}
70
- tooltip="Next"
100
+ tooltip={labels?.next ?? "Next"}
71
101
  />
72
102
  </View>
73
103
  </View>
@@ -39,6 +39,20 @@ export function sortBy<T>(
39
39
  });
40
40
  }
41
41
 
42
+ /**
43
+ * Localizable a11y fragments for `SortHeader`. The visible header is the app's
44
+ * own `label`; only the screen-reader announcement is built here, so these
45
+ * cover that string. Omit any field to keep the English default.
46
+ */
47
+ export interface SortHeaderLabels {
48
+ /** a11y prefix around the column label. Default `(label) => `Sort by ${label}``. */
49
+ sortBy?: (label: string) => string;
50
+ /** Appended when sorted ascending. Default ", ascending". */
51
+ ascending?: string;
52
+ /** Appended when sorted descending. Default ", descending". */
53
+ descending?: string;
54
+ }
55
+
42
56
  export interface SortHeaderProps {
43
57
  label: string;
44
58
  sortKey: string;
@@ -47,6 +61,8 @@ export interface SortHeaderProps {
47
61
  /** Right-align for numeric columns — the arrow then sits LEFT of the label. */
48
62
  align?: "left" | "right";
49
63
  style?: ViewStyle;
64
+ /** Override the English a11y strings for localization. Omit to keep defaults. */
65
+ labels?: SortHeaderLabels;
50
66
  }
51
67
 
52
68
  /**
@@ -57,15 +73,20 @@ export interface SortHeaderProps {
57
73
  * flush with the column content beneath it.
58
74
  */
59
75
  export function SortHeader(props: SortHeaderProps) {
60
- const { label, sortKey, sort, onSort, align = "left", style } = props;
76
+ const { label, sortKey, sort, onSort, align = "left", style, labels } = props;
61
77
  const active = sort?.key === sortKey;
62
78
  const arrow = active ? (sort.dir === "asc" ? "chevron-up" : "chevron-down") : undefined;
63
- const dirText = active ? (sort.dir === "asc" ? ", ascending" : ", descending") : "";
79
+ const dirText = active
80
+ ? sort.dir === "asc"
81
+ ? (labels?.ascending ?? ", ascending")
82
+ : (labels?.descending ?? ", descending")
83
+ : "";
84
+ const sortByLabel = (labels?.sortBy ?? ((l: string) => `Sort by ${l}`))(label);
64
85
 
65
86
  return (
66
87
  <PressableHighlight
67
88
  accessibilityRole="button"
68
- accessibilityLabel={`Sort by ${label}${dirText}`}
89
+ accessibilityLabel={`${sortByLabel}${dirText}`}
69
90
  onPress={() => onSort(sortKey)}
70
91
  style={[styles.header, align === "right" ? styles.right : null, style]}
71
92
  >
package/src/table.tsx CHANGED
@@ -11,7 +11,7 @@ import { StyleSheet, View, Pressable, type ViewStyle } from "react-native";
11
11
  import { Text } from "./text";
12
12
  import { Divider } from "./divider";
13
13
  import { PressableRow } from "./pressable_row";
14
- import { SortHeader, type SortState } from "./sort_header";
14
+ import { SortHeader, type SortState, type SortHeaderLabels } from "./sort_header";
15
15
 
16
16
  /**
17
17
  * One column of a register — its width/flex/align/label/sortability defined ONCE,
@@ -50,6 +50,8 @@ export interface TableProps {
50
50
  sort?: SortState | null;
51
51
  /** Cycles the sort (none→asc→desc→none) — pair with `cycleSort` in the parent. */
52
52
  onSort?: (key: string) => void;
53
+ /** Localized a11y strings for the sortable headers. Omit to keep English. */
54
+ sortLabels?: SortHeaderLabels;
53
55
  /** Reserve a leading gutter (px) for rows that render a `leading` slot (a checkbox). */
54
56
  leading?: number;
55
57
  /** Reserve a trailing gutter (px) for rows that render a `trailing` slot (a ⋯ / button). */
@@ -67,7 +69,7 @@ export interface TableProps {
67
69
  * non-columnar list (entity piles, card stacks) use `PressableRow` directly.
68
70
  */
69
71
  export function Table(props: TableProps) {
70
- const { columns, sort, onSort, leading = 0, trailing = 0, children } = props;
72
+ const { columns, sort, onSort, sortLabels, leading = 0, trailing = 0, children } = props;
71
73
  const rows = Children.toArray(children).filter(isValidElement);
72
74
 
73
75
  return (
@@ -78,7 +80,7 @@ export function Table(props: TableProps) {
78
80
  <View key={col.key} style={colStyle(col)}>
79
81
  {col.label ? (
80
82
  col.sortable && onSort ? (
81
- <SortHeader label={col.label} sortKey={col.key} sort={sort ?? null} onSort={onSort} align={col.align} />
83
+ <SortHeader label={col.label} sortKey={col.key} sort={sort ?? null} onSort={onSort} align={col.align} labels={sortLabels} />
82
84
  ) : (
83
85
  <Text size="xs" color="muted" transform="uppercase" numberOfLines={1}>
84
86
  {col.label}