@lotics/ui 47.16.0 → 47.16.1
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 +7 -2
- package/package.json +1 -1
- package/src/charge_lines.tsx +15 -3
- package/src/format_money.ts +23 -10
package/docs/catalog.md
CHANGED
|
@@ -648,7 +648,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
648
648
|
- **`markdown.css`** — import once for the web markdown styling.
|
|
649
649
|
- **`format_date`** — `formatDate` / `parseDate` / `toISODate` + `DateFormatStyle` (incl.
|
|
650
650
|
`monthYear` and `quarterYear`, the two period labels).
|
|
651
|
-
- **`format_money`** — `formatMoney` / `formatCompactNumber`.
|
|
651
|
+
- **`format_money`** — `formatMoney` / `formatCompactNumber`. **`compact` abbreviates in the
|
|
652
|
+
LOCALE's own words**: `vi` gets "486 tr ₫" / "1,28 tỷ ₫", every other locale gets Intl's
|
|
653
|
+
compact currency ("$1.2M"). `tỷ` and `tr` are Vietnamese words, so a figure under `en-US`
|
|
654
|
+
never wears one — it reads as a typo, not as a number.
|
|
652
655
|
- **`text_utils`** — text/typography plumbing: `getTextColor` (the TextColor→hex map incl.
|
|
653
656
|
the AA-cleared valence set), the Inter `fontFamily*` stacks, and `getInputTextStyle` /
|
|
654
657
|
`getInputLineHeight` — the 16px-mobile/14px-desktop input contract that stops Safari iOS
|
|
@@ -1664,7 +1667,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1664
1667
|
**And before reaching for a verb at all, ask whether the value is simply the field's DEFAULT** —
|
|
1665
1668
|
a tariff, a list price, the figure a line is created with. Then there is no verb: the field
|
|
1666
1669
|
already holds it, and clearing the field restores it. A button that re-applies what the field
|
|
1667
|
-
would hold anyway is a control for a state the reader cannot reach. `
|
|
1670
|
+
would hold anyway is a control for a state the reader cannot reach. **A flat line's `amount: null` is a cost NOBODY HAS STATED** — an excluded service, a
|
|
1671
|
+
figure the supplier still owes — and prints a dash, never the band's zero, because a service
|
|
1672
|
+
shown at zero reads as free; `0` stays a price somebody set. `extra` carries a second control belonging
|
|
1668
1673
|
to the charge (how it was paid); `warning` names a problem on the line that has it; `locked`
|
|
1669
1674
|
turns the editors into values for a settled band and draws no verb of any kind; `empty` speaks for a record nobody has priced
|
|
1670
1675
|
yet. `formatMoney` is the BAND's, so two lines cannot disagree about a currency, and the `total`
|
package/package.json
CHANGED
package/src/charge_lines.tsx
CHANGED
|
@@ -73,7 +73,11 @@ export interface ChargeLineProps {
|
|
|
73
73
|
quantity?: number | null;
|
|
74
74
|
unitPrice?: number | null;
|
|
75
75
|
/** Flat line: the amount itself. Ignored when `quantity`/`unitPrice` are set,
|
|
76
|
-
* because a derived figure and a typed one cannot both be the truth.
|
|
76
|
+
* because a derived figure and a typed one cannot both be the truth.
|
|
77
|
+
* `null` is a cost NOBODY HAS STATED — a supplier's exclusion, a line still
|
|
78
|
+
* awaiting a figure — and renders as a dash, never as the band's zero: a
|
|
79
|
+
* service printed at zero reads as free. Pass `0` for a price somebody
|
|
80
|
+
* actually set to nothing. */
|
|
77
81
|
amount?: number | null;
|
|
78
82
|
onAmountChange?: (next: number | null) => void | Promise<void>;
|
|
79
83
|
/** The same, for a FLAT line's amount. Unconditional and `disabled` when idle. */
|
|
@@ -204,6 +208,14 @@ export function ChargeLine(props: ChargeLineProps) {
|
|
|
204
208
|
: undefined;
|
|
205
209
|
const priced = quantity !== undefined || unitPrice !== undefined;
|
|
206
210
|
const amount = priced ? (quantity ?? 0) * (unitPrice ?? 0) : (props.amount ?? 0);
|
|
211
|
+
/**
|
|
212
|
+
* A flat line whose amount is NULL has no figure — a cost nobody has stated
|
|
213
|
+
* yet, a service the supplier excluded. Read-only it used to print the
|
|
214
|
+
* band's zero, which says the line is free; the editable spelling of the
|
|
215
|
+
* same value already showed the placeholder dash. One value cannot render
|
|
216
|
+
* as two different claims, and of the two only the dash is true.
|
|
217
|
+
*/
|
|
218
|
+
const unstated = !priced && props.amount == null;
|
|
207
219
|
/** The row's money verb — the unit price's on a priced line, the amount's on a
|
|
208
220
|
* flat one. A locked band has nothing to act on, so it draws none. */
|
|
209
221
|
const warning = props.warning ? (
|
|
@@ -276,8 +288,8 @@ export function ChargeLine(props: ChargeLineProps) {
|
|
|
276
288
|
answer — and it lands in no column, so the total below closes nothing. */}
|
|
277
289
|
<View style={styles.amountSlot}>
|
|
278
290
|
{priced || locked || !props.onAmountChange ? (
|
|
279
|
-
<Text size="sm" tabular style={[styles.right, styles.inset]}>
|
|
280
|
-
{money(amount)}
|
|
291
|
+
<Text size="sm" tabular color={unstated ? "muted" : undefined} style={[styles.right, styles.inset]}>
|
|
292
|
+
{unstated ? "—" : money(amount)}
|
|
281
293
|
</Text>
|
|
282
294
|
) : (
|
|
283
295
|
<InlineNumberInput
|
package/src/format_money.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export interface FormatMoneyOptions {
|
|
2
2
|
locale?: string;
|
|
3
3
|
currency?: string;
|
|
4
|
-
/** Abbreviate for display density
|
|
5
|
-
* "
|
|
6
|
-
*
|
|
4
|
+
/** Abbreviate for display density — in the LOCALE's own words: `vi` gets
|
|
5
|
+
* "1,28 tỷ ₫" / "486 tr ₫", everywhere else gets Intl's compact currency
|
|
6
|
+
* ("$1.2M"). For stat strips/cards where the full figure lives in the table
|
|
7
|
+
* below — never for the table itself. */
|
|
7
8
|
compact?: boolean;
|
|
8
9
|
/** Significant digits below the currency's minor unit. `Intl`'s currency
|
|
9
10
|
* style floors at that unit — 2 for USD — so a genuine per-unit price like
|
|
@@ -14,18 +15,25 @@ export interface FormatMoneyOptions {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
* "
|
|
19
|
-
*
|
|
18
|
+
* A magnitude abbreviated IN THE READER'S OWN LANGUAGE: `vi` gets its own
|
|
19
|
+
* words (≥1 tỷ → "1,28 tỷ", ≥1 triệu → "486 tr"), every other locale gets
|
|
20
|
+
* Intl's compact notation ("1.2M", "486K"). Smaller values render in full.
|
|
21
|
+
* Bare numbers only — for money, use `formatMoney` with `compact`.
|
|
22
|
+
*
|
|
23
|
+
* `tỷ` and `tr` are Vietnamese WORDS, so they belong to a Vietnamese locale
|
|
24
|
+
* and nowhere else: appending them under `en-US` printed "1 tr USD" on a US
|
|
25
|
+
* register, which reads as a typo rather than as a number. A locale Intl has
|
|
26
|
+
* no compact form for falls back to grouped digits — long, and never foreign.
|
|
20
27
|
*/
|
|
21
28
|
export function formatCompactNumber(value: number, locale = "vi-VN"): string {
|
|
29
|
+
if (Math.abs(value) < 1_000_000) return value.toLocaleString(locale);
|
|
30
|
+
if (!locale.startsWith("vi")) {
|
|
31
|
+
return value.toLocaleString(locale, { notation: "compact", maximumFractionDigits: 1 });
|
|
32
|
+
}
|
|
22
33
|
if (Math.abs(value) >= 1_000_000_000) {
|
|
23
34
|
return `${(value / 1_000_000_000).toLocaleString(locale, { maximumFractionDigits: 2 })} tỷ`;
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
return `${Math.round(value / 1_000_000).toLocaleString(locale)} tr`;
|
|
27
|
-
}
|
|
28
|
-
return value.toLocaleString(locale);
|
|
36
|
+
return `${Math.round(value / 1_000_000).toLocaleString(locale)} tr`;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
/**
|
|
@@ -37,6 +45,11 @@ export function formatCompactNumber(value: number, locale = "vi-VN"): string {
|
|
|
37
45
|
export function formatMoney(value: number, options: FormatMoneyOptions = {}): string {
|
|
38
46
|
const { locale = "vi-VN", currency = "VND", compact = false, maxFractionDigits } = options;
|
|
39
47
|
if (compact && Math.abs(value) >= 1_000_000) {
|
|
48
|
+
// Outside Vietnamese, Intl abbreviates the CURRENCY itself — "$1.2M" — so
|
|
49
|
+
// the symbol keeps its place instead of trailing a foreign magnitude word.
|
|
50
|
+
if (!locale.startsWith("vi")) {
|
|
51
|
+
return value.toLocaleString(locale, { style: "currency", currency, notation: "compact", maximumFractionDigits: 1 });
|
|
52
|
+
}
|
|
40
53
|
const suffixed = formatCompactNumber(value, locale);
|
|
41
54
|
return currency === "VND" ? `${suffixed} ₫` : `${suffixed} ${currency}`;
|
|
42
55
|
}
|