@kahitsan/ksui 0.32.0 → 0.34.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.
Files changed (65) hide show
  1. package/package.json +4 -1
  2. package/src/components/base/AccountAvatar.tsx +1 -1
  3. package/src/components/base/AddAttachmentTile.tsx +4 -4
  4. package/src/components/base/BadgeSelect.tsx +10 -10
  5. package/src/components/base/Button.tsx +70 -38
  6. package/src/components/base/CameraCapture.tsx +8 -8
  7. package/src/components/base/ChartLegend.tsx +3 -3
  8. package/src/components/base/CopyButton.tsx +1 -1
  9. package/src/components/base/DataTable.tsx +68 -72
  10. package/src/components/base/DatePicker.tsx +77 -82
  11. package/src/components/base/DateTile.tsx +7 -7
  12. package/src/components/base/DetailRow.tsx +2 -2
  13. package/src/components/base/Dropdown.tsx +15 -15
  14. package/src/components/base/ExistingAttachmentTile.tsx +6 -6
  15. package/src/components/base/EyebrowBadge.tsx +15 -15
  16. package/src/components/base/FormErrorBanner.tsx +1 -1
  17. package/src/components/base/FormField.tsx +2 -2
  18. package/src/components/base/ImageCropper.tsx +19 -19
  19. package/src/components/base/ImageViewer.tsx +7 -11
  20. package/src/components/base/KpiCard.tsx +7 -7
  21. package/src/components/base/Modal.tsx +10 -14
  22. package/src/components/base/ProgressBar.tsx +63 -59
  23. package/src/components/base/RadioCardGroup.tsx +13 -6
  24. package/src/components/base/SectionHeading.tsx +4 -4
  25. package/src/components/base/SegmentedFilter.tsx +60 -4
  26. package/src/components/base/SocialLinksBar.tsx +3 -15
  27. package/src/components/base/StatusIndicator.tsx +11 -11
  28. package/src/components/base/StatusPill.tsx +15 -15
  29. package/src/components/base/TagPill.tsx +1 -1
  30. package/src/components/base/ThemeToggle.tsx +12 -20
  31. package/src/components/base/Tooltip.tsx +2 -2
  32. package/src/components/composite/AccountRadioPicker.tsx +8 -4
  33. package/src/components/composite/ComboBox.tsx +51 -44
  34. package/src/components/composite/CustomRenderer.tsx +6 -13
  35. package/src/components/composite/FileField.tsx +10 -17
  36. package/src/components/composite/FlowGraph.tsx +29 -36
  37. package/src/components/composite/FlowRunner.tsx +11 -18
  38. package/src/components/composite/FormAdvancedSection.tsx +31 -31
  39. package/src/components/composite/LiveTimer.tsx +3 -3
  40. package/src/components/composite/MarkdownNotes.tsx +11 -11
  41. package/src/components/composite/MentionTextarea.tsx +10 -10
  42. package/src/components/composite/NotFound.tsx +7 -7
  43. package/src/components/composite/PaymentAccountPicker.tsx +19 -19
  44. package/src/components/composite/SalesBodyEditor.tsx +26 -26
  45. package/src/components/composite/SearchableSelect.tsx +14 -14
  46. package/src/components/composite/SecretReveal.tsx +5 -5
  47. package/src/components/composite/TransactionAccountFields.tsx +78 -0
  48. package/src/components/composite/TransactionForm.tsx +83 -169
  49. package/src/components/composite/TransactionPayableFields.tsx +97 -0
  50. package/src/components/composite/TransferAccountsPicker.tsx +20 -20
  51. package/src/components/composite/TransferFeeChip.tsx +2 -2
  52. package/src/components/composite/VoucherPicker.tsx +25 -25
  53. package/src/components/composite/resource/ResourceForm.tsx +1 -1
  54. package/src/components/composite/resource/ResourcePage.tsx +8 -8
  55. package/src/components/composite/resource/cells.tsx +6 -3
  56. package/src/components/composite/resource/form-spec.ts +141 -0
  57. package/src/components/composite/resource/route-adapter.ts +63 -0
  58. package/src/components/composite/resource/route-spec.test.ts +125 -0
  59. package/src/components/composite/resource/route-spec.ts +158 -0
  60. package/src/index.ts +8 -0
  61. package/src/utils/INPUT_CLASS.ts +6 -3
  62. package/src/utils/account-icons.ts +29 -7
  63. package/src/utils/highlight.tsx +5 -6
  64. package/src/utils/inject-css.ts +13 -0
  65. package/tailwind.js +11 -7
@@ -222,8 +222,8 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
222
222
  });
223
223
 
224
224
  return (
225
- <div class="rounded-lg border border-emerald-500/20 bg-emerald-500/5 p-3 space-y-3">
226
- <div class="flex items-center justify-between gap-2 text-[10px] uppercase tracking-widest text-emerald-400 font-semibold">
225
+ <div class="rounded-lg border border-[color-mix(in_srgb,var(--ks-success,#10b981)_20%,transparent)] bg-[color-mix(in_srgb,var(--ks-success,#10b981)_5%,transparent)] p-3 space-y-3">
226
+ <div class="flex items-center justify-between gap-2 text-[10px] uppercase tracking-widest text-[var(--ks-success-fg,#34d399)] font-semibold">
227
227
  <span class="flex items-center gap-1.5">
228
228
  <PackageIcon size={12} />
229
229
  Packages availed
@@ -231,7 +231,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
231
231
  <button
232
232
  type="button"
233
233
  onClick={() => setPickerOpen((v) => !v)}
234
- class="ks-interactive inline-flex items-center gap-1 rounded-md border border-emerald-500/30 bg-emerald-500/10 px-2 py-0.5 text-[10px] uppercase tracking-widest text-emerald-300 hover:bg-emerald-500/20"
234
+ class="ks-interactive inline-flex items-center gap-1 rounded-md border border-[color-mix(in_srgb,var(--ks-success,#10b981)_30%,transparent)] bg-[color-mix(in_srgb,var(--ks-success,#10b981)_10%,transparent)] px-2 py-0.5 text-[10px] uppercase tracking-widest text-[var(--ks-success-fg,#34d399)] hover:bg-[color-mix(in_srgb,var(--ks-success,#10b981)_20%,transparent)]"
235
235
  >
236
236
  <Plus size={10} /> Add package
237
237
  </button>
@@ -240,23 +240,23 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
240
240
  <Show when={pickerOpen()}>
241
241
  <Show
242
242
  when={!loading()}
243
- fallback={<div class="text-xs text-zinc-500">Loading packages…</div>}
243
+ fallback={<div class="text-xs text-[var(--ks-fg-subtle,#71717a)]">Loading packages…</div>}
244
244
  >
245
245
  <Show
246
246
  when={!loadError()}
247
- fallback={<div class="text-xs text-rose-400">{loadError()}</div>}
247
+ fallback={<div class="text-xs text-[var(--ks-danger-fg,#f87171)]">{loadError()}</div>}
248
248
  >
249
249
  <Show
250
250
  when={packages().length > 0}
251
251
  fallback={
252
- <div class="text-xs text-zinc-500">No active packages.</div>
252
+ <div class="text-xs text-[var(--ks-fg-subtle,#71717a)]">No active packages.</div>
253
253
  }
254
254
  >
255
- <div class="space-y-1.5 max-h-56 overflow-y-auto rounded-md border border-emerald-500/15 p-2 bg-zinc-950/40">
255
+ <div class="space-y-1.5 max-h-56 overflow-y-auto rounded-md border border-[color-mix(in_srgb,var(--ks-success,#10b981)_15%,transparent)] p-2 bg-[color-mix(in_srgb,var(--ks-bg,#0a0a0a)_40%,transparent)]">
256
256
  <For each={packages()}>
257
257
  {(pkg) => (
258
258
  <div class="space-y-1">
259
- <div class="text-[11px] uppercase tracking-widest text-zinc-400">
259
+ <div class="text-[11px] uppercase tracking-widest text-[var(--ks-fg-muted,#a1a1aa)]">
260
260
  {pkg.name}
261
261
  </div>
262
262
  <div class="flex flex-wrap gap-1.5">
@@ -265,7 +265,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
265
265
  <button
266
266
  type="button"
267
267
  onClick={() => addVariant(pkg, v)}
268
- class="ks-interactive inline-flex items-center gap-1 rounded border border-zinc-700 bg-zinc-900 px-2 py-0.5 text-xs text-zinc-300 hover:border-emerald-500/40 hover:text-emerald-300"
268
+ class="ks-interactive inline-flex items-center gap-1 rounded border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-input-bg,#18181b)] px-2 py-0.5 text-xs text-[var(--ks-fg,#ffffff)] hover:border-[color-mix(in_srgb,var(--ks-success,#10b981)_40%,transparent)] hover:text-[var(--ks-success-fg,#34d399)]"
269
269
  >
270
270
  {v.name} · {formatPHP(Number(v.price))}
271
271
  </button>
@@ -284,7 +284,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
284
284
  <Show
285
285
  when={props.items.length > 0}
286
286
  fallback={
287
- <div class="text-xs text-zinc-500">No packages added yet.</div>
287
+ <div class="text-xs text-[var(--ks-fg-subtle,#71717a)]">No packages added yet.</div>
288
288
  }
289
289
  >
290
290
  <div class="space-y-1.5">
@@ -292,11 +292,11 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
292
292
  {(line) => (
293
293
  <div class="flex items-center gap-2 text-sm">
294
294
  <div class="min-w-0 flex-1">
295
- <div class="text-zinc-200 truncate">
295
+ <div class="text-[var(--ks-fg,#ffffff)] truncate">
296
296
  {line.package_name}{" "}
297
- <span class="text-zinc-500">· {line.variant_name}</span>
297
+ <span class="text-[var(--ks-fg-subtle,#71717a)]">· {line.variant_name}</span>
298
298
  </div>
299
- <div class="text-[11px] text-zinc-500 tabular-nums">
299
+ <div class="text-[11px] text-[var(--ks-fg-subtle,#71717a)] tabular-nums">
300
300
  {formatPHP(line.unit_price)} · {line.duration_value}{" "}
301
301
  {line.duration_unit}
302
302
  {line.duration_value !== 1 ? "s" : ""}
@@ -306,30 +306,30 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
306
306
  <button
307
307
  type="button"
308
308
  onClick={() => adjust(line.key, -1)}
309
- class="ks-interactive flex h-6 w-6 items-center justify-center rounded border border-zinc-700 bg-zinc-900 text-zinc-300 hover:border-emerald-500/40"
309
+ class="ks-interactive flex h-6 w-6 items-center justify-center rounded border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-input-bg,#18181b)] text-[var(--ks-fg,#ffffff)] hover:border-[color-mix(in_srgb,var(--ks-success,#10b981)_40%,transparent)]"
310
310
  aria-label="Decrease quantity"
311
311
  >
312
312
  <Minus size={12} />
313
313
  </button>
314
- <span class="w-6 text-center text-sm text-zinc-200 tabular-nums">
314
+ <span class="w-6 text-center text-sm text-[var(--ks-fg,#ffffff)] tabular-nums">
315
315
  {line.quantity}
316
316
  </span>
317
317
  <button
318
318
  type="button"
319
319
  onClick={() => adjust(line.key, 1)}
320
- class="ks-interactive flex h-6 w-6 items-center justify-center rounded border border-zinc-700 bg-zinc-900 text-zinc-300 hover:border-emerald-500/40"
320
+ class="ks-interactive flex h-6 w-6 items-center justify-center rounded border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-input-bg,#18181b)] text-[var(--ks-fg,#ffffff)] hover:border-[color-mix(in_srgb,var(--ks-success,#10b981)_40%,transparent)]"
321
321
  aria-label="Increase quantity"
322
322
  >
323
323
  <Plus size={12} />
324
324
  </button>
325
325
  </div>
326
- <div class="w-20 text-right text-zinc-300 tabular-nums whitespace-nowrap">
326
+ <div class="w-20 text-right text-[var(--ks-fg,#ffffff)] tabular-nums whitespace-nowrap">
327
327
  {formatPHP(line.unit_price * line.quantity)}
328
328
  </div>
329
329
  <button
330
330
  type="button"
331
331
  onClick={() => remove(line.key)}
332
- class="ks-interactive flex h-6 w-6 items-center justify-center rounded text-zinc-500 hover:text-rose-400"
332
+ class="ks-interactive flex h-6 w-6 items-center justify-center rounded text-[var(--ks-fg-subtle,#71717a)] hover:text-[var(--ks-danger-fg,#f87171)]"
333
333
  aria-label="Remove line"
334
334
  >
335
335
  <Trash2 size={12} />
@@ -342,7 +342,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
342
342
 
343
343
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
344
344
  <div>
345
- <label class="text-[10px] uppercase tracking-widest text-zinc-500 font-semibold">
345
+ <label class="text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold">
346
346
  Billed to
347
347
  </label>
348
348
  <ComboBox<ClientOption>
@@ -360,7 +360,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
360
360
  />
361
361
  </div>
362
362
  <div>
363
- <label class="text-[10px] uppercase tracking-widest text-zinc-500 font-semibold">
363
+ <label class="text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold">
364
364
  Voucher
365
365
  </label>
366
366
  <VoucherPicker
@@ -377,7 +377,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
377
377
 
378
378
  <Show when={!props.voucher}>
379
379
  <div>
380
- <label class="text-[10px] uppercase tracking-widest text-zinc-500 font-semibold">
380
+ <label class="text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold">
381
381
  Manual discount (₱)
382
382
  </label>
383
383
  <input
@@ -386,19 +386,19 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
386
386
  step="0.01"
387
387
  value={props.manualDiscount}
388
388
  onInput={(e) => props.setManualDiscount(e.currentTarget.value)}
389
- class="mt-0.5 w-full rounded-md border border-zinc-700 bg-zinc-900 px-2 py-1 text-sm text-zinc-200 tabular-nums focus:border-emerald-500/50 focus:outline-none"
389
+ class="mt-0.5 w-full rounded-md border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-input-bg,#18181b)] px-2 py-1 text-sm text-[var(--ks-fg,#ffffff)] tabular-nums focus:border-[color-mix(in_srgb,var(--ks-success,#10b981)_50%,transparent)] focus:outline-none"
390
390
  />
391
391
  </div>
392
392
  </Show>
393
393
 
394
394
  <Show when={props.items.length > 0}>
395
- <div class="border-t border-emerald-500/15 pt-2 text-xs space-y-0.5 tabular-nums">
396
- <div class="flex items-center justify-between text-zinc-400">
395
+ <div class="border-t border-[color-mix(in_srgb,var(--ks-success,#10b981)_15%,transparent)] pt-2 text-xs space-y-0.5 tabular-nums">
396
+ <div class="flex items-center justify-between text-[var(--ks-fg-muted,#a1a1aa)]">
397
397
  <span>Subtotal</span>
398
398
  <span>{formatPHP(subtotal())}</span>
399
399
  </div>
400
400
  <Show when={effectiveDiscount() > 0}>
401
- <div class="flex items-center justify-between text-zinc-400">
401
+ <div class="flex items-center justify-between text-[var(--ks-fg-muted,#a1a1aa)]">
402
402
  <span>
403
403
  {props.voucher
404
404
  ? `Voucher ${props.voucher.code}`
@@ -407,7 +407,7 @@ export default function SalesBodyEditor(props: SalesBodyEditorProps) {
407
407
  <span>− {formatPHP(effectiveDiscount())}</span>
408
408
  </div>
409
409
  </Show>
410
- <div class="flex items-center justify-between text-zinc-200 font-semibold">
410
+ <div class="flex items-center justify-between text-[var(--ks-fg,#ffffff)] font-semibold">
411
411
  <span>Total</span>
412
412
  <span>{formatPHP(total())}</span>
413
413
  </div>
@@ -151,7 +151,7 @@ export default function SearchableSelect(props: SearchableSelectProps): JSX.Elem
151
151
  onClick={() => !props.disabled && setOpen((o) => !o)}
152
152
  class={
153
153
  props.triggerClass ??
154
- "inline-flex items-center gap-1.5 text-xs text-zinc-300 bg-zinc-800/60 hover:bg-zinc-800 border border-zinc-700/60 rounded px-2 py-1 cursor-pointer transition-colors"
154
+ "inline-flex items-center gap-1.5 text-xs text-[var(--ks-fg,#ffffff)] bg-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] hover:bg-[var(--ks-surface-raised,#1a1a1a)] border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_60%,transparent)] rounded px-2 py-1 cursor-pointer transition-colors"
155
155
  }
156
156
  classList={{ "cursor-not-allowed opacity-60": props.disabled }}
157
157
  title={props.disabled ? undefined : "Click to select"}
@@ -160,33 +160,33 @@ export default function SearchableSelect(props: SearchableSelectProps): JSX.Elem
160
160
  {busy()
161
161
  ? "…"
162
162
  : (currentLabel() ?? (
163
- <span class="text-zinc-500 italic">{props.placeholder ?? "Select…"}</span>
163
+ <span class="text-[var(--ks-fg-subtle,#71717a)] italic">{props.placeholder ?? "Select…"}</span>
164
164
  ))}
165
165
  </span>
166
- <ChevronsUpDown size={12} class="text-zinc-500 shrink-0" />
166
+ <ChevronsUpDown size={12} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0" />
167
167
  </button>
168
168
  <Show when={open()}>
169
169
  <Portal>
170
170
  <div
171
171
  ref={popupRef}
172
- class="z-[10000] rounded-md border border-zinc-700 bg-zinc-900/95 backdrop-blur shadow-xl overflow-hidden flex flex-col"
172
+ class="z-[10000] rounded-md border border-[var(--ks-input-border,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_95%,transparent)] backdrop-blur shadow-xl overflow-hidden flex flex-col"
173
173
  style={popupStyle()}
174
174
  >
175
- <div class="px-2 py-1.5 border-b border-zinc-800">
175
+ <div class="px-2 py-1.5 border-b border-[var(--ks-border-strong,#3f3f46)]">
176
176
  <input
177
177
  ref={inputRef}
178
178
  type="text"
179
179
  value={query()}
180
180
  onInput={(e) => setQuery(e.currentTarget.value)}
181
181
  placeholder={props.searchPlaceholder ?? "Search…"}
182
- class="w-full px-2 py-1 text-xs bg-zinc-950 border border-zinc-800 rounded text-zinc-200 placeholder:text-zinc-600 focus:outline-none focus:border-amber-500/50"
182
+ class="w-full px-2 py-1 text-xs bg-[var(--ks-bg,#0a0a0a)] border border-[var(--ks-border-strong,#3f3f46)] rounded text-[var(--ks-fg,#ffffff)] placeholder:text-[var(--ks-input-placeholder,#71717a)] focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
183
183
  />
184
184
  </div>
185
185
  <div class="flex-1 overflow-y-auto">
186
186
  <Show
187
187
  when={!props.loading && filtered().length > 0}
188
188
  fallback={
189
- <div class="px-3 py-3 text-xs text-zinc-500 text-center">{emptyStateLabel()}</div>
189
+ <div class="px-3 py-3 text-xs text-[var(--ks-fg-subtle,#71717a)] text-center">{emptyStateLabel()}</div>
190
190
  }
191
191
  >
192
192
  <For each={filtered()}>
@@ -196,22 +196,22 @@ export default function SearchableSelect(props: SearchableSelectProps): JSX.Elem
196
196
  <button
197
197
  type="button"
198
198
  onClick={() => select(opt)}
199
- class="w-full text-left px-3 py-2 text-xs hover:bg-amber-500/10 transition-colors flex items-center justify-between gap-2"
199
+ class="w-full text-left px-3 py-2 text-xs hover:bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_10%,transparent)] transition-colors flex items-center justify-between gap-2"
200
200
  classList={{
201
- "text-amber-400": selected(),
202
- "text-zinc-200": !selected(),
201
+ "text-[var(--ks-accent,#fbbf24)]": selected(),
202
+ "text-[var(--ks-fg,#ffffff)]": !selected(),
203
203
  }}
204
204
  >
205
205
  <span class="flex flex-col min-w-0">
206
206
  <span class="font-medium truncate">{opt.label}</span>
207
207
  <Show when={opt.description}>
208
- <span class="text-[10px] text-zinc-500 truncate">
208
+ <span class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] truncate">
209
209
  {opt.description}
210
210
  </span>
211
211
  </Show>
212
212
  </span>
213
213
  <Show when={selected()}>
214
- <span class="text-amber-400 shrink-0">✓</span>
214
+ <span class="text-[var(--ks-accent,#fbbf24)] shrink-0">✓</span>
215
215
  </Show>
216
216
  </button>
217
217
  );
@@ -220,11 +220,11 @@ export default function SearchableSelect(props: SearchableSelectProps): JSX.Elem
220
220
  </Show>
221
221
  </div>
222
222
  <Show when={props.allowClear && props.value != null && props.value !== ""}>
223
- <div class="border-t border-zinc-800">
223
+ <div class="border-t border-[var(--ks-border-strong,#3f3f46)]">
224
224
  <button
225
225
  type="button"
226
226
  onClick={() => select(null)}
227
- class="w-full text-left px-3 py-2 text-xs text-red-400 hover:bg-red-500/10 transition-colors flex items-center gap-2"
227
+ class="w-full text-left px-3 py-2 text-xs text-[var(--ks-danger-fg,#f87171)] hover:bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)] transition-colors flex items-center gap-2"
228
228
  >
229
229
  <X size={12} />
230
230
  <span>Clear selection</span>
@@ -34,19 +34,19 @@ const SecretReveal: Component<SecretRevealProps> = (props) => {
34
34
  };
35
35
 
36
36
  return (
37
- <div class="mb-4 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
38
- <p class="text-xs text-amber-300 font-medium mb-2">{local.warning ?? DEFAULT_WARNING}</p>
37
+ <div class="mb-4 p-3 rounded-lg bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_10%,transparent)] border border-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_20%,transparent)]">
38
+ <p class="text-xs text-[var(--ks-accent-hover,#fcd34d)] font-medium mb-2">{local.warning ?? DEFAULT_WARNING}</p>
39
39
  <div class="flex items-center gap-2">
40
40
  <code
41
41
  data-testid={local.testId}
42
- class="flex-1 px-2 py-1.5 rounded bg-zinc-900 border border-zinc-700 text-amber-200 text-xs font-mono break-all"
42
+ class="flex-1 px-2 py-1.5 rounded bg-[var(--ks-input-bg,#18181b)] border border-[var(--ks-input-border,#3f3f46)] text-[var(--ks-accent-hover,#fcd34d)] text-xs font-mono break-all"
43
43
  >
44
44
  {local.secret}
45
45
  </code>
46
46
  <button
47
47
  type="button"
48
48
  onClick={copy}
49
- class="ks-interactive px-2 py-1.5 rounded bg-zinc-800 border border-zinc-700 text-zinc-300 hover:text-white text-xs flex items-center gap-1"
49
+ class="ks-interactive px-2 py-1.5 rounded bg-[var(--ks-surface-raised,#1a1a1a)] border border-[var(--ks-input-border,#3f3f46)] text-[var(--ks-fg-muted,#a1a1aa)] hover:text-[var(--ks-fg,#ffffff)] text-xs flex items-center gap-1"
50
50
  aria-label="Copy secret"
51
51
  >
52
52
  <Copy size={12} />
@@ -54,7 +54,7 @@ const SecretReveal: Component<SecretRevealProps> = (props) => {
54
54
  </button>
55
55
  </div>
56
56
  <Show when={local.caption}>
57
- <p class="text-[10px] text-zinc-500 mt-2">{local.caption}</p>
57
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-2">{local.caption}</p>
58
58
  </Show>
59
59
  </div>
60
60
  );
@@ -0,0 +1,78 @@
1
+ import { Show } from "solid-js";
2
+ import type { PaymentAccountOption } from "./PaymentAccountPicker";
3
+ import FormField from "../base/FormField";
4
+ import AccountRadioPicker from "./AccountRadioPicker";
5
+ import TransferAccountsPicker from "./TransferAccountsPicker";
6
+
7
+ export type TransactionFormAccount = PaymentAccountOption & {
8
+ balance?: number | string | null;
9
+ };
10
+
11
+ export interface TransactionAccountFieldsProps {
12
+ category: string;
13
+ accounts: TransactionFormAccount[];
14
+ accountLabel: string;
15
+ accountHint: string;
16
+ showSecondAccount: boolean;
17
+ secondAccountLabel?: string;
18
+ sourceAccount: string;
19
+ setSourceAccount: (v: string) => void;
20
+ destAccount: string;
21
+ setDestAccount: (v: string) => void;
22
+ amount: string;
23
+ transferFeeAmount: string;
24
+ transferFeeEnabled: boolean;
25
+ allowTransferFee: boolean;
26
+ }
27
+
28
+ /**
29
+ * The single-account-picker vs transfer-accounts-picker branch, split out of
30
+ * TransactionForm to keep that file under the file-size budget. `sale` writes
31
+ * to destAccount, every other category writes to sourceAccount — that
32
+ * category-driven single/second-account split is the only domain logic here.
33
+ */
34
+ export default function TransactionAccountFields(props: TransactionAccountFieldsProps) {
35
+ return (
36
+ <Show
37
+ when={props.showSecondAccount}
38
+ fallback={
39
+ <FormField label={props.accountLabel}>
40
+ <AccountRadioPicker
41
+ accounts={props.accounts}
42
+ ariaLabel={props.accountLabel}
43
+ value={
44
+ props.category === "sale" ? props.destAccount : props.sourceAccount
45
+ }
46
+ onChange={(v) => {
47
+ if (props.category === "sale") {
48
+ props.setDestAccount(v);
49
+ props.setSourceAccount("");
50
+ } else {
51
+ props.setSourceAccount(v);
52
+ props.setDestAccount("");
53
+ }
54
+ }}
55
+ />
56
+ <Show when={props.accountHint}>
57
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-0.5">
58
+ {props.accountHint}
59
+ </p>
60
+ </Show>
61
+ </FormField>
62
+ }
63
+ >
64
+ <TransferAccountsPicker
65
+ accounts={props.accounts}
66
+ sourceAccount={props.sourceAccount}
67
+ setSourceAccount={props.setSourceAccount}
68
+ destAccount={props.destAccount}
69
+ setDestAccount={props.setDestAccount}
70
+ sourceLabel={props.accountLabel}
71
+ destLabel={props.secondAccountLabel!}
72
+ amount={props.amount}
73
+ feeAmount={props.transferFeeAmount}
74
+ feeEnabled={props.transferFeeEnabled && props.allowTransferFee}
75
+ />
76
+ </Show>
77
+ );
78
+ }