@kahitsan/ksui 0.39.0 → 0.39.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.2",
|
|
4
4
|
"description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createEffect, Show, For } from "solid-js";
|
|
2
2
|
import AccountAvatar from "../base/AccountAvatar";
|
|
3
|
+
import Button from "../base/Button";
|
|
3
4
|
import type { PaymentAccountOption } from "./PaymentAccountPicker";
|
|
4
5
|
|
|
5
6
|
export interface AccountRadioPickerProps {
|
|
@@ -9,6 +10,8 @@ export interface AccountRadioPickerProps {
|
|
|
9
10
|
ariaLabel: string;
|
|
10
11
|
excludeId?: string;
|
|
11
12
|
autoDefault?: boolean;
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
tone?: "income" | "expense" | "transfer";
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
@@ -82,6 +85,7 @@ export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
|
82
85
|
aria-label={props.ariaLabel}
|
|
83
86
|
tabIndex={-1}
|
|
84
87
|
class="grid max-sm:grid-cols-2 sm:grid-cols-3 gap-2"
|
|
88
|
+
classList={{ "ks-transaction-account-picker": props.compact }}
|
|
85
89
|
onKeyDown={onKeyDown}
|
|
86
90
|
>
|
|
87
91
|
<For each={visible()}>
|
|
@@ -89,17 +93,29 @@ export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
|
89
93
|
const selected = () => props.value === a.id.toString();
|
|
90
94
|
const isTabStop = () => selected() || (!props.value && i() === 0);
|
|
91
95
|
return (
|
|
92
|
-
<
|
|
93
|
-
ref={(el) => (buttonRefs[i()] = el)}
|
|
96
|
+
<Button
|
|
97
|
+
ref={(el: HTMLButtonElement) => (buttonRefs[i()] = el)}
|
|
94
98
|
type="button"
|
|
95
99
|
role="radio"
|
|
96
100
|
aria-checked={selected()}
|
|
97
101
|
tabIndex={isTabStop() ? 0 : -1}
|
|
102
|
+
intent="secondary"
|
|
103
|
+
variant="clip1"
|
|
104
|
+
size="sm"
|
|
105
|
+
noGlow
|
|
106
|
+
noRipple
|
|
107
|
+
noScanline
|
|
98
108
|
onClick={() => props.onChange(a.id.toString())}
|
|
99
|
-
class="group flex items-center gap-2
|
|
109
|
+
class="group flex items-center gap-2 px-3 py-3 text-left text-sm transition-colors cursor-pointer"
|
|
100
110
|
classList={{
|
|
101
111
|
"border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_10%,transparent)] text-[var(--ks-accent-hover,#fcd34d)]":
|
|
102
|
-
selected(),
|
|
112
|
+
selected() && (!props.compact || !props.tone),
|
|
113
|
+
"border-[color-mix(in_srgb,var(--ks-success,#10b981)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-success,#10b981)_10%,transparent)] text-[var(--ks-success-fg,#34d399)]":
|
|
114
|
+
selected() && props.compact && props.tone === "income",
|
|
115
|
+
"border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)] text-[var(--ks-danger-fg,#f87171)]":
|
|
116
|
+
selected() && props.compact && props.tone === "expense",
|
|
117
|
+
"border-[color-mix(in_srgb,var(--ks-info,#38bdf8)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-info,#38bdf8)_10%,transparent)] text-[var(--ks-info,#38bdf8)]":
|
|
118
|
+
selected() && props.compact && props.tone === "transfer",
|
|
103
119
|
"border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_50%,transparent)] text-[var(--ks-fg-muted,#a1a1aa)] hover:border-[var(--ks-border-strong,#3f3f46)] hover:bg-[var(--ks-surface-raised,#1a1a1a)]":
|
|
104
120
|
!selected(),
|
|
105
121
|
}}
|
|
@@ -114,7 +130,7 @@ export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
|
114
130
|
}
|
|
115
131
|
/>
|
|
116
132
|
<span class="truncate">{a.name}</span>
|
|
117
|
-
</
|
|
133
|
+
</Button>
|
|
118
134
|
);
|
|
119
135
|
}}
|
|
120
136
|
</For>
|
|
@@ -23,6 +23,7 @@ export interface TransactionAccountFieldsProps {
|
|
|
23
23
|
transferFeeAmount: string;
|
|
24
24
|
transferFeeEnabled: boolean;
|
|
25
25
|
allowTransferFee: boolean;
|
|
26
|
+
compact?: boolean;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -43,6 +44,8 @@ export default function TransactionAccountFields(props: TransactionAccountFields
|
|
|
43
44
|
value={
|
|
44
45
|
props.category === "sale" ? props.destAccount : props.sourceAccount
|
|
45
46
|
}
|
|
47
|
+
compact={props.compact}
|
|
48
|
+
tone={props.category === "sale" ? "income" : "expense"}
|
|
46
49
|
onChange={(v) => {
|
|
47
50
|
if (props.category === "sale") {
|
|
48
51
|
props.setDestAccount(v);
|
|
@@ -299,7 +299,35 @@ export interface TransactionFormProps {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
export default function TransactionForm(props: TransactionFormProps) {
|
|
302
|
-
injectCSS(
|
|
302
|
+
injectCSS(
|
|
303
|
+
"ksui-transaction-form-compact",
|
|
304
|
+
`.ks-transaction-compact{display:flex;flex-direction:column;min-height:0;height:100%;font-family:var(--ks-font-body,"Inter", system-ui, sans-serif)}
|
|
305
|
+
.ks-transaction-compact form{display:flex!important;flex:1 1 auto!important;flex-direction:column!important;min-height:0!important;overflow:hidden!important}
|
|
306
|
+
.ks-transaction-compact-type-switcher{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.375rem;margin:0 1.25rem .25rem;padding:.375rem;border-radius:.75rem;background:color-mix(in srgb,var(--ks-fg,#ffffff) 5%,transparent)}
|
|
307
|
+
.ks-transaction-compact-type-switcher>button{min-height:2.25rem;border:0;border-radius:.5rem;padding:.5rem .25rem}
|
|
308
|
+
.ks-transaction-compact-type-switcher>button.ks-transaction-type-active{background:color-mix(in srgb,var(--ks-primary,#c9a961) 16%,transparent);color:var(--ks-primary,#c9a961)}
|
|
309
|
+
.ks-transaction-compact-body{display:flex!important;flex:1 1 auto!important;flex-direction:column!important;width:100%!important;min-height:0!important;overflow-x:hidden!important;overflow-y:auto!important;padding:1rem 1.25rem 1.5rem!important;gap:1rem}
|
|
310
|
+
.ks-transaction-compact-body>div{width:100%!important;flex:0 0 auto!important}
|
|
311
|
+
.ks-transaction-compact-footer{flex:0 0 auto!important}
|
|
312
|
+
.ks-transaction-compact-body>.ks-transaction-compact-amount{order:2;padding:0;background:transparent}
|
|
313
|
+
.ks-transaction-compact-body>.ks-transaction-compact-description{order:3}
|
|
314
|
+
.ks-transaction-compact-body>.ks-transaction-compact-date{order:4;padding:.75rem;border:1px solid color-mix(in srgb,var(--ks-border,rgba(39,39,42,0.5)) 70%,transparent);border-radius:.75rem;background:color-mix(in srgb,var(--ks-overlay,rgba(0,0,0,0.7)) 78%,transparent)}
|
|
315
|
+
.ks-transaction-compact-body>.ks-transaction-compact-account{order:5}
|
|
316
|
+
.ks-transaction-compact-body>.ks-transaction-compact-details-toggle{order:6;display:flex;align-items:center;gap:.75rem;width:100%;padding:.25rem 0;color:var(--ks-fg-subtle,#71717a)}
|
|
317
|
+
.ks-transaction-compact-body>button:before,.ks-transaction-compact-body>button:after{content:"";height:1px;flex:1;background:color-mix(in srgb,var(--ks-fg,#ffffff) 8%,transparent)}
|
|
318
|
+
.ks-transaction-compact-body>div:has(input[data-testid="transactions-form-amount"])>label,.ks-transaction-compact-body>div:has(input[data-testid="transactions-form-description"])>label{display:none}
|
|
319
|
+
.ks-transaction-compact-body input,.ks-transaction-compact-body select,.ks-transaction-compact-body textarea{border-radius:.625rem}
|
|
320
|
+
.ks-transaction-compact-body input[data-testid="transactions-form-amount"]{height:4rem;font-family:var(--ks-font-mono,ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace);font-size:3.25rem;line-height:1;text-align:center}
|
|
321
|
+
.ks-transaction-compact-body input[data-testid="transactions-form-description"]{background:transparent;border-color:var(--ks-border)}
|
|
322
|
+
.ks-transaction-compact-body>div:has(button[aria-label="Today"])>label{font-size:10px;display:flex;align-items:center;gap:.5rem;color:color-mix(in srgb,var(--ks-fg,#ffffff) 35%,transparent);text-transform:uppercase;letter-spacing:.12em}
|
|
323
|
+
.ks-transaction-compact-body>div:has(button[aria-label="Today"])>label:before{content:"◷";font-size:15px;color:var(--ks-fg-muted,#a1a1aa)}
|
|
324
|
+
.ks-transaction-compact-body [role="radiogroup"]{display:flex;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
|
|
325
|
+
.ks-transaction-compact-body [role="radiogroup"]::-webkit-scrollbar{display:none}
|
|
326
|
+
.ks-transaction-compact-body [role="radio"]{min-width:8rem}
|
|
327
|
+
.ks-transaction-compact:not(.ks-transaction-compact-details) .ks-transaction-compact-body>div:has([data-testid="form-payee-picker-trigger"]),.ks-transaction-compact:not(.ks-transaction-compact-details) .ks-transaction-compact-body>div:has([data-testid="subcategory-select"]),.ks-transaction-compact:not(.ks-transaction-compact-details) .ks-transaction-compact-body>div:has(textarea),.ks-transaction-compact:not(.ks-transaction-compact-details) .ks-transaction-compact-body>div:has([data-testid="mention-textarea"]),.ks-transaction-compact:not(.ks-transaction-compact-details) .ks-transaction-compact-body>div:has(button[aria-label="Add attachment"]){display:none}
|
|
328
|
+
.ks-transaction-compact-footer>div:last-child>button{min-height:2.75rem}
|
|
329
|
+
`,
|
|
330
|
+
);
|
|
303
331
|
const compact = () => props.layout === "compact";
|
|
304
332
|
const catConfig = () =>
|
|
305
333
|
CATEGORY_FORM[props.category] || CATEGORY_FORM.expense;
|
|
@@ -426,7 +454,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
426
454
|
return (
|
|
427
455
|
<div
|
|
428
456
|
class="relative flex flex-col flex-1 min-h-0"
|
|
429
|
-
classList={{ "ks-transaction-compact": compact() }}
|
|
457
|
+
classList={{ "ks-transaction-compact": compact(), "ks-transaction-compact-details": compact() && viewMode() === "advanced" }}
|
|
430
458
|
onDragEnter={handleDragEnter}
|
|
431
459
|
onDragLeave={handleDragLeave}
|
|
432
460
|
onDragOver={handleDragOver}
|
|
@@ -443,6 +471,39 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
443
471
|
</div>
|
|
444
472
|
</Show>
|
|
445
473
|
|
|
474
|
+
<Show when={compact() && !props.simpleMode}>
|
|
475
|
+
<div class="ks-transaction-compact-type-switcher" role="tablist" aria-label="Transaction type">
|
|
476
|
+
<For each={categoryOptions()}>
|
|
477
|
+
{(cat) => (
|
|
478
|
+
<Button
|
|
479
|
+
type="button"
|
|
480
|
+
role="tab"
|
|
481
|
+
aria-selected={props.category === cat}
|
|
482
|
+
data-testid={`transactions-form-category-${cat}`}
|
|
483
|
+
intent="secondary"
|
|
484
|
+
variant="ghost"
|
|
485
|
+
size="sm"
|
|
486
|
+
noGlow
|
|
487
|
+
noRipple
|
|
488
|
+
noScanline
|
|
489
|
+
class={`ks-transaction-type-button ks-transaction-type-${cat}`}
|
|
490
|
+
classList={{ "ks-transaction-type-active": props.category === cat }}
|
|
491
|
+
onClick={() => {
|
|
492
|
+
props.setCategory(cat);
|
|
493
|
+
props.setSourceAccount("");
|
|
494
|
+
props.setDestAccount("");
|
|
495
|
+
if (cat !== "business") {
|
|
496
|
+
props.setTransferFeeEnabled(false);
|
|
497
|
+
props.setTransferFeeAmount("");
|
|
498
|
+
}
|
|
499
|
+
}}
|
|
500
|
+
>
|
|
501
|
+
{CATEGORY_FORM[cat].label}
|
|
502
|
+
</Button>
|
|
503
|
+
)}
|
|
504
|
+
</For>
|
|
505
|
+
</div>
|
|
506
|
+
</Show>
|
|
446
507
|
<Show when={cameraOpen()}>
|
|
447
508
|
<CameraCapture
|
|
448
509
|
onCapture={(file) => {
|
|
@@ -471,56 +532,6 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
471
532
|
</div>
|
|
472
533
|
</Show>
|
|
473
534
|
|
|
474
|
-
<Show when={!props.simpleMode}>
|
|
475
|
-
<div>
|
|
476
|
-
<div class="text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold mb-2">
|
|
477
|
-
Type
|
|
478
|
-
</div>
|
|
479
|
-
<div
|
|
480
|
-
class="grid gap-2"
|
|
481
|
-
classList={{
|
|
482
|
-
"grid-cols-3": categoryOptions().length === 3,
|
|
483
|
-
"grid-cols-4": categoryOptions().length === 4,
|
|
484
|
-
}}
|
|
485
|
-
>
|
|
486
|
-
<For each={categoryOptions()}>
|
|
487
|
-
{(cat) => {
|
|
488
|
-
const cfg = CATEGORY_FORM[cat];
|
|
489
|
-
const tone = CATEGORY_TONE[cat];
|
|
490
|
-
const tc = TONE_CLASSES[tone.tone];
|
|
491
|
-
const Ico = tone.icon;
|
|
492
|
-
return (
|
|
493
|
-
<button
|
|
494
|
-
type="button"
|
|
495
|
-
data-testid={`transactions-form-category-${cat}`}
|
|
496
|
-
onClick={() => {
|
|
497
|
-
props.setCategory(cat);
|
|
498
|
-
props.setSourceAccount("");
|
|
499
|
-
props.setDestAccount("");
|
|
500
|
-
if (cat !== "business") {
|
|
501
|
-
props.setTransferFeeEnabled(false);
|
|
502
|
-
props.setTransferFeeAmount("");
|
|
503
|
-
}
|
|
504
|
-
}}
|
|
505
|
-
class="flex min-h-[42px] items-center justify-center gap-2 px-3 py-2 border text-sm transition-colors ks-hud-clip-button cursor-pointer active:opacity-80"
|
|
506
|
-
classList={{
|
|
507
|
-
[`${tc.bg} ${tc.border} ${tc.text}`]:
|
|
508
|
-
props.category === cat,
|
|
509
|
-
"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)]":
|
|
510
|
-
props.category !== cat,
|
|
511
|
-
}}
|
|
512
|
-
>
|
|
513
|
-
<Ico size={16} />
|
|
514
|
-
<span class="font-medium">{cfg.label}</span>
|
|
515
|
-
</button>
|
|
516
|
-
);
|
|
517
|
-
}}
|
|
518
|
-
</For>
|
|
519
|
-
</div>
|
|
520
|
-
<p class="text-[11px] text-[var(--ks-fg-subtle,#71717a)] mt-2">{catConfig().hint}</p>
|
|
521
|
-
</div>
|
|
522
|
-
</Show>
|
|
523
|
-
|
|
524
535
|
<Show when={props.category === "sale"}>
|
|
525
536
|
<SalesBodyEditor
|
|
526
537
|
items={props.saleItems}
|
|
@@ -537,6 +548,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
537
548
|
<Show
|
|
538
549
|
when={!(props.category === "sale" && props.saleItems.length > 0)}
|
|
539
550
|
>
|
|
551
|
+
<div class={compact() ? "ks-transaction-compact-amount" : undefined}>
|
|
540
552
|
<FormField label={compact() ? "" : "Amount *"}>
|
|
541
553
|
<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">
|
|
542
554
|
<span class="self-center text-3xl font-bold text-[var(--ks-fg-subtle,#71717a)] tabular-nums">
|
|
@@ -567,6 +579,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
567
579
|
</Show>
|
|
568
580
|
</div>
|
|
569
581
|
</FormField>
|
|
582
|
+
</div>
|
|
570
583
|
<Show
|
|
571
584
|
when={
|
|
572
585
|
props.category === "business" &&
|
|
@@ -604,6 +617,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
604
617
|
</Show>
|
|
605
618
|
</Show>
|
|
606
619
|
|
|
620
|
+
<div class={compact() ? "ks-transaction-compact-date" : undefined}>
|
|
607
621
|
<FormField label={compact() ? "TRANSACTION DATE" : "Date *"}>
|
|
608
622
|
<DatePicker
|
|
609
623
|
value={props.date}
|
|
@@ -616,6 +630,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
616
630
|
</p>
|
|
617
631
|
</Show>
|
|
618
632
|
</FormField>
|
|
633
|
+
</div>
|
|
619
634
|
|
|
620
635
|
<Show when={props.isBackdated}>
|
|
621
636
|
<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">
|
|
@@ -635,6 +650,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
635
650
|
</div>
|
|
636
651
|
</Show>
|
|
637
652
|
|
|
653
|
+
<div class={compact() ? "ks-transaction-compact-description" : undefined}>
|
|
638
654
|
<FormField label={compact() ? "" : "Description *"}>
|
|
639
655
|
<input
|
|
640
656
|
type="text"
|
|
@@ -646,6 +662,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
646
662
|
required
|
|
647
663
|
/>
|
|
648
664
|
</FormField>
|
|
665
|
+
</div>
|
|
649
666
|
|
|
650
667
|
<Show when={catConfig().showPayee}>
|
|
651
668
|
<div class="grid grid-cols-2 gap-4">
|
|
@@ -770,6 +787,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
770
787
|
/>
|
|
771
788
|
</Show>
|
|
772
789
|
|
|
790
|
+
<div class={compact() ? "ks-transaction-compact-account" : undefined}>
|
|
773
791
|
<TransactionAccountFields
|
|
774
792
|
category={props.category}
|
|
775
793
|
accounts={props.accounts}
|
|
@@ -785,7 +803,9 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
785
803
|
transferFeeAmount={props.transferFeeAmount}
|
|
786
804
|
transferFeeEnabled={props.transferFeeEnabled}
|
|
787
805
|
allowTransferFee={props.allowTransferFee}
|
|
806
|
+
compact={compact()}
|
|
788
807
|
/>
|
|
808
|
+
</div>
|
|
789
809
|
|
|
790
810
|
<FormField label="Notes">
|
|
791
811
|
<MentionTextarea
|
|
@@ -897,7 +917,7 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
897
917
|
</div>
|
|
898
918
|
|
|
899
919
|
<Show when={!props.simpleMode}>
|
|
900
|
-
<div class="flex justify-center pt-2">
|
|
920
|
+
<div class="flex justify-center pt-2" classList={{ "ks-transaction-compact-details-toggle": compact() }}>
|
|
901
921
|
<button
|
|
902
922
|
type="button"
|
|
903
923
|
onClick={() =>
|