@twentyfirstmedia/ui-core 0.2.0 → 0.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/dist/components/admin.d.ts.map +1 -1
- package/dist/components/admin.js +95 -71
- package/dist/components/admin.js.map +1 -1
- package/dist/components/agents.d.ts.map +1 -1
- package/dist/components/agents.js +49 -35
- package/dist/components/agents.js.map +1 -1
- package/dist/components/app-chrome.d.ts.map +1 -1
- package/dist/components/app-chrome.js +6 -2
- package/dist/components/app-chrome.js.map +1 -1
- package/dist/components/approvals.d.ts.map +1 -1
- package/dist/components/approvals.js +22 -18
- package/dist/components/approvals.js.map +1 -1
- package/dist/components/building-blocks.d.ts.map +1 -1
- package/dist/components/building-blocks.js +6 -2
- package/dist/components/building-blocks.js.map +1 -1
- package/dist/components/charts.d.ts.map +1 -1
- package/dist/components/charts.js +20 -10
- package/dist/components/charts.js.map +1 -1
- package/dist/components/chat.d.ts.map +1 -1
- package/dist/components/chat.js +31 -19
- package/dist/components/chat.js.map +1 -1
- package/dist/components/commerce.d.ts +2 -2
- package/dist/components/commerce.d.ts.map +1 -1
- package/dist/components/commerce.js +45 -31
- package/dist/components/commerce.js.map +1 -1
- package/dist/components/data-display.d.ts +5 -9
- package/dist/components/data-display.d.ts.map +1 -1
- package/dist/components/data-display.js +34 -35
- package/dist/components/data-display.js.map +1 -1
- package/dist/components/data-table.d.ts.map +1 -1
- package/dist/components/data-table.js +15 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/developer.d.ts.map +1 -1
- package/dist/components/developer.js +20 -11
- package/dist/components/developer.js.map +1 -1
- package/dist/components/feedback.d.ts.map +1 -1
- package/dist/components/feedback.js +12 -6
- package/dist/components/feedback.js.map +1 -1
- package/dist/components/form.d.ts +2 -1
- package/dist/components/form.d.ts.map +1 -1
- package/dist/components/form.js +12 -9
- package/dist/components/form.js.map +1 -1
- package/dist/components/forms.d.ts +2 -1
- package/dist/components/forms.d.ts.map +1 -1
- package/dist/components/forms.js +92 -42
- package/dist/components/forms.js.map +1 -1
- package/dist/components/foundation.d.ts +5 -2
- package/dist/components/foundation.d.ts.map +1 -1
- package/dist/components/foundation.js +15 -12
- package/dist/components/foundation.js.map +1 -1
- package/dist/components/inputs.d.ts.map +1 -1
- package/dist/components/inputs.js +3 -1
- package/dist/components/inputs.js.map +1 -1
- package/dist/components/mail.d.ts.map +1 -1
- package/dist/components/mail.js +15 -10
- package/dist/components/mail.js.map +1 -1
- package/dist/components/media.d.ts.map +1 -1
- package/dist/components/media.js +39 -18
- package/dist/components/media.js.map +1 -1
- package/dist/components/navigation.d.ts.map +1 -1
- package/dist/components/navigation.js +21 -9
- package/dist/components/navigation.js.map +1 -1
- package/dist/components/operations.d.ts.map +1 -1
- package/dist/components/operations.js +37 -27
- package/dist/components/operations.js.map +1 -1
- package/dist/components/previews.d.ts.map +1 -1
- package/dist/components/previews.js +14 -10
- package/dist/components/previews.js.map +1 -1
- package/dist/components/primitives.d.ts.map +1 -1
- package/dist/components/primitives.js +4 -1
- package/dist/components/primitives.js.map +1 -1
- package/dist/components/real-estate.d.ts.map +1 -1
- package/dist/components/real-estate.js +43 -28
- package/dist/components/real-estate.js.map +1 -1
- package/dist/components/rich-text-editor.d.ts.map +1 -1
- package/dist/components/rich-text-editor.js +18 -15
- package/dist/components/rich-text-editor.js.map +1 -1
- package/dist/components/select-menu.d.ts.map +1 -1
- package/dist/components/select-menu.js +4 -1
- package/dist/components/select-menu.js.map +1 -1
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +8 -3
- package/dist/components/shell.js.map +1 -1
- package/dist/components/site.d.ts.map +1 -1
- package/dist/components/site.js +17 -13
- package/dist/components/site.js.map +1 -1
- package/dist/components/types.d.ts +102 -3
- package/dist/components/types.d.ts.map +1 -1
- package/dist/components/workspace.d.ts.map +1 -1
- package/dist/components/workspace.js +50 -34
- package/dist/components/workspace.js.map +1 -1
- package/dist/context.d.ts +39 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +76 -0
- package/dist/context.js.map +1 -0
- package/dist/format.d.ts +33 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +75 -10
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +18 -11
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +11 -18
- package/dist/provider.js.map +1 -1
- package/dist/strings.d.ts +503 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +537 -0
- package/dist/strings.js.map +1 -0
- package/package.json +1 -1
- package/src/components/admin.tsx +117 -93
- package/src/components/agents.tsx +55 -41
- package/src/components/app-chrome.tsx +6 -2
- package/src/components/approvals.tsx +26 -22
- package/src/components/building-blocks.tsx +6 -2
- package/src/components/charts.tsx +21 -11
- package/src/components/chat.tsx +45 -35
- package/src/components/commerce.tsx +61 -53
- package/src/components/data-display.tsx +38 -45
- package/src/components/data-table.tsx +20 -15
- package/src/components/developer.tsx +26 -15
- package/src/components/feedback.tsx +19 -7
- package/src/components/form.tsx +17 -9
- package/src/components/forms.tsx +131 -53
- package/src/components/foundation.tsx +23 -36
- package/src/components/inputs.tsx +4 -2
- package/src/components/mail.tsx +16 -11
- package/src/components/media.tsx +64 -21
- package/src/components/navigation.tsx +25 -13
- package/src/components/operations.tsx +40 -32
- package/src/components/previews.tsx +17 -15
- package/src/components/primitives.tsx +8 -5
- package/src/components/real-estate.tsx +55 -35
- package/src/components/rich-text-editor.tsx +20 -17
- package/src/components/select-menu.tsx +5 -2
- package/src/components/shell.tsx +8 -3
- package/src/components/site.tsx +18 -14
- package/src/components/types.ts +111 -3
- package/src/components/workspace.tsx +79 -60
- package/src/context.ts +112 -0
- package/src/format.ts +108 -15
- package/src/index.ts +43 -0
- package/src/provider.tsx +36 -40
- package/src/strings.ts +1500 -0
|
@@ -3,7 +3,8 @@ import * as React from "react";
|
|
|
3
3
|
import { leaving, usePresence } from "../presence.js";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { Icon } from "../Icon.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useFormat, useStrings } from "../context.js";
|
|
7
|
+
import { DEVELOPER_STRINGS } from "../strings.js";
|
|
7
8
|
import { useControlled, useId } from "../hooks.js";
|
|
8
9
|
import { Portal } from "../portal.js";
|
|
9
10
|
import { CodeBlock } from "./chat.js";
|
|
@@ -84,6 +85,7 @@ export function CodeViewer(p: CodeViewerProps) {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
export function SecretField(p: SecretFieldProps) {
|
|
88
|
+
const t = useStrings(DEVELOPER_STRINGS);
|
|
87
89
|
const shown = React.useState(false),
|
|
88
90
|
copied = React.useState(false);
|
|
89
91
|
const masked = (p.prefix || "") + "••••••••" + (p.last4 || "");
|
|
@@ -100,11 +102,11 @@ export function SecretField(p: SecretFieldProps) {
|
|
|
100
102
|
}}
|
|
101
103
|
>
|
|
102
104
|
{p.onReveal && (
|
|
103
|
-
<Tooltip label={shown[0] ?
|
|
105
|
+
<Tooltip label={shown[0] ? t.hide : t.reveal}>
|
|
104
106
|
<button
|
|
105
107
|
type="button"
|
|
106
108
|
className="tf-iconbtn tf-iconbtn-ghost tf-iconbtn-sm"
|
|
107
|
-
aria-label={shown[0] ?
|
|
109
|
+
aria-label={shown[0] ? t.hide : t.reveal}
|
|
108
110
|
onClick={() => {
|
|
109
111
|
if (shown[0]) return shown[1](false);
|
|
110
112
|
Promise.resolve(p.onReveal?.()).then((ok) => {
|
|
@@ -117,11 +119,11 @@ export function SecretField(p: SecretFieldProps) {
|
|
|
117
119
|
</Tooltip>
|
|
118
120
|
)}
|
|
119
121
|
{p.onCopy && (
|
|
120
|
-
<Tooltip label={copied[0] ?
|
|
122
|
+
<Tooltip label={copied[0] ? t.copied : t.copy}>
|
|
121
123
|
<button
|
|
122
124
|
type="button"
|
|
123
125
|
className="tf-iconbtn tf-iconbtn-ghost tf-iconbtn-sm"
|
|
124
|
-
aria-label=
|
|
126
|
+
aria-label={t.copy}
|
|
125
127
|
onClick={() => {
|
|
126
128
|
p.onCopy?.();
|
|
127
129
|
copied[1](true);
|
|
@@ -136,7 +138,7 @@ export function SecretField(p: SecretFieldProps) {
|
|
|
136
138
|
)}
|
|
137
139
|
{p.onRotate && (
|
|
138
140
|
<Button size="sm" variant="soft" icon="refresh" onClick={p.onRotate}>
|
|
139
|
-
{
|
|
141
|
+
{t.rotate}
|
|
140
142
|
</Button>
|
|
141
143
|
)}
|
|
142
144
|
</span>
|
|
@@ -145,6 +147,8 @@ export function SecretField(p: SecretFieldProps) {
|
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
export function MoneyInput(p: MoneyInputProps) {
|
|
150
|
+
const t = useStrings(DEVELOPER_STRINGS);
|
|
151
|
+
const { formatMoney, formatNumber, formatPercent } = useFormat();
|
|
148
152
|
const id = useId("mi"),
|
|
149
153
|
v = useControlled<number | null>(
|
|
150
154
|
p.value,
|
|
@@ -206,7 +210,7 @@ export function MoneyInput(p: MoneyInputProps) {
|
|
|
206
210
|
{p.vatOptions && (
|
|
207
211
|
<select
|
|
208
212
|
className="tf-input tf-money-vat"
|
|
209
|
-
aria-label=
|
|
213
|
+
aria-label={t.vatRate}
|
|
210
214
|
value={p.vat}
|
|
211
215
|
onChange={(e) => {
|
|
212
216
|
p.onVat && p.onVat(e.target.value);
|
|
@@ -231,9 +235,11 @@ export function MoneyInput(p: MoneyInputProps) {
|
|
|
231
235
|
</div>
|
|
232
236
|
{p.showGross && v[0] != null && p.vat != null && !isNaN(+p.vat) && (
|
|
233
237
|
<span className="tf-field-hint">
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
{t.gross(
|
|
239
|
+
formatMoney(v[0] * (1 + Number(p.vat) / 100), p.currency, {
|
|
240
|
+
minimumFractionDigits: 2,
|
|
241
|
+
}),
|
|
242
|
+
)}
|
|
237
243
|
</span>
|
|
238
244
|
)}
|
|
239
245
|
</FormField>
|
|
@@ -259,6 +265,7 @@ export function SettingsLayout(p: SettingsLayoutProps) {
|
|
|
259
265
|
}
|
|
260
266
|
|
|
261
267
|
export function ShortcutsOverlay(p: ShortcutsOverlayProps) {
|
|
268
|
+
const t = useStrings(DEVELOPER_STRINGS);
|
|
262
269
|
const pr = usePresence(!!p.open);
|
|
263
270
|
React.useEffect(() => {
|
|
264
271
|
if (!p.open) return;
|
|
@@ -279,7 +286,7 @@ export function ShortcutsOverlay(p: ShortcutsOverlayProps) {
|
|
|
279
286
|
className="tf-modal tf-shortcuts"
|
|
280
287
|
role="dialog"
|
|
281
288
|
aria-modal="true"
|
|
282
|
-
aria-label={p.title ||
|
|
289
|
+
aria-label={p.title || t.keyboardShortcuts}
|
|
283
290
|
>
|
|
284
291
|
<div
|
|
285
292
|
className="tf-row"
|
|
@@ -287,8 +294,8 @@ export function ShortcutsOverlay(p: ShortcutsOverlayProps) {
|
|
|
287
294
|
justifyContent: "space-between",
|
|
288
295
|
}}
|
|
289
296
|
>
|
|
290
|
-
<h2 className="tf-modal-title">{p.title ||
|
|
291
|
-
<IconButton icon="x" label=
|
|
297
|
+
<h2 className="tf-modal-title">{p.title || t.keyboardShortcuts}</h2>
|
|
298
|
+
<IconButton icon="x" label={t.close} size="sm" onClick={p.onClose} />
|
|
292
299
|
</div>
|
|
293
300
|
<div className="tf-shortcuts-grid">
|
|
294
301
|
{(p.groups || []).map((g) => (
|
|
@@ -310,6 +317,7 @@ export function ShortcutsOverlay(p: ShortcutsOverlayProps) {
|
|
|
310
317
|
}
|
|
311
318
|
|
|
312
319
|
export function StatCell(p: StatCellProps) {
|
|
320
|
+
const { formatNumber } = useFormat();
|
|
313
321
|
const up = p.delta != null && p.delta >= 0,
|
|
314
322
|
good = p.invert ? !up : up;
|
|
315
323
|
return (
|
|
@@ -350,6 +358,8 @@ export function StatCell(p: StatCellProps) {
|
|
|
350
358
|
}
|
|
351
359
|
|
|
352
360
|
export function UptimeBar(p: UptimeBarProps) {
|
|
361
|
+
const t = useStrings(DEVELOPER_STRINGS);
|
|
362
|
+
const { formatPercent } = useFormat();
|
|
353
363
|
const days = (p.days || []).map((d) => (typeof d === "string" ? { status: d } : d)),
|
|
354
364
|
hov = React.useState<number | null>(null);
|
|
355
365
|
return (
|
|
@@ -376,9 +386,9 @@ export function UptimeBar(p: UptimeBarProps) {
|
|
|
376
386
|
<div className="tf-uptime-foot">
|
|
377
387
|
<span>
|
|
378
388
|
{hov[0] != null
|
|
379
|
-
? (days[hov[0]].label ||
|
|
389
|
+
? (days[hov[0]].label || t.daysAgo(days.length - hov[0] - 1)) +
|
|
380
390
|
(days[hov[0]].note ? " · " + days[hov[0]].note : "")
|
|
381
|
-
: p.caption ||
|
|
391
|
+
: p.caption || t.lastDays(days.length)}
|
|
382
392
|
</span>
|
|
383
393
|
{p.uptime != null && (
|
|
384
394
|
<b className="tf-mono">
|
|
@@ -393,6 +403,7 @@ export function UptimeBar(p: UptimeBarProps) {
|
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
export function Meter(p: MeterProps) {
|
|
406
|
+
const { formatPercent } = useFormat();
|
|
396
407
|
const pct = Math.max(0, Math.min(100, ((p.value || 0) / (p.max || 100)) * 100)),
|
|
397
408
|
tone =
|
|
398
409
|
p.tone || (pct >= (p.danger || 90) ? "blush" : pct >= (p.warn || 75) ? "butter" : "mint");
|
|
@@ -3,6 +3,8 @@ import * as React from "react";
|
|
|
3
3
|
import { leaving, usePresence } from "../presence.js";
|
|
4
4
|
import { moveFocus, useId } from "../hooks.js";
|
|
5
5
|
import { cx } from "../cx.js";
|
|
6
|
+
import { useStrings } from "../context.js";
|
|
7
|
+
import { FEEDBACK_STRINGS } from "../strings.js";
|
|
6
8
|
import { Icon } from "../Icon.js";
|
|
7
9
|
import { Portal } from "../portal.js";
|
|
8
10
|
import {
|
|
@@ -179,6 +181,7 @@ export function Menu(p: MenuProps) {
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
export function Modal(p: ModalProps) {
|
|
184
|
+
const t = useStrings(FEEDBACK_STRINGS);
|
|
182
185
|
const pr = usePresence(p.open ?? true);
|
|
183
186
|
if (!pr.mounted) return null;
|
|
184
187
|
return (
|
|
@@ -186,7 +189,7 @@ export function Modal(p: ModalProps) {
|
|
|
186
189
|
<div className="tf-backdrop" aria-hidden />
|
|
187
190
|
<div className="tf-modal" role="dialog" aria-modal="true" aria-label={p.title}>
|
|
188
191
|
<span className="tf-modal-close">
|
|
189
|
-
<IconButton icon="x" label=
|
|
192
|
+
<IconButton icon="x" label={t.close} size="sm" onClick={p.onClose} />
|
|
190
193
|
</span>
|
|
191
194
|
{p.icon && <TintIcon icon={p.icon} tint={p.tint || "sky"} size={40} />}
|
|
192
195
|
<div>
|
|
@@ -251,6 +254,7 @@ const ALERT_ICON: Record<string, IconName> = {
|
|
|
251
254
|
};
|
|
252
255
|
|
|
253
256
|
export function Alert(p: AlertProps) {
|
|
257
|
+
const t = useStrings(FEEDBACK_STRINGS);
|
|
254
258
|
const tone = p.tone || "info";
|
|
255
259
|
return (
|
|
256
260
|
<div
|
|
@@ -268,7 +272,13 @@ export function Alert(p: AlertProps) {
|
|
|
268
272
|
<div className="tf-alert-actions">
|
|
269
273
|
{p.action}
|
|
270
274
|
{p.onDismiss && (
|
|
271
|
-
<IconButton
|
|
275
|
+
<IconButton
|
|
276
|
+
icon="x"
|
|
277
|
+
label={t.dismiss}
|
|
278
|
+
size="sm"
|
|
279
|
+
variant="ghost"
|
|
280
|
+
onClick={p.onDismiss}
|
|
281
|
+
/>
|
|
272
282
|
)}
|
|
273
283
|
</div>
|
|
274
284
|
)}
|
|
@@ -277,6 +287,7 @@ export function Alert(p: AlertProps) {
|
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
export function ConfirmDialog(p: ConfirmDialogProps) {
|
|
290
|
+
const t = useStrings(FEEDBACK_STRINGS);
|
|
280
291
|
const titleId = useId("confirm");
|
|
281
292
|
const typed = React.useState(""),
|
|
282
293
|
ok = !p.confirmText || typed[0] === p.confirmText,
|
|
@@ -326,7 +337,7 @@ export function ConfirmDialog(p: ConfirmDialogProps) {
|
|
|
326
337
|
{p.confirmText && (
|
|
327
338
|
<label className="tf-field">
|
|
328
339
|
<span className="tf-field-hint">
|
|
329
|
-
|
|
340
|
+
{t.typeToConfirm(<b className="tf-mono">{p.confirmText}</b>)}
|
|
330
341
|
</span>
|
|
331
342
|
<input
|
|
332
343
|
className="tf-input"
|
|
@@ -340,14 +351,14 @@ export function ConfirmDialog(p: ConfirmDialogProps) {
|
|
|
340
351
|
)}
|
|
341
352
|
<div className="tf-modal-foot">
|
|
342
353
|
<button ref={cancel} type="button" className="tf-btn tf-btn-ghost" onClick={p.onCancel}>
|
|
343
|
-
{p.cancelLabel ||
|
|
354
|
+
{p.cancelLabel || t.cancel}
|
|
344
355
|
</button>
|
|
345
356
|
<Button
|
|
346
357
|
variant={p.danger ? "danger-solid" : "primary"}
|
|
347
358
|
disabled={!ok}
|
|
348
359
|
onClick={p.onConfirm}
|
|
349
360
|
>
|
|
350
|
-
{p.confirmLabel ||
|
|
361
|
+
{p.confirmLabel || t.confirm}
|
|
351
362
|
</Button>
|
|
352
363
|
</div>
|
|
353
364
|
</div>
|
|
@@ -357,6 +368,7 @@ export function ConfirmDialog(p: ConfirmDialogProps) {
|
|
|
357
368
|
}
|
|
358
369
|
|
|
359
370
|
export function ToastItem(p: { t: ToastData }) {
|
|
371
|
+
const strings = useStrings(FEEDBACK_STRINGS);
|
|
360
372
|
const t = p.t,
|
|
361
373
|
paused = React.useState(false),
|
|
362
374
|
left = React.useRef(t.duration),
|
|
@@ -396,7 +408,7 @@ export function ToastItem(p: { t: ToastData }) {
|
|
|
396
408
|
toast.dismiss(t.id);
|
|
397
409
|
}}
|
|
398
410
|
>
|
|
399
|
-
{
|
|
411
|
+
{strings.undo}
|
|
400
412
|
</button>
|
|
401
413
|
)}
|
|
402
414
|
{t.action && (
|
|
@@ -414,7 +426,7 @@ export function ToastItem(p: { t: ToastData }) {
|
|
|
414
426
|
<button
|
|
415
427
|
type="button"
|
|
416
428
|
className="tf-toast-close"
|
|
417
|
-
aria-label=
|
|
429
|
+
aria-label={strings.dismiss}
|
|
418
430
|
onClick={() => {
|
|
419
431
|
toast.dismiss(t.id);
|
|
420
432
|
}}
|
package/src/components/form.tsx
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { cx } from "../cx.js";
|
|
9
|
+
import { useStrings } from "../context.js";
|
|
10
|
+
import { FORM_STRINGS } from "../strings.js";
|
|
9
11
|
import type { FormProps, FormRule, FormValues } from "./types.js";
|
|
10
12
|
|
|
11
13
|
interface FormState {
|
|
@@ -22,21 +24,26 @@ export function useFieldError(name: string | undefined): string | undefined {
|
|
|
22
24
|
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
23
25
|
|
|
24
26
|
/** Runs one field's rules; returns the message of the first rule that fails. */
|
|
25
|
-
export function checkRule(
|
|
27
|
+
export function checkRule(
|
|
28
|
+
rule: FormRule,
|
|
29
|
+
raw: string | string[] | undefined,
|
|
30
|
+
values: FormValues,
|
|
31
|
+
t: typeof FORM_STRINGS = FORM_STRINGS,
|
|
32
|
+
) {
|
|
26
33
|
const value = Array.isArray(raw) ? raw.join(",") : (raw ?? "");
|
|
27
34
|
const empty = value.trim() === "";
|
|
28
35
|
if (rule.required && empty)
|
|
29
|
-
return typeof rule.required === "string" ? rule.required :
|
|
36
|
+
return typeof rule.required === "string" ? rule.required : t.fieldRequired;
|
|
30
37
|
if (empty) return undefined;
|
|
31
|
-
if (rule.email && !EMAIL.test(value)) return rule.message ||
|
|
38
|
+
if (rule.email && !EMAIL.test(value)) return rule.message || t.invalidEmail;
|
|
32
39
|
if (rule.minLength != null && value.length < rule.minLength)
|
|
33
|
-
return rule.message ||
|
|
40
|
+
return rule.message || t.minLength(rule.minLength);
|
|
34
41
|
if (rule.maxLength != null && value.length > rule.maxLength)
|
|
35
|
-
return rule.message ||
|
|
42
|
+
return rule.message || t.maxLength(rule.maxLength);
|
|
36
43
|
const n = Number(value.replace(/[^\d.-]/g, ""));
|
|
37
|
-
if (rule.min != null && n < rule.min) return rule.message ||
|
|
38
|
-
if (rule.max != null && n > rule.max) return rule.message ||
|
|
39
|
-
if (rule.pattern && !rule.pattern.test(value)) return rule.message ||
|
|
44
|
+
if (rule.min != null && n < rule.min) return rule.message || t.minValue(rule.min);
|
|
45
|
+
if (rule.max != null && n > rule.max) return rule.message || t.maxValue(rule.max);
|
|
46
|
+
if (rule.pattern && !rule.pattern.test(value)) return rule.message || t.invalidFormat;
|
|
40
47
|
return rule.validate?.(value, values) || undefined;
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -51,6 +58,7 @@ function read(form: HTMLFormElement): FormValues {
|
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
export function Form(p: FormProps) {
|
|
61
|
+
const t = useStrings(FORM_STRINGS);
|
|
54
62
|
const ref = React.useRef<HTMLFormElement>(null);
|
|
55
63
|
const [errors, setErrors] = React.useState<Record<string, string>>({});
|
|
56
64
|
const [tried, setTried] = React.useState(false);
|
|
@@ -59,7 +67,7 @@ export function Form(p: FormProps) {
|
|
|
59
67
|
const next: Record<string, string> = only ? { ...errors } : {};
|
|
60
68
|
for (const [name, rule] of Object.entries(rules)) {
|
|
61
69
|
if (only && name !== only) continue;
|
|
62
|
-
const msg = checkRule(rule, values[name], values);
|
|
70
|
+
const msg = checkRule(rule, values[name], values, t);
|
|
63
71
|
if (msg) next[name] = msg;
|
|
64
72
|
else delete next[name];
|
|
65
73
|
}
|