@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
@@ -2,7 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { cx } from "../cx.js";
5
- import { formatMoney } from "../format.js";
5
+ import { useFormat, useStrings } from "../context.js";
6
+ import { AGENTS_STRINGS } from "../strings.js";
6
7
  import { Icon } from "../Icon.js";
7
8
  import { useControlled } from "../hooks.js";
8
9
  import { AgentMark, TypingDots } from "./chat.js";
@@ -38,6 +39,7 @@ import type {
38
39
 
39
40
  /* ── Agents ── */
40
41
  export function AgentCard(p: AgentCardProps) {
42
+ const t = useStrings(AGENTS_STRINGS);
41
43
  return (
42
44
  <div
43
45
  className={cx("tf-agentcard", p.onClick && "click")}
@@ -53,7 +55,7 @@ export function AgentCard(p: AgentCardProps) {
53
55
  {p.onChat && (
54
56
  <IconButton
55
57
  icon="mail"
56
- label={`Chat with ${p.name}`}
58
+ label={t.chatWith(p.name)}
57
59
  size="sm"
58
60
  onClick={(e) => {
59
61
  e.stopPropagation();
@@ -67,9 +69,9 @@ export function AgentCard(p: AgentCardProps) {
67
69
  dense
68
70
  items={
69
71
  [
70
- ["Model", p.model],
71
- ["Cost today", p.cost, true],
72
- ["Last action", p.last],
72
+ [t.model, p.model],
73
+ [t.costToday, p.cost, true],
74
+ [t.lastAction, p.last],
73
75
  ].filter((r) => r[1] != null) as KeyValueItem[]
74
76
  }
75
77
  />
@@ -104,6 +106,7 @@ export function AgentDetails(p: AgentDetailsProps) {
104
106
  );
105
107
  }
106
108
  export function ChatList(p: ChatListProps) {
109
+ const t = useStrings(AGENTS_STRINGS);
107
110
  const q = React.useState("");
108
111
  const groups = (p.groups || [])
109
112
  .map((g) =>
@@ -118,7 +121,7 @@ export function ChatList(p: ChatListProps) {
118
121
  return (
119
122
  <div className="tf-chatlist">
120
123
  <SearchField
121
- placeholder={p.searchPlaceholder || "Search chats"}
124
+ placeholder={p.searchPlaceholder || t.searchChats}
122
125
  value={q[0]}
123
126
  onChange={(e) => {
124
127
  q[1](e.target.value);
@@ -134,7 +137,7 @@ export function ChatList(p: ChatListProps) {
134
137
  padding: 12,
135
138
  }}
136
139
  >
137
- {"No chats match"}
140
+ {t.noChatsMatch}
138
141
  </p>
139
142
  ) : (
140
143
  groups.map((g) => (
@@ -191,10 +194,11 @@ export function ChatWorkspace(p: ChatWorkspaceProps) {
191
194
  );
192
195
  }
193
196
  export function SearchHits(p: SearchHitsProps) {
197
+ const t = useStrings(AGENTS_STRINGS);
194
198
  return (
195
199
  <div className="tf-hits">
196
200
  {(p.items || []).length === 0 ? (
197
- <EmptyState icon="search" title="Nothing found" text={p.emptyText} />
201
+ <EmptyState icon="search" title={t.nothingFound} text={p.emptyText} />
198
202
  ) : (
199
203
  p.items.map((x) => {
200
204
  const parts = String(x.text).split(
@@ -225,14 +229,16 @@ export function SearchHits(p: SearchHitsProps) {
225
229
  );
226
230
  }
227
231
  export function ThinkingLine(p: ThinkingLineProps) {
232
+ const t = useStrings(AGENTS_STRINGS);
228
233
  return (
229
234
  <div className="tf-thinking" role="status">
230
235
  <TypingDots />
231
- <span>{p.text || "Thinking…"}</span>
236
+ <span>{p.text || t.thinking}</span>
232
237
  </div>
233
238
  );
234
239
  }
235
240
  export function FeedbackRating(p: FeedbackRatingProps) {
241
+ const t = useStrings(AGENTS_STRINGS);
236
242
  const v = useControlled<"up" | "down" | null>(p.value, null, (x) => {
237
243
  if (x) p.onRate?.(x);
238
244
  }),
@@ -242,7 +248,7 @@ export function FeedbackRating(p: FeedbackRatingProps) {
242
248
  return (
243
249
  <span className="tf-feedback-done">
244
250
  <Icon name="check" size={13} />
245
- {p.thanks || "Thanks, noted"}
251
+ {p.thanks || t.thanksNoted}
246
252
  </span>
247
253
  );
248
254
  return (
@@ -259,7 +265,7 @@ export function FeedbackRating(p: FeedbackRatingProps) {
259
265
  type="button"
260
266
  className={cx("tf-msg-act", v[0] === "up" && "on")}
261
267
  aria-pressed={v[0] === "up"}
262
- aria-label="Good"
268
+ aria-label={t.good}
263
269
  onClick={() => {
264
270
  v[1]("up");
265
271
  if (!p.askOnUp) {
@@ -274,7 +280,7 @@ export function FeedbackRating(p: FeedbackRatingProps) {
274
280
  type="button"
275
281
  className={cx("tf-msg-act", v[0] === "down" && "on")}
276
282
  aria-pressed={v[0] === "down"}
277
- aria-label="Not good"
283
+ aria-label={t.notGood}
278
284
  onClick={() => {
279
285
  v[1]("down");
280
286
  }}
@@ -294,14 +300,14 @@ export function FeedbackRating(p: FeedbackRatingProps) {
294
300
  <input
295
301
  className="tf-input"
296
302
  autoFocus
297
- placeholder={p.placeholder || "What should be different?"}
303
+ placeholder={p.placeholder || t.whatShouldBeDifferent}
298
304
  value={c[0]}
299
305
  onChange={(e) => {
300
306
  c[1](e.target.value);
301
307
  }}
302
308
  />
303
309
  <Button type="submit" size="sm">
304
- {"Send"}
310
+ {t.send}
305
311
  </Button>
306
312
  </form>
307
313
  )}
@@ -341,6 +347,7 @@ export function ActivityFeed(p: ActivityFeedProps) {
341
347
  );
342
348
  }
343
349
  export function DailyBriefing(p: DailyBriefingProps) {
350
+ const t = useStrings(AGENTS_STRINGS);
344
351
  return (
345
352
  <div className="tf-briefing">
346
353
  <div className="tf-briefing-head">
@@ -364,7 +371,7 @@ export function DailyBriefing(p: DailyBriefingProps) {
364
371
  padding: 0,
365
372
  }}
366
373
  >
367
- {p.attentionTitle || "Needs you"}
374
+ {p.attentionTitle || t.needsYou}
368
375
  </span>
369
376
  {p.attention.map((a, i) => (
370
377
  <button key={i} type="button" className="tf-briefing-item" onClick={a.onClick}>
@@ -388,7 +395,7 @@ export function DailyBriefing(p: DailyBriefingProps) {
388
395
  padding: 0,
389
396
  }}
390
397
  >
391
- {p.suggestionsTitle || "Suggestions"}
398
+ {p.suggestionsTitle || t.suggestions}
392
399
  </span>
393
400
  <ul className="tf-briefing-sugg">
394
401
  {p.suggestions.map((s, i) => (
@@ -401,24 +408,25 @@ export function DailyBriefing(p: DailyBriefingProps) {
401
408
  );
402
409
  }
403
410
  export function BudgetStop(p: BudgetStopProps) {
411
+ const t = useStrings(AGENTS_STRINGS);
412
+ const { formatMoney } = useFormat();
404
413
  return (
405
414
  <div className="tf-budgetstop">
406
415
  <Alert
407
416
  tone="danger"
408
- title={p.title || "Budget used up · agents paused"}
417
+ title={p.title || t.budgetUsedUp}
409
418
  action={
410
419
  p.onRaise && (
411
420
  <Button size="sm" variant="secondary" onClick={p.onRaise}>
412
- {p.actionLabel || "Raise budget"}
421
+ {p.actionLabel || t.raiseBudget}
413
422
  </Button>
414
423
  )
415
424
  }
416
425
  >
417
- {p.text ||
418
- "Everything is kept. Agents continue where they stopped once the budget allows it."}
426
+ {p.text || t.budgetText}
419
427
  </Alert>
420
428
  <Meter
421
- label="Spent this month"
429
+ label={t.spentThisMonth}
422
430
  value={p.spent}
423
431
  max={p.budget}
424
432
  text={formatMoney(p.spent) + " / " + formatMoney(p.budget)}
@@ -428,8 +436,8 @@ export function BudgetStop(p: BudgetStopProps) {
428
436
  <KeyValue
429
437
  dense
430
438
  items={[
431
- ["Paused", p.paused],
432
- ["Still running", p.running || "Nothing"],
439
+ [t.paused, p.paused],
440
+ [t.stillRunning, p.running || t.nothing],
433
441
  ]}
434
442
  />
435
443
  )}
@@ -437,6 +445,7 @@ export function BudgetStop(p: BudgetStopProps) {
437
445
  );
438
446
  }
439
447
  export function MemoryViewer(p: MemoryViewerProps) {
448
+ const t = useStrings(AGENTS_STRINGS);
440
449
  const q = React.useState(""),
441
450
  scope = React.useState<string | null>(null);
442
451
  const scopes: string[] = [];
@@ -452,7 +461,7 @@ export function MemoryViewer(p: MemoryViewerProps) {
452
461
  <div className="tf-memory">
453
462
  <div className="tf-filterbar">
454
463
  <SearchField
455
- placeholder="Search memory"
464
+ placeholder={t.searchMemory}
456
465
  value={q[0]}
457
466
  onChange={(e) => {
458
467
  q[1](e.target.value);
@@ -475,7 +484,7 @@ export function MemoryViewer(p: MemoryViewerProps) {
475
484
  ))}
476
485
  </div>
477
486
  {list.length === 0 ? (
478
- <p className="tf-card-desc">{"Nothing remembered here yet."}</p>
487
+ <p className="tf-card-desc">{t.nothingRemembered}</p>
479
488
  ) : (
480
489
  list.map((m) => (
481
490
  <div key={m.id} className="tf-memitem">
@@ -497,7 +506,7 @@ export function MemoryViewer(p: MemoryViewerProps) {
497
506
  {p.onEdit && (
498
507
  <IconButton
499
508
  icon="edit"
500
- label="Edit"
509
+ label={t.edit}
501
510
  size="sm"
502
511
  variant="ghost"
503
512
  onClick={() => {
@@ -508,7 +517,7 @@ export function MemoryViewer(p: MemoryViewerProps) {
508
517
  {p.onDelete && (
509
518
  <IconButton
510
519
  icon="trash"
511
- label="Forget"
520
+ label={t.forget}
512
521
  size="sm"
513
522
  variant="ghost"
514
523
  onClick={() => {
@@ -525,6 +534,7 @@ export function MemoryViewer(p: MemoryViewerProps) {
525
534
  );
526
535
  }
527
536
  export function OverrideDiff(p: OverrideDiffProps) {
537
+ const t = useStrings(AGENTS_STRINGS);
528
538
  const a = String(p.before || "").split("\n"),
529
539
  b = String(p.after || "").split("\n"),
530
540
  rows = [];
@@ -561,12 +571,12 @@ export function OverrideDiff(p: OverrideDiffProps) {
561
571
  >
562
572
  {p.onDiscard && (
563
573
  <Button size="sm" variant="ghost" onClick={p.onDiscard}>
564
- {p.discardLabel || "Discard override"}
574
+ {p.discardLabel || t.discardOverride}
565
575
  </Button>
566
576
  )}
567
577
  {p.onPromote && (
568
578
  <Button size="sm" variant="soft" icon="branch" onClick={p.onPromote}>
569
- {p.promoteLabel || "Move to code"}
579
+ {p.promoteLabel || t.moveToCode}
570
580
  </Button>
571
581
  )}
572
582
  </span>
@@ -590,6 +600,7 @@ const TRIGGER_ICON: Record<string, IconName> = {
590
600
  mail: "mail",
591
601
  };
592
602
  export function TriggerList(p: TriggerListProps) {
603
+ const strings = useStrings(AGENTS_STRINGS);
593
604
  return (
594
605
  <div className="tf-triggers">
595
606
  {(p.items || []).map((t) => (
@@ -616,11 +627,11 @@ export function TriggerList(p: TriggerListProps) {
616
627
  />
617
628
  )}
618
629
  {p.onRun && (
619
- <Tooltip label="Run now">
630
+ <Tooltip label={strings.runNow}>
620
631
  <button
621
632
  type="button"
622
633
  className="tf-iconbtn tf-iconbtn-soft tf-iconbtn-sm"
623
- aria-label="Run now"
634
+ aria-label={strings.runNow}
624
635
  onClick={() => {
625
636
  p.onRun?.(t);
626
637
  }}
@@ -635,11 +646,12 @@ export function TriggerList(p: TriggerListProps) {
635
646
  );
636
647
  }
637
648
  export function ScheduleCalendar(p: ScheduleCalendarProps) {
649
+ const { locale } = useFormat();
638
650
  const from = p.from != null ? p.from : 6,
639
651
  to = p.to != null ? p.to : 22,
640
652
  hours = [];
641
653
  for (let i = from; i < to; i++) hours.push(i);
642
- const days = p.days || weekdays(),
654
+ const days = p.days || weekdays(locale),
643
655
  rowH = 32;
644
656
  return (
645
657
  <div
@@ -729,6 +741,7 @@ export function OrgChart(p: OrgChartProps) {
729
741
  );
730
742
  }
731
743
  export function VoiceMode(p: VoiceModeProps) {
744
+ const t = useStrings(AGENTS_STRINGS);
732
745
  const st = p.state || "listening",
733
746
  bars = [];
734
747
  for (let i = 0; i < 24; i++) bars.push(i);
@@ -737,13 +750,14 @@ export function VoiceMode(p: VoiceModeProps) {
737
750
  <AgentMark tint={p.tint || "lilac"} size={72} live={st !== "idle"} />
738
751
  <b className="tf-voice-name">{p.name}</b>
739
752
  <span className="tf-voice-state">
740
- {p.stateLabel || st === "listening"
741
- ? "Listening…"
742
- : st === "thinking"
743
- ? "Thinking…"
744
- : st === "speaking"
745
- ? "Speaking…"
746
- : "Paused"}
753
+ {p.stateLabel ||
754
+ (st === "listening"
755
+ ? t.listening
756
+ : st === "thinking"
757
+ ? t.thinking
758
+ : st === "speaking"
759
+ ? t.speaking
760
+ : t.paused)}
747
761
  </span>
748
762
  <div className="tf-voice-wave" aria-hidden>
749
763
  {bars.map((b) => (
@@ -766,7 +780,7 @@ export function VoiceMode(p: VoiceModeProps) {
766
780
  {p.onMute && (
767
781
  <IconButton
768
782
  icon={p.muted ? "x" : "user"}
769
- label={p.muted ? "Unmute" : "Mute"}
783
+ label={p.muted ? t.unmute : t.mute}
770
784
  variant="soft"
771
785
  size="lg"
772
786
  onClick={p.onMute}
@@ -777,7 +791,7 @@ export function VoiceMode(p: VoiceModeProps) {
777
791
  type="button"
778
792
  className="tf-voice-end"
779
793
  onClick={p.onEnd}
780
- aria-label="End conversation"
794
+ aria-label={t.endConversation}
781
795
  >
782
796
  <Icon name="x" size={20} />
783
797
  </button>
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
+ import { useStrings } from "../context.js";
4
+ import { APP_CHROME_STRINGS } from "../strings.js";
3
5
  import { cx } from "../cx.js";
4
6
  import { Icon } from "../Icon.js";
5
7
  import { Badge, TintIcon } from "./foundation.js";
@@ -15,8 +17,9 @@ import type {
15
17
 
16
18
  /* ── App chrome ── */
17
19
  export function MobileTabBar(p: MobileTabBarProps) {
20
+ const t = useStrings(APP_CHROME_STRINGS);
18
21
  return (
19
- <nav className="tf-tabbar" aria-label={p.label || "Main"}>
22
+ <nav className="tf-tabbar" aria-label={p.label || t.mainNavigation}>
20
23
  {(p.items || []).map((it) => {
21
24
  const on = it.id === p.active;
22
25
  return (
@@ -123,6 +126,7 @@ export function ConfigPRSummary(p: ConfigPRSummaryProps) {
123
126
  }
124
127
 
125
128
  export function ModuleRegistry(p: ModuleRegistryProps) {
129
+ const t = useStrings(APP_CHROME_STRINGS);
126
130
  return (
127
131
  <div className="tf-modules">
128
132
  {(p.items || []).map((m) => (
@@ -147,7 +151,7 @@ export function ModuleRegistry(p: ModuleRegistryProps) {
147
151
  </span>
148
152
  )}
149
153
  {m.core ? (
150
- <Tooltip label={p.coreLabel || "Core module, always on"}>
154
+ <Tooltip label={p.coreLabel || t.coreModule}>
151
155
  <span className="tf-module-lock" tabIndex={0}>
152
156
  <Icon name="lock" size={14} />
153
157
  </span>
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
3
  import { cx } from "../cx.js";
4
+ import { useStrings } from "../context.js";
5
+ import { APPROVALS_STRINGS } from "../strings.js";
4
6
  import { Icon } from "../Icon.js";
5
7
  import { StatusShape } from "./status.js";
6
8
  import { Badge, Button } from "./foundation.js";
@@ -16,34 +18,35 @@ import type {
16
18
  } from "./types.js";
17
19
 
18
20
  /* ── Approval A–D ── */
19
- const FAMILY: Record<RiskFamily, [IconName, string]> = {
20
- money: ["card", "Money"],
21
- infra: ["server", "Infrastructure"],
22
- comms: ["mail", "Communication"],
23
- marketing: ["trend", "Marketing"],
24
- apps: ["code", "Own apps"],
25
- legal: ["book", "Legal"],
21
+ const FAMILY: Record<RiskFamily, IconName> = {
22
+ money: "card",
23
+ infra: "server",
24
+ comms: "mail",
25
+ marketing: "trend",
26
+ apps: "code",
27
+ legal: "book",
26
28
  };
27
29
 
28
30
  export function ApprovalCard(p: ApprovalCardProps) {
31
+ const t = useStrings(APPROVALS_STRINGS);
29
32
  const opts: ApprovalOption[] = p.options || [
30
33
  {
31
34
  key: "A",
32
- label: p.approveLabel || "Yes, run it as shown",
35
+ label: p.approveLabel || t.approveAsShown,
33
36
  },
34
37
  {
35
38
  key: "B",
36
- label: "Yes, with a change",
37
- input: "What should change?",
39
+ label: t.approveWithChange,
40
+ input: t.whatShouldChange,
38
41
  },
39
42
  {
40
43
  key: "C",
41
- label: "No, discard",
44
+ label: t.discard,
42
45
  },
43
46
  {
44
47
  key: "D",
45
- label: "Different instruction",
46
- input: "What should happen instead?",
48
+ label: t.differentInstruction,
49
+ input: t.whatInstead,
47
50
  },
48
51
  ];
49
52
  const open = React.useState<ApprovalKey | null>(null),
@@ -115,7 +118,7 @@ export function ApprovalCard(p: ApprovalCardProps) {
115
118
  >
116
119
  <div className="tf-approval-top">
117
120
  <StatusShape status="signal" />
118
- <b>{p.heading || "Approval needed"}</b>
121
+ <b>{p.heading || t.approvalNeeded}</b>
119
122
  {(p.agent || p.project) && (
120
123
  <span className="tf-approval-who">
121
124
  {[p.agent, p.project].filter(Boolean).join(" · ")}
@@ -123,8 +126,8 @@ export function ApprovalCard(p: ApprovalCardProps) {
123
126
  )}
124
127
  {fam && (
125
128
  <Badge tone="neutral" dot={false} className="tf-family">
126
- <Icon name={fam[0]} size={12} />
127
- {p.familyLabel || fam[1]}
129
+ <Icon name={fam} size={12} />
130
+ {p.familyLabel || (p.family && t.riskFamily[p.family])}
128
131
  </Badge>
129
132
  )}
130
133
  {p.age && <span className="tf-approval-age">{p.age}</span>}
@@ -178,7 +181,7 @@ export function ApprovalCard(p: ApprovalCardProps) {
178
181
  open[1](null);
179
182
  }}
180
183
  >
181
- {"Cancel"}
184
+ {t.cancel}
182
185
  </Button>
183
186
  <Button
184
187
  size="sm"
@@ -187,7 +190,7 @@ export function ApprovalCard(p: ApprovalCardProps) {
187
190
  decide(o);
188
191
  }}
189
192
  >
190
- {o.key + " · " + "Send"}
193
+ {o.key + " · " + t.send}
191
194
  </Button>
192
195
  </div>
193
196
  </div>
@@ -212,12 +215,13 @@ export function ApprovalCard(p: ApprovalCardProps) {
212
215
  }
213
216
 
214
217
  export function ApprovalInbox(p: ApprovalInboxProps) {
218
+ const t = useStrings(APPROVALS_STRINGS);
215
219
  const items = (p.items || []).slice().sort((a, b) => (b.ageMin || 0) - (a.ageMin || 0));
216
220
  return (
217
221
  <div
218
222
  className="tf-inbox"
219
223
  role="listbox"
220
- aria-label={p.label || "Approvals"}
224
+ aria-label={p.label || t.approvals}
221
225
  onKeyDown={(e) => {
222
226
  const i = items.findIndex((x) => x.id === p.selected);
223
227
  if (e.key === "j" || e.key === "ArrowDown") {
@@ -236,7 +240,7 @@ export function ApprovalInbox(p: ApprovalInboxProps) {
236
240
  <EmptyState
237
241
  icon="check"
238
242
  tint="mint"
239
- title={p.emptyTitle || "Nothing waiting for you"}
243
+ title={p.emptyTitle || t.nothingWaiting}
240
244
  text={p.emptyText}
241
245
  />
242
246
  ) : (
@@ -260,8 +264,8 @@ export function ApprovalInbox(p: ApprovalInboxProps) {
260
264
  </span>
261
265
  {fam && (
262
266
  <Badge tone="neutral" dot={false} className="tf-family">
263
- <Icon name={fam[0]} size={12} />
264
- {x.familyLabel || fam[1]}
267
+ <Icon name={fam} size={12} />
268
+ {x.familyLabel || (x.family && t.riskFamily[x.family])}
265
269
  </Badge>
266
270
  )}
267
271
  <span className={cx("tf-inbox-age", x.urgent && "warn")}>
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
+ import { useStrings } from "../context.js";
5
+ import { BUILDING_BLOCKS_STRINGS } from "../strings.js";
4
6
  import { cx } from "../cx.js";
5
7
  import { useControlled, useOutside } from "../hooks.js";
6
8
  import { Icon } from "../Icon.js";
@@ -55,11 +57,12 @@ export function KeyValue(p: KeyValueProps) {
55
57
  );
56
58
  }
57
59
  export function Spinner(p: SpinnerProps) {
60
+ const t = useStrings(BUILDING_BLOCKS_STRINGS);
58
61
  return (
59
62
  <span
60
63
  className={cx("tf-spinner", p.size === "lg" && "lg")}
61
64
  role="status"
62
- aria-label={p.label || "Loading"}
65
+ aria-label={p.label || t.loading}
63
66
  />
64
67
  );
65
68
  }
@@ -339,6 +342,7 @@ export function InlineEdit(p: InlineEditProps) {
339
342
  * jump to the limits, double-click resets.
340
343
  */
341
344
  export function ResizablePanels(p: ResizablePanelsProps) {
345
+ const t = useStrings(BUILDING_BLOCKS_STRINGS);
342
346
  const n = (p.panels || []).length;
343
347
  const vertical = p.orientation === "vertical";
344
348
  const min = p.min ?? 10;
@@ -411,7 +415,7 @@ export function ResizablePanels(p: ResizablePanelsProps) {
411
415
  className="tf-panels-handle"
412
416
  role="separator"
413
417
  aria-orientation={vertical ? "horizontal" : "vertical"}
414
- aria-label={`Resize ${p.labels?.[i] ?? `panel ${i + 1}`}`}
418
+ aria-label={t.resizePanel(p.labels?.[i], i + 1)}
415
419
  aria-valuenow={Math.round(sizes[i] ?? 0)}
416
420
  aria-valuemin={min}
417
421
  aria-valuemax={Math.round((sizes[i] ?? 0) + (sizes[i + 1] ?? 0) - min)}