@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
@@ -24,11 +24,11 @@ import ArrowDownLeft from "lucide-solid/icons/arrow-down-left";
24
24
  import ArrowUpRight from "lucide-solid/icons/arrow-up-right";
25
25
  import ArrowRightLeft from "lucide-solid/icons/arrow-right-left";
26
26
 
27
- import AccountRadioPicker from "./AccountRadioPicker";
28
27
  import FormAdvancedSection from "./FormAdvancedSection";
29
28
  import SalesBodyEditor, { type SalesLine } from "./SalesBodyEditor";
30
29
  import TransferFeeChip from "./TransferFeeChip";
31
- import TransferAccountsPicker from "./TransferAccountsPicker";
30
+ import TransactionAccountFields from "./TransactionAccountFields";
31
+ import TransactionPayableFields from "./TransactionPayableFields";
32
32
  import ComboBox from "./ComboBox";
33
33
  import type { ClientOption, PayeeOption, PayeeKind } from "./picker-types";
34
34
  import VoucherPicker, { type VoucherOption } from "./VoucherPicker";
@@ -43,7 +43,6 @@ import ExistingAttachmentTile, {
43
43
  import FormField from "../base/FormField";
44
44
  import DatePicker from "../base/DatePicker";
45
45
  import Button from "../base/Button";
46
- import SegmentedFilter from "../base/SegmentedFilter";
47
46
  import {
48
47
  type PendingFile,
49
48
  createPendingFile,
@@ -137,35 +136,27 @@ const TONE_CLASSES: Record<
137
136
  { bg: string; text: string; border: string }
138
137
  > = {
139
138
  emerald: {
140
- bg: "bg-emerald-500/10",
141
- text: "text-emerald-400",
142
- border: "border-emerald-500/30",
139
+ bg: "bg-[color-mix(in_srgb,var(--ks-success,#10b981)_10%,transparent)]",
140
+ text: "text-[var(--ks-success-fg,#34d399)]",
141
+ border: "border-[color-mix(in_srgb,var(--ks-success,#10b981)_30%,transparent)]",
142
+ },
143
+ red: {
144
+ bg: "bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)]",
145
+ text: "text-[var(--ks-danger-fg,#f87171)]",
146
+ border: "border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_30%,transparent)]",
147
+ },
148
+ blue: {
149
+ bg: "bg-[color-mix(in_srgb,var(--ks-info,#38bdf8)_10%,transparent)]",
150
+ text: "text-[var(--ks-info,#38bdf8)]",
151
+ border: "border-[color-mix(in_srgb,var(--ks-info,#38bdf8)_30%,transparent)]",
143
152
  },
144
- red: { bg: "bg-red-500/10", text: "text-red-400", border: "border-red-500/30" },
145
- blue: { bg: "bg-blue-500/10", text: "text-blue-400", border: "border-blue-500/30" },
146
153
  amber: {
147
- bg: "bg-amber-500/10",
148
- text: "text-amber-400",
149
- border: "border-amber-500/30",
154
+ bg: "bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_10%,transparent)]",
155
+ text: "text-[var(--ks-accent,#fbbf24)]",
156
+ border: "border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_30%,transparent)]",
150
157
  },
151
158
  };
152
159
 
153
- const PAYABLE_KIND_OPTIONS: { id: string; label: string }[] = [
154
- { id: "subscription", label: "Subscription" },
155
- { id: "utility", label: "Utility" },
156
- { id: "rent", label: "Rent / Lease" },
157
- { id: "loan", label: "Loan" },
158
- { id: "tax", label: "Tax" },
159
- { id: "other", label: "Other" },
160
- ];
161
-
162
- const PDC_OPTIONS: { id: string; label: string; dot: string }[] = [
163
- { id: "issued", label: "PDC issued", dot: "bg-amber-400" },
164
- { id: "presented", label: "PDC presented", dot: "bg-blue-400" },
165
- { id: "cleared", label: "PDC cleared", dot: "bg-emerald-400" },
166
- { id: "bounced", label: "PDC bounced", dot: "bg-red-400" },
167
- ];
168
-
169
160
  const CATEGORY_FORM: Record<
170
161
  string,
171
162
  {
@@ -438,12 +429,12 @@ export default function TransactionForm(props: TransactionFormProps) {
438
429
  onPaste={handlePaste}
439
430
  >
440
431
  <Show when={dragging()}>
441
- <div class="absolute inset-0 z-30 border-2 border-dashed border-amber-400/60 bg-amber-500/10 backdrop-blur-sm flex flex-col items-center justify-center pointer-events-none">
442
- <Upload size={32} class="text-amber-400 mb-2" />
443
- <span class="text-sm text-amber-400 font-medium">
432
+ <div class="absolute inset-0 z-30 border-2 border-dashed border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_60%,transparent)] bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_10%,transparent)] backdrop-blur-sm flex flex-col items-center justify-center pointer-events-none">
433
+ <Upload size={32} class="text-[var(--ks-accent,#fbbf24)] mb-2" />
434
+ <span class="text-sm text-[var(--ks-accent,#fbbf24)] font-medium">
444
435
  Drop files to attach
445
436
  </span>
446
- <span class="text-[10px] text-amber-400/60 mt-1">Images or PDFs</span>
437
+ <span class="text-[10px] text-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_60%,transparent)] mt-1">Images or PDFs</span>
447
438
  </div>
448
439
  </Show>
449
440
 
@@ -467,7 +458,7 @@ export default function TransactionForm(props: TransactionFormProps) {
467
458
  <div class="flex-1 overflow-x-hidden overflow-y-auto px-5 sm:px-6 py-5 space-y-4">
468
459
  <Show when={props.error}>
469
460
  <div
470
- class="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-400"
461
+ class="rounded-lg border border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_30%,transparent)] bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)] px-3 py-2 text-sm text-[var(--ks-danger-fg,#f87171)]"
471
462
  data-testid="transactions-form-error"
472
463
  >
473
464
  {props.error}
@@ -476,7 +467,7 @@ export default function TransactionForm(props: TransactionFormProps) {
476
467
 
477
468
  <Show when={!props.simpleMode}>
478
469
  <div>
479
- <div class="text-[10px] uppercase tracking-widest text-zinc-500 font-semibold mb-2">
470
+ <div class="text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold mb-2">
480
471
  Type
481
472
  </div>
482
473
  <div
@@ -509,7 +500,7 @@ export default function TransactionForm(props: TransactionFormProps) {
509
500
  classList={{
510
501
  [`${tc.bg} ${tc.border} ${tc.text}`]:
511
502
  props.category === cat,
512
- "border-zinc-800 bg-transparent text-zinc-500 hover:border-zinc-700 hover:text-zinc-200":
503
+ "border-[var(--ks-border,rgba(39,39,42,0.5))] bg-transparent text-[var(--ks-fg-subtle,#71717a)] hover:border-[var(--ks-input-border,#3f3f46)] hover:text-[var(--ks-fg,#ffffff)]":
513
504
  props.category !== cat,
514
505
  }}
515
506
  >
@@ -520,7 +511,7 @@ export default function TransactionForm(props: TransactionFormProps) {
520
511
  }}
521
512
  </For>
522
513
  </div>
523
- <p class="text-[11px] text-zinc-500 mt-2">{catConfig().hint}</p>
514
+ <p class="text-[11px] text-[var(--ks-fg-subtle,#71717a)] mt-2">{catConfig().hint}</p>
524
515
  </div>
525
516
  </Show>
526
517
 
@@ -541,8 +532,8 @@ export default function TransactionForm(props: TransactionFormProps) {
541
532
  when={!(props.category === "sale" && props.saleItems.length > 0)}
542
533
  >
543
534
  <FormField label="Amount *">
544
- <div class="flex items-stretch gap-2 px-4 py-3 border bg-zinc-900/60 border-zinc-800/60 ks-hud-clip-button focus-within:border-amber-500/50 transition-colors">
545
- <span class="self-center text-3xl font-bold text-zinc-500 tabular-nums">
535
+ <div class="flex items-stretch gap-2 px-4 py-3 border bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] ks-hud-clip-button focus-within:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)] transition-colors">
536
+ <span class="self-center text-3xl font-bold text-[var(--ks-fg-subtle,#71717a)] tabular-nums">
546
537
 
547
538
  </span>
548
539
  <input
@@ -552,7 +543,7 @@ export default function TransactionForm(props: TransactionFormProps) {
552
543
  data-testid="transactions-form-amount"
553
544
  value={props.amount}
554
545
  onInput={(e) => props.setAmount(e.currentTarget.value)}
555
- class="min-w-0 flex-1 bg-transparent text-2xl sm:text-3xl font-bold tabular-nums text-zinc-100 placeholder-zinc-700 focus:outline-none"
546
+ class="min-w-0 flex-1 bg-transparent text-2xl sm:text-3xl font-bold tabular-nums text-[var(--ks-fg,#ffffff)] placeholder-[var(--ks-input-border,#3f3f46)] focus:outline-none"
556
547
  placeholder="0.00"
557
548
  required
558
549
  />
@@ -582,8 +573,8 @@ export default function TransactionForm(props: TransactionFormProps) {
582
573
  data-testid="transactions-form-transfer-fee-field"
583
574
  >
584
575
  <FormField label="Transfer fee *">
585
- <div class="flex items-center gap-2 px-3 py-2 border bg-zinc-950/50 border-blue-500/30 ks-hud-clip-button focus-within:border-blue-500/60 transition-colors">
586
- <span class="text-lg font-bold text-zinc-500 tabular-nums">
576
+ <div class="flex items-center gap-2 px-3 py-2 border bg-[color-mix(in_srgb,var(--ks-bg,#0a0a0a)_50%,transparent)] border-[color-mix(in_srgb,var(--ks-info,#38bdf8)_30%,transparent)] ks-hud-clip-button focus-within:border-[color-mix(in_srgb,var(--ks-info,#38bdf8)_60%,transparent)] transition-colors">
577
+ <span class="text-lg font-bold text-[var(--ks-fg-subtle,#71717a)] tabular-nums">
587
578
 
588
579
  </span>
589
580
  <input
@@ -595,11 +586,11 @@ export default function TransactionForm(props: TransactionFormProps) {
595
586
  onInput={(e) =>
596
587
  props.setTransferFeeAmount(e.currentTarget.value)
597
588
  }
598
- class="min-w-0 flex-1 bg-transparent text-lg font-semibold tabular-nums text-zinc-100 placeholder-zinc-700 focus:outline-none"
589
+ class="min-w-0 flex-1 bg-transparent text-lg font-semibold tabular-nums text-[var(--ks-fg,#ffffff)] placeholder-[var(--ks-input-border,#3f3f46)] focus:outline-none"
599
590
  placeholder="0.00"
600
591
  />
601
592
  </div>
602
- <p class="mt-1 text-[10px] text-zinc-600">
593
+ <p class="mt-1 text-[10px] text-[var(--ks-fg-subtle,#71717a)]">
603
594
  Saved as a separate expense from the source account.
604
595
  </p>
605
596
  </FormField>
@@ -614,14 +605,14 @@ export default function TransactionForm(props: TransactionFormProps) {
614
605
  disabled={!props.isAdmin}
615
606
  />
616
607
  <Show when={!props.isAdmin}>
617
- <p class="text-[10px] text-zinc-600 mt-0.5">
608
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-0.5">
618
609
  Only admins can change the date
619
610
  </p>
620
611
  </Show>
621
612
  </FormField>
622
613
 
623
614
  <Show when={props.isBackdated}>
624
- <div class="rounded-lg border border-amber-500/20 bg-amber-500/5 px-3 py-2">
615
+ <div class="rounded-lg border border-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_20%,transparent)] bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_5%,transparent)] px-3 py-2">
625
616
  <FormField label="Backdate Reason *">
626
617
  <input
627
618
  type="text"
@@ -630,7 +621,7 @@ export default function TransactionForm(props: TransactionFormProps) {
630
621
  onInput={(e) =>
631
622
  props.setBackdateReason(e.currentTarget.value)
632
623
  }
633
- class="w-full rounded-lg border border-amber-500/30 bg-zinc-800/50 px-3 py-2 text-sm text-zinc-200 focus:border-amber-500/50 focus:outline-none"
624
+ class="w-full rounded-lg border border-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_30%,transparent)] bg-[var(--ks-border,rgba(39,39,42,0.5))] px-3 py-2 text-sm text-[var(--ks-fg,#ffffff)] focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)] focus:outline-none"
634
625
  placeholder="Why are you backdating this transaction?"
635
626
  required
636
627
  />
@@ -644,7 +635,7 @@ export default function TransactionForm(props: TransactionFormProps) {
644
635
  data-testid="transactions-form-description"
645
636
  value={props.description}
646
637
  onInput={(e) => props.setDescription(e.currentTarget.value)}
647
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button focus:outline-none focus:border-amber-500/50"
638
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
648
639
  placeholder={catConfig().descPlaceholder}
649
640
  required
650
641
  />
@@ -687,7 +678,7 @@ export default function TransactionForm(props: TransactionFormProps) {
687
678
  type="text"
688
679
  value={props.refNumber}
689
680
  onInput={(e) => props.setRefNumber(e.currentTarget.value)}
690
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button focus:outline-none focus:border-amber-500/50"
681
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
691
682
  placeholder="OR#, SI#, or ref number"
692
683
  />
693
684
  </FormField>
@@ -699,7 +690,7 @@ export default function TransactionForm(props: TransactionFormProps) {
699
690
  type="text"
700
691
  value={props.refNumber}
701
692
  onInput={(e) => props.setRefNumber(e.currentTarget.value)}
702
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button focus:outline-none focus:border-amber-500/50"
693
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
703
694
  placeholder="Reference number (optional)"
704
695
  />
705
696
  </FormField>
@@ -713,7 +704,7 @@ export default function TransactionForm(props: TransactionFormProps) {
713
704
  <select
714
705
  disabled
715
706
  data-testid="subcategory-select-loading"
716
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-500 ks-hud-clip-button focus:outline-none"
707
+ class="w-full bg-[color-mix(in_srgb,var(--ks-input-bg,#18181b)_60%,transparent)] border border-[var(--ks-border,rgba(39,39,42,0.5))] px-3 py-3 text-sm text-[var(--ks-fg-subtle,#71717a)] ks-hud-clip-button focus:outline-none"
717
708
  >
718
709
  <option>Loading…</option>
719
710
  </select>
@@ -745,128 +736,51 @@ export default function TransactionForm(props: TransactionFormProps) {
745
736
  }
746
737
  placeholder="— Uncategorised —"
747
738
  searchPlaceholder="Search categories…"
748
- triggerClass="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button cursor-pointer focus:outline-none focus:border-amber-500/50 flex items-center justify-between gap-2"
739
+ triggerClass="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button cursor-pointer focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)] flex items-center justify-between gap-2"
749
740
  triggerLabelClass="truncate text-left flex-1 min-w-0"
750
741
  />
751
742
  </Show>
752
- <p class="text-[10px] text-zinc-600 mt-0.5">
743
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-0.5">
753
744
  Optional. Used for tax-prep classification.
754
745
  </p>
755
746
  </FormField>
756
747
  </Show>
757
748
 
758
749
  <Show when={props.category === "payable"}>
759
- <div class="rounded-lg border border-amber-500/20 bg-amber-500/5 p-3 space-y-3">
760
- <div class="flex items-center gap-2 text-[10px] uppercase tracking-widest text-amber-400 font-semibold">
761
- <CalendarDays size={12} />
762
- <span>Payable details</span>
763
- </div>
764
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
765
- <FormField label="Kind *">
766
- <select
767
- value={props.payableKind}
768
- onChange={(e) =>
769
- props.setPayableKind(e.currentTarget.value)
770
- }
771
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button cursor-pointer focus:outline-none focus:border-amber-500/50"
772
- >
773
- <For each={PAYABLE_KIND_OPTIONS}>
774
- {(opt) => <option value={opt.id}>{opt.label}</option>}
775
- </For>
776
- </select>
777
- </FormField>
778
- <FormField label="Due date">
779
- <DatePicker
780
- value={props.dueDate}
781
- onChange={(d: string | null) => props.setDueDate(d || "")}
782
- />
783
- <p class="text-[10px] text-zinc-600 mt-0.5">
784
- When payment is owed. Past-due payables show in the Payables
785
- tab.
786
- </p>
787
- </FormField>
788
- </div>
789
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
790
- <FormField label="Cheque number">
791
- <input
792
- type="text"
793
- value={props.chequeNumber}
794
- onInput={(e) =>
795
- props.setChequeNumber(e.currentTarget.value)
796
- }
797
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-3 text-sm text-zinc-200 ks-hud-clip-button focus:outline-none focus:border-amber-500/50"
798
- placeholder="e.g. 0004429-007"
799
- />
800
- <p class="text-[10px] text-zinc-600 mt-0.5">
801
- For post-dated cheques (PDC). Leave blank for direct
802
- payments.
803
- </p>
804
- </FormField>
805
- <Show when={props.chequeNumber.trim()}>
806
- <FormField label="PDC status">
807
- <SegmentedFilter
808
- options={PDC_OPTIONS.map((opt) => ({
809
- value: opt.id,
810
- label: opt.label.replace("PDC ", ""),
811
- }))}
812
- value={props.pdcStatus}
813
- onChange={props.setPdcStatus}
814
- />
815
- </FormField>
816
- </Show>
817
- </div>
818
- </div>
819
- </Show>
820
-
821
- <Show
822
- when={catConfig().showSecondAccount}
823
- fallback={
824
- <FormField label={catConfig().accountLabel}>
825
- <AccountRadioPicker
826
- accounts={props.accounts}
827
- ariaLabel={catConfig().accountLabel}
828
- value={
829
- props.category === "sale"
830
- ? props.destAccount
831
- : props.sourceAccount
832
- }
833
- onChange={(v) => {
834
- if (props.category === "sale") {
835
- props.setDestAccount(v);
836
- props.setSourceAccount("");
837
- } else {
838
- props.setSourceAccount(v);
839
- props.setDestAccount("");
840
- }
841
- }}
842
- />
843
- <Show when={catConfig().accountHint}>
844
- <p class="text-[10px] text-zinc-600 mt-0.5">
845
- {catConfig().accountHint}
846
- </p>
847
- </Show>
848
- </FormField>
849
- }
850
- >
851
- <TransferAccountsPicker
852
- accounts={props.accounts}
853
- sourceAccount={props.sourceAccount}
854
- setSourceAccount={props.setSourceAccount}
855
- destAccount={props.destAccount}
856
- setDestAccount={props.setDestAccount}
857
- sourceLabel={catConfig().accountLabel}
858
- destLabel={catConfig().secondAccountLabel!}
859
- amount={props.amount}
860
- feeAmount={props.transferFeeAmount}
861
- feeEnabled={props.transferFeeEnabled && props.allowTransferFee}
750
+ <TransactionPayableFields
751
+ payableKind={props.payableKind}
752
+ setPayableKind={props.setPayableKind}
753
+ dueDate={props.dueDate}
754
+ setDueDate={props.setDueDate}
755
+ chequeNumber={props.chequeNumber}
756
+ setChequeNumber={props.setChequeNumber}
757
+ pdcStatus={props.pdcStatus}
758
+ setPdcStatus={props.setPdcStatus}
862
759
  />
863
760
  </Show>
864
761
 
762
+ <TransactionAccountFields
763
+ category={props.category}
764
+ accounts={props.accounts}
765
+ accountLabel={catConfig().accountLabel}
766
+ accountHint={catConfig().accountHint}
767
+ showSecondAccount={catConfig().showSecondAccount}
768
+ secondAccountLabel={catConfig().secondAccountLabel}
769
+ sourceAccount={props.sourceAccount}
770
+ setSourceAccount={props.setSourceAccount}
771
+ destAccount={props.destAccount}
772
+ setDestAccount={props.setDestAccount}
773
+ amount={props.amount}
774
+ transferFeeAmount={props.transferFeeAmount}
775
+ transferFeeEnabled={props.transferFeeEnabled}
776
+ allowTransferFee={props.allowTransferFee}
777
+ />
778
+
865
779
  <FormField label="Notes">
866
780
  <MentionTextarea
867
781
  value={props.notes}
868
782
  setValue={props.setNotes}
869
- class="w-full bg-zinc-900/60 border border-zinc-800/60 px-3 py-2 text-sm text-zinc-200 ks-hud-clip-button focus:outline-none focus:border-amber-500/50 resize-none"
783
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-2 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)] resize-none"
870
784
  rows={2}
871
785
  placeholder="Optional notes... (type @ to mention a client)"
872
786
  ariaLabel="Notes"
@@ -874,7 +788,7 @@ export default function TransactionForm(props: TransactionFormProps) {
874
788
  </FormField>
875
789
 
876
790
  <div>
877
- <div class="flex items-center gap-1 mb-2 text-xs text-zinc-500">
791
+ <div class="flex items-center gap-1 mb-2 text-xs text-[var(--ks-fg-subtle,#71717a)]">
878
792
  <Paperclip size={12} /> Attachments
879
793
  <Show
880
794
  when={
@@ -883,7 +797,7 @@ export default function TransactionForm(props: TransactionFormProps) {
883
797
  0
884
798
  }
885
799
  >
886
- <span class="text-zinc-600">
800
+ <span class="text-[var(--ks-fg-subtle,#71717a)]">
887
801
  (
888
802
  {(props.existingAttachments?.length ?? 0) +
889
803
  props.pendingFiles.length}
@@ -908,7 +822,7 @@ export default function TransactionForm(props: TransactionFormProps) {
908
822
  <Show
909
823
  when={pf.previewUrl}
910
824
  fallback={
911
- <div class="flex w-24 h-24 flex-col items-center justify-center gap-1 rounded-lg border border-zinc-700 bg-zinc-800/50 px-2 text-xs text-zinc-300">
825
+ <div class="flex w-24 h-24 flex-col items-center justify-center gap-1 rounded-lg border border-[var(--ks-input-border,#3f3f46)] bg-[var(--ks-border,rgba(39,39,42,0.5))] px-2 text-xs text-[var(--ks-fg-muted,#a1a1aa)]">
912
826
  <FileIcon size={20} />
913
827
  <span class="truncate max-w-full text-[10px]">
914
828
  {pf.file.name}
@@ -916,7 +830,7 @@ export default function TransactionForm(props: TransactionFormProps) {
916
830
  </div>
917
831
  }
918
832
  >
919
- <div class="block rounded-lg border border-zinc-700 overflow-hidden">
833
+ <div class="block rounded-lg border border-[var(--ks-input-border,#3f3f46)] overflow-hidden">
920
834
  <img
921
835
  src={pf.previewUrl!}
922
836
  alt={pf.file.name}
@@ -932,7 +846,7 @@ export default function TransactionForm(props: TransactionFormProps) {
932
846
  props.pendingFiles.filter((f) => f.id !== pf.id)
933
847
  );
934
848
  }}
935
- class="absolute -top-2 -right-2 flex w-7 h-7 items-center justify-center rounded-full bg-red-600/90 border border-red-400/60 text-white cursor-pointer hover:bg-red-500 active:bg-red-700 shadow-lg"
849
+ class="absolute -top-2 -right-2 flex w-7 h-7 items-center justify-center rounded-full bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_90%,transparent)] border border-[color-mix(in_srgb,var(--ks-danger-fg,#f87171)_60%,transparent)] text-[var(--ks-fg,#ffffff)] cursor-pointer hover:bg-[var(--ks-danger,#ef4444)] active:bg-[var(--ks-danger,#ef4444)] shadow-lg"
936
850
  aria-label={`Remove ${pf.file.name}`}
937
851
  >
938
852
  <X size={12} />
@@ -965,7 +879,7 @@ export default function TransactionForm(props: TransactionFormProps) {
965
879
  />
966
880
 
967
881
  <Show when={props.pendingFiles.length === 0}>
968
- <p class="text-[10px] text-zinc-600 mt-1">
882
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-1">
969
883
  Drop files here or paste from clipboard.
970
884
  </p>
971
885
  </Show>
@@ -978,7 +892,7 @@ export default function TransactionForm(props: TransactionFormProps) {
978
892
  onClick={() =>
979
893
  setViewMode(viewMode() === "default" ? "advanced" : "default")
980
894
  }
981
- class="text-xs text-zinc-500 hover:text-amber-400 px-3 py-1.5 transition-colors cursor-pointer"
895
+ class="text-xs text-[var(--ks-fg-subtle,#71717a)] hover:text-[var(--ks-accent,#fbbf24)] px-3 py-1.5 transition-colors cursor-pointer"
982
896
  >
983
897
  {viewMode() === "default"
984
898
  ? "Show advanced fields"
@@ -1010,16 +924,16 @@ export default function TransactionForm(props: TransactionFormProps) {
1010
924
  </Show>
1011
925
  </div>
1012
926
 
1013
- <div class="px-5 sm:px-6 py-4 border-t border-zinc-800/60 bg-zinc-950 flex flex-col-reverse sm:flex-row sm:items-center sm:justify-between gap-3 shrink-0">
1014
- <div class="text-xs text-zinc-500">
1015
- <span class="text-zinc-600">Will record as </span>
927
+ <div class="px-5 sm:px-6 py-4 border-t border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] bg-[var(--ks-bg,#0a0a0a)] flex flex-col-reverse sm:flex-row sm:items-center sm:justify-between gap-3 shrink-0">
928
+ <div class="text-xs text-[var(--ks-fg-subtle,#71717a)]">
929
+ <span class="text-[var(--ks-fg-subtle,#71717a)]">Will record as </span>
1016
930
  <span
1017
931
  class="font-bold"
1018
932
  classList={{
1019
- "text-emerald-400": props.category === "sale",
1020
- "text-red-400": props.category === "expense",
1021
- "text-amber-400": props.category === "payable",
1022
- "text-blue-400": props.category === "business",
933
+ "text-[var(--ks-success-fg,#34d399)]": props.category === "sale",
934
+ "text-[var(--ks-danger-fg,#f87171)]": props.category === "expense",
935
+ "text-[var(--ks-accent,#fbbf24)]": props.category === "payable",
936
+ "text-[var(--ks-info,#38bdf8)]": props.category === "business",
1023
937
  }}
1024
938
  >
1025
939
  {(CATEGORY_FORM[props.category] || CATEGORY_FORM.expense).label}
@@ -0,0 +1,97 @@
1
+ import { For, Show } from "solid-js";
2
+ import CalendarDays from "lucide-solid/icons/calendar-days";
3
+ import FormField from "../base/FormField";
4
+ import DatePicker from "../base/DatePicker";
5
+ import SegmentedFilter from "../base/SegmentedFilter";
6
+
7
+ const PAYABLE_KIND_OPTIONS: { id: string; label: string }[] = [
8
+ { id: "subscription", label: "Subscription" },
9
+ { id: "utility", label: "Utility" },
10
+ { id: "rent", label: "Rent / Lease" },
11
+ { id: "loan", label: "Loan" },
12
+ { id: "tax", label: "Tax" },
13
+ { id: "other", label: "Other" },
14
+ ];
15
+
16
+ const PDC_OPTIONS: { id: string; label: string; dot: string }[] = [
17
+ { id: "issued", label: "PDC issued", dot: "bg-[var(--ks-accent,#fbbf24)]" },
18
+ { id: "presented", label: "PDC presented", dot: "bg-[var(--ks-info,#38bdf8)]" },
19
+ { id: "cleared", label: "PDC cleared", dot: "bg-[var(--ks-success-fg,#34d399)]" },
20
+ { id: "bounced", label: "PDC bounced", dot: "bg-[var(--ks-danger-fg,#f87171)]" },
21
+ ];
22
+
23
+ export interface TransactionPayableFieldsProps {
24
+ payableKind: string;
25
+ setPayableKind: (v: string) => void;
26
+ dueDate: string;
27
+ setDueDate: (v: string) => void;
28
+ chequeNumber: string;
29
+ setChequeNumber: (v: string) => void;
30
+ pdcStatus: string;
31
+ setPdcStatus: (v: string) => void;
32
+ }
33
+
34
+ /**
35
+ * The "payable" category's extra fields, split out of TransactionForm to keep
36
+ * that file under the file-size budget. Kind/due-date/cheque/PDC-status only
37
+ * ever apply to the payable category, so this stays a pure sub-form.
38
+ */
39
+ export default function TransactionPayableFields(props: TransactionPayableFieldsProps) {
40
+ return (
41
+ <div class="rounded-lg border border-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_20%,transparent)] bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_5%,transparent)] p-3 space-y-3">
42
+ <div class="flex items-center gap-2 text-[10px] uppercase tracking-widest text-[var(--ks-accent,#fbbf24)] font-semibold">
43
+ <CalendarDays size={12} />
44
+ <span>Payable details</span>
45
+ </div>
46
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
47
+ <FormField label="Kind *">
48
+ <select
49
+ value={props.payableKind}
50
+ onChange={(e) => props.setPayableKind(e.currentTarget.value)}
51
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button cursor-pointer focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
52
+ >
53
+ <For each={PAYABLE_KIND_OPTIONS}>
54
+ {(opt) => <option value={opt.id}>{opt.label}</option>}
55
+ </For>
56
+ </select>
57
+ </FormField>
58
+ <FormField label="Due date">
59
+ <DatePicker
60
+ value={props.dueDate}
61
+ onChange={(d: string | null) => props.setDueDate(d || "")}
62
+ />
63
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-0.5">
64
+ When payment is owed. Past-due payables show in the Payables tab.
65
+ </p>
66
+ </FormField>
67
+ </div>
68
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
69
+ <FormField label="Cheque number">
70
+ <input
71
+ type="text"
72
+ value={props.chequeNumber}
73
+ onInput={(e) => props.setChequeNumber(e.currentTarget.value)}
74
+ class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
75
+ placeholder="e.g. 0004429-007"
76
+ />
77
+ <p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mt-0.5">
78
+ For post-dated cheques (PDC). Leave blank for direct payments.
79
+ </p>
80
+ </FormField>
81
+ <Show when={props.chequeNumber.trim()}>
82
+ <FormField label="PDC status">
83
+ <SegmentedFilter
84
+ options={PDC_OPTIONS.map((opt) => ({
85
+ value: opt.id,
86
+ label: opt.label.replace("PDC ", ""),
87
+ }))}
88
+ value={props.pdcStatus}
89
+ onChange={props.setPdcStatus}
90
+ ariaLabel="PDC status"
91
+ />
92
+ </FormField>
93
+ </Show>
94
+ </div>
95
+ </div>
96
+ );
97
+ }