@twentyfirstmedia/ui-core 0.2.1 → 0.3.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.
Files changed (147) hide show
  1. package/dist/components/admin.d.ts.map +1 -1
  2. package/dist/components/admin.js +95 -71
  3. package/dist/components/admin.js.map +1 -1
  4. package/dist/components/agents.d.ts.map +1 -1
  5. package/dist/components/agents.js +49 -35
  6. package/dist/components/agents.js.map +1 -1
  7. package/dist/components/app-chrome.d.ts.map +1 -1
  8. package/dist/components/app-chrome.js +6 -2
  9. package/dist/components/app-chrome.js.map +1 -1
  10. package/dist/components/approvals.d.ts.map +1 -1
  11. package/dist/components/approvals.js +22 -18
  12. package/dist/components/approvals.js.map +1 -1
  13. package/dist/components/building-blocks.d.ts.map +1 -1
  14. package/dist/components/building-blocks.js +6 -2
  15. package/dist/components/building-blocks.js.map +1 -1
  16. package/dist/components/charts.d.ts.map +1 -1
  17. package/dist/components/charts.js +20 -10
  18. package/dist/components/charts.js.map +1 -1
  19. package/dist/components/chat.d.ts.map +1 -1
  20. package/dist/components/chat.js +31 -19
  21. package/dist/components/chat.js.map +1 -1
  22. package/dist/components/commerce.d.ts +2 -2
  23. package/dist/components/commerce.d.ts.map +1 -1
  24. package/dist/components/commerce.js +45 -31
  25. package/dist/components/commerce.js.map +1 -1
  26. package/dist/components/data-display.d.ts +5 -9
  27. package/dist/components/data-display.d.ts.map +1 -1
  28. package/dist/components/data-display.js +34 -35
  29. package/dist/components/data-display.js.map +1 -1
  30. package/dist/components/data-table.d.ts.map +1 -1
  31. package/dist/components/data-table.js +15 -10
  32. package/dist/components/data-table.js.map +1 -1
  33. package/dist/components/developer.d.ts.map +1 -1
  34. package/dist/components/developer.js +20 -11
  35. package/dist/components/developer.js.map +1 -1
  36. package/dist/components/feedback.d.ts.map +1 -1
  37. package/dist/components/feedback.js +12 -6
  38. package/dist/components/feedback.js.map +1 -1
  39. package/dist/components/form.d.ts +2 -1
  40. package/dist/components/form.d.ts.map +1 -1
  41. package/dist/components/form.js +12 -9
  42. package/dist/components/form.js.map +1 -1
  43. package/dist/components/forms.d.ts +2 -1
  44. package/dist/components/forms.d.ts.map +1 -1
  45. package/dist/components/forms.js +92 -42
  46. package/dist/components/forms.js.map +1 -1
  47. package/dist/components/foundation.d.ts +5 -2
  48. package/dist/components/foundation.d.ts.map +1 -1
  49. package/dist/components/foundation.js +15 -12
  50. package/dist/components/foundation.js.map +1 -1
  51. package/dist/components/inputs.d.ts.map +1 -1
  52. package/dist/components/inputs.js +3 -1
  53. package/dist/components/inputs.js.map +1 -1
  54. package/dist/components/mail.d.ts.map +1 -1
  55. package/dist/components/mail.js +15 -10
  56. package/dist/components/mail.js.map +1 -1
  57. package/dist/components/media.d.ts.map +1 -1
  58. package/dist/components/media.js +39 -18
  59. package/dist/components/media.js.map +1 -1
  60. package/dist/components/navigation.d.ts.map +1 -1
  61. package/dist/components/navigation.js +21 -9
  62. package/dist/components/navigation.js.map +1 -1
  63. package/dist/components/operations.d.ts.map +1 -1
  64. package/dist/components/operations.js +37 -27
  65. package/dist/components/operations.js.map +1 -1
  66. package/dist/components/previews.d.ts.map +1 -1
  67. package/dist/components/previews.js +14 -10
  68. package/dist/components/previews.js.map +1 -1
  69. package/dist/components/primitives.d.ts.map +1 -1
  70. package/dist/components/primitives.js +4 -1
  71. package/dist/components/primitives.js.map +1 -1
  72. package/dist/components/real-estate.d.ts.map +1 -1
  73. package/dist/components/real-estate.js +43 -28
  74. package/dist/components/real-estate.js.map +1 -1
  75. package/dist/components/rich-text-editor.d.ts.map +1 -1
  76. package/dist/components/rich-text-editor.js +18 -15
  77. package/dist/components/rich-text-editor.js.map +1 -1
  78. package/dist/components/select-menu.d.ts.map +1 -1
  79. package/dist/components/select-menu.js +4 -1
  80. package/dist/components/select-menu.js.map +1 -1
  81. package/dist/components/shell.d.ts.map +1 -1
  82. package/dist/components/shell.js +8 -3
  83. package/dist/components/shell.js.map +1 -1
  84. package/dist/components/site.d.ts.map +1 -1
  85. package/dist/components/site.js +17 -13
  86. package/dist/components/site.js.map +1 -1
  87. package/dist/components/types.d.ts +102 -3
  88. package/dist/components/types.d.ts.map +1 -1
  89. package/dist/components/workspace.d.ts.map +1 -1
  90. package/dist/components/workspace.js +50 -34
  91. package/dist/components/workspace.js.map +1 -1
  92. package/dist/context.d.ts +39 -0
  93. package/dist/context.d.ts.map +1 -0
  94. package/dist/context.js +76 -0
  95. package/dist/context.js.map +1 -0
  96. package/dist/format.d.ts +33 -6
  97. package/dist/format.d.ts.map +1 -1
  98. package/dist/format.js +75 -10
  99. package/dist/format.js.map +1 -1
  100. package/dist/index.d.ts +3 -2
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +3 -2
  103. package/dist/index.js.map +1 -1
  104. package/dist/provider.d.ts +18 -11
  105. package/dist/provider.d.ts.map +1 -1
  106. package/dist/provider.js +11 -18
  107. package/dist/provider.js.map +1 -1
  108. package/dist/strings.d.ts +503 -0
  109. package/dist/strings.d.ts.map +1 -0
  110. package/dist/strings.js +537 -0
  111. package/dist/strings.js.map +1 -0
  112. package/dist/themes.css +4 -0
  113. package/package.json +1 -1
  114. package/src/components/admin.tsx +117 -93
  115. package/src/components/agents.tsx +55 -41
  116. package/src/components/app-chrome.tsx +6 -2
  117. package/src/components/approvals.tsx +26 -22
  118. package/src/components/building-blocks.tsx +6 -2
  119. package/src/components/charts.tsx +21 -11
  120. package/src/components/chat.tsx +45 -35
  121. package/src/components/commerce.tsx +61 -53
  122. package/src/components/data-display.tsx +38 -45
  123. package/src/components/data-table.tsx +20 -15
  124. package/src/components/developer.tsx +26 -15
  125. package/src/components/feedback.tsx +19 -7
  126. package/src/components/form.tsx +17 -9
  127. package/src/components/forms.tsx +131 -53
  128. package/src/components/foundation.tsx +23 -36
  129. package/src/components/inputs.tsx +4 -2
  130. package/src/components/mail.tsx +16 -11
  131. package/src/components/media.tsx +64 -21
  132. package/src/components/navigation.tsx +25 -13
  133. package/src/components/operations.tsx +40 -32
  134. package/src/components/previews.tsx +17 -15
  135. package/src/components/primitives.tsx +8 -5
  136. package/src/components/real-estate.tsx +55 -35
  137. package/src/components/rich-text-editor.tsx +20 -17
  138. package/src/components/select-menu.tsx +5 -2
  139. package/src/components/shell.tsx +8 -3
  140. package/src/components/site.tsx +18 -14
  141. package/src/components/types.ts +111 -3
  142. package/src/components/workspace.tsx +79 -60
  143. package/src/context.ts +112 -0
  144. package/src/format.ts +108 -15
  145. package/src/index.ts +43 -0
  146. package/src/provider.tsx +36 -40
  147. 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 { formatMoney, formatNumber, formatPercent } from "../format.js";
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] ? "Hide" : "Reveal"}>
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] ? "Hide" : "Reveal"}
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] ? "Copied" : "Copy"}>
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="Copy"
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
- {"Rotate"}
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="VAT rate"
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
- {`Gross ${formatMoney(v[0] * (1 + Number(p.vat) / 100), p.currency, {
235
- minimumFractionDigits: 2,
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 || "Keyboard shortcuts"}
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 || "Keyboard shortcuts"}</h2>
291
- <IconButton icon="x" label="Close" size="sm" onClick={p.onClose} />
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 || `${days.length - hov[0] - 1} days ago`) +
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 || `Last ${days.length} days`}
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="Close" size="sm" onClick={p.onClose} />
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 icon="x" label="Dismiss" size="sm" variant="ghost" onClick={p.onDismiss} />
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
- Type <b className="tf-mono">{p.confirmText}</b> to confirm
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 || "Cancel"}
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 || "Confirm"}
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
- {"Undo"}
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="Dismiss"
429
+ aria-label={strings.dismiss}
418
430
  onClick={() => {
419
431
  toast.dismiss(t.id);
420
432
  }}
@@ -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(rule: FormRule, raw: string | string[] | undefined, values: FormValues) {
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 : "This field is 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 || "Enter a valid email address";
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 || `Use at least ${rule.minLength} characters`;
40
+ return rule.message || t.minLength(rule.minLength);
34
41
  if (rule.maxLength != null && value.length > rule.maxLength)
35
- return rule.message || `Use at most ${rule.maxLength} characters`;
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 || `Use ${rule.min} or more`;
38
- if (rule.max != null && n > rule.max) return rule.message || `Use ${rule.max} or less`;
39
- if (rule.pattern && !rule.pattern.test(value)) return rule.message || "Use the expected format";
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
  }