@kahitsan/ksui 0.33.0 → 0.34.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.
- package/package.json +4 -1
- package/src/components/base/AccountAvatar.tsx +1 -1
- package/src/components/base/AddAttachmentTile.tsx +4 -4
- package/src/components/base/BadgeSelect.tsx +10 -10
- package/src/components/base/Button.tsx +70 -38
- package/src/components/base/CameraCapture.tsx +8 -8
- package/src/components/base/ChartLegend.tsx +3 -3
- package/src/components/base/CopyButton.tsx +1 -1
- package/src/components/base/DataTable.tsx +68 -72
- package/src/components/base/DatePicker.tsx +77 -82
- package/src/components/base/DateTile.tsx +7 -7
- package/src/components/base/DetailRow.tsx +2 -2
- package/src/components/base/Dropdown.tsx +15 -15
- package/src/components/base/ExistingAttachmentTile.tsx +6 -6
- package/src/components/base/EyebrowBadge.tsx +15 -15
- package/src/components/base/FormErrorBanner.tsx +1 -1
- package/src/components/base/FormField.tsx +2 -2
- package/src/components/base/ImageCropper.tsx +19 -19
- package/src/components/base/ImageViewer.tsx +7 -11
- package/src/components/base/KpiCard.tsx +7 -7
- package/src/components/base/Modal.tsx +10 -14
- package/src/components/base/ProgressBar.tsx +63 -59
- package/src/components/base/RadioCardGroup.tsx +13 -6
- package/src/components/base/SectionHeading.tsx +4 -4
- package/src/components/base/SegmentedFilter.tsx +60 -4
- package/src/components/base/SocialLinksBar.tsx +3 -15
- package/src/components/base/StatusIndicator.tsx +11 -11
- package/src/components/base/StatusPill.tsx +15 -15
- package/src/components/base/TagPill.tsx +1 -1
- package/src/components/base/ThemeToggle.tsx +12 -20
- package/src/components/base/Tooltip.tsx +2 -2
- package/src/components/composite/AccountRadioPicker.tsx +8 -4
- package/src/components/composite/ComboBox.tsx +51 -44
- package/src/components/composite/CustomRenderer.tsx +6 -13
- package/src/components/composite/FileField.tsx +10 -17
- package/src/components/composite/FlowGraph.tsx +33 -37
- package/src/components/composite/FlowRunner.tsx +11 -18
- package/src/components/composite/FormAdvancedSection.tsx +31 -31
- package/src/components/composite/LiveTimer.tsx +3 -3
- package/src/components/composite/MarkdownNotes.tsx +11 -11
- package/src/components/composite/MentionTextarea.tsx +10 -10
- package/src/components/composite/NotFound.tsx +7 -7
- package/src/components/composite/PaymentAccountPicker.tsx +19 -19
- package/src/components/composite/SalesBodyEditor.tsx +26 -26
- package/src/components/composite/SearchableSelect.tsx +14 -14
- package/src/components/composite/SecretReveal.tsx +5 -5
- package/src/components/composite/TransactionAccountFields.tsx +78 -0
- package/src/components/composite/TransactionForm.tsx +83 -169
- package/src/components/composite/TransactionPayableFields.tsx +97 -0
- package/src/components/composite/TransferAccountsPicker.tsx +20 -20
- package/src/components/composite/TransferFeeChip.tsx +2 -2
- package/src/components/composite/VoucherPicker.tsx +25 -25
- package/src/components/composite/resource/ResourceForm.tsx +1 -1
- package/src/components/composite/resource/ResourcePage.tsx +8 -8
- package/src/components/composite/resource/cells.tsx +6 -3
- package/src/utils/INPUT_CLASS.ts +6 -3
- package/src/utils/account-icons.ts +29 -7
- package/src/utils/highlight.tsx +5 -6
- package/src/utils/inject-css.ts +13 -0
- package/tailwind.js +11 -7
|
@@ -25,35 +25,28 @@ import {
|
|
|
25
25
|
type FlowNode,
|
|
26
26
|
type FlowState,
|
|
27
27
|
} from "../../utils/flow";
|
|
28
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
28
29
|
|
|
29
30
|
const STYLE_ID = "ksui-flow-runner-style";
|
|
30
|
-
|
|
31
|
-
function ensureStyle(): void {
|
|
32
|
-
if (typeof document === "undefined") return;
|
|
33
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
34
|
-
const style = document.createElement("style");
|
|
35
|
-
style.id = STYLE_ID;
|
|
36
|
-
style.textContent = `
|
|
31
|
+
const STYLE_CSS = `
|
|
37
32
|
.ksui-fr{display:flex;flex-direction:column;gap:0.875rem;color:var(--ksui-fr-fg,inherit);}
|
|
38
33
|
.ksui-fr-title{font-size:0.95rem;font-weight:600;margin:0;}
|
|
39
34
|
.ksui-fr-prompt,.ksui-fr-body{font-size:0.85rem;opacity:0.85;margin:0;}
|
|
40
35
|
.ksui-fr-field{display:flex;flex-direction:column;gap:0.25rem;}
|
|
41
36
|
.ksui-fr-label{font-size:0.78rem;font-weight:500;}
|
|
42
|
-
.ksui-fr-input{padding:0.5rem 0.625rem;border-radius:0.5rem;border:1px solid var(--ksui-fr-border,
|
|
37
|
+
.ksui-fr-input{padding:0.5rem 0.625rem;border-radius:0.5rem;border:1px solid var(--ksui-fr-border,var(--ks-input-border,#3f3f46));background:var(--ksui-fr-input-bg,var(--ks-input-bg,#18181b));color:inherit;font-size:0.85rem;}
|
|
43
38
|
.ksui-fr-actions{display:flex;gap:0.5rem;flex-wrap:wrap;}
|
|
44
|
-
.ksui-fr-btn{padding:0.5rem 0.875rem;border-radius:0.5rem;border:1px solid var(--ksui-fr-border,
|
|
39
|
+
.ksui-fr-btn{padding:0.5rem 0.875rem;border-radius:0.5rem;border:1px solid var(--ksui-fr-border,var(--ks-input-border,#3f3f46));background:var(--ksui-fr-btn-bg,var(--ks-surface-raised,#1a1a1a));color:inherit;font-size:0.82rem;cursor:pointer;}
|
|
45
40
|
.ksui-fr-btn:disabled{opacity:0.5;cursor:not-allowed;}
|
|
46
|
-
.ksui-fr-btn.primary{background:var(--ksui-fr-primary,#c9a961);color:var(--ksui-fr-primary-fg,#18181b);border-color:transparent;}
|
|
47
|
-
.ksui-fr-btn.danger{background:var(--ksui-fr-danger,#ef4444);color
|
|
41
|
+
.ksui-fr-btn.primary{background:var(--ksui-fr-primary,var(--ks-primary,#c9a961));color:var(--ksui-fr-primary-fg,#18181b);border-color:transparent;}
|
|
42
|
+
.ksui-fr-btn.danger{background:var(--ksui-fr-danger,var(--ks-danger,#ef4444));color:var(--ks-fg,#ffffff);border-color:transparent;}
|
|
48
43
|
.ksui-fr-msg{padding:0.625rem 0.75rem;border-radius:0.5rem;font-size:0.85rem;}
|
|
49
|
-
.ksui-fr-msg.info{background:rgba(
|
|
50
|
-
.ksui-fr-msg.success{background:rgba(
|
|
51
|
-
.ksui-fr-msg.error{background:rgba(239,68,68,0.12);}
|
|
52
|
-
.ksui-fr-error{padding:0.625rem 0.75rem;border-radius:0.5rem;font-size:0.82rem;background:rgba(239,68,68,0.12);border:1px solid
|
|
44
|
+
.ksui-fr-msg.info{background:var(--ks-info-bg,rgba(56,189,248,0.12));}
|
|
45
|
+
.ksui-fr-msg.success{background:var(--ks-success-bg,rgba(16,185,129,0.12));}
|
|
46
|
+
.ksui-fr-msg.error{background:var(--ks-danger-bg,rgba(239,68,68,0.12));}
|
|
47
|
+
.ksui-fr-error{padding:0.625rem 0.75rem;border-radius:0.5rem;font-size:0.82rem;background:var(--ks-danger-bg,rgba(239,68,68,0.12));border:1px solid color-mix(in srgb, var(--ks-danger, #ef4444) 30%, transparent);}
|
|
53
48
|
.ksui-fr-loading{font-size:0.82rem;opacity:0.7;}
|
|
54
49
|
`;
|
|
55
|
-
document.head.appendChild(style);
|
|
56
|
-
}
|
|
57
50
|
|
|
58
51
|
export interface FlowRunnerProps {
|
|
59
52
|
/** The first node the server handed the client (the flow entry). */
|
|
@@ -77,7 +70,7 @@ export interface FlowRunnerProps {
|
|
|
77
70
|
}
|
|
78
71
|
|
|
79
72
|
export const FlowRunner: Component<FlowRunnerProps> = (props) => {
|
|
80
|
-
|
|
73
|
+
injectCSS(STYLE_ID, STYLE_CSS);
|
|
81
74
|
const [node, setNode] = createSignal<FlowNode>(props.initialNode);
|
|
82
75
|
const [busy, setBusy] = createSignal(false);
|
|
83
76
|
const [error, setError] = createSignal<string | null>(null);
|
|
@@ -36,13 +36,13 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
36
36
|
return (
|
|
37
37
|
<div
|
|
38
38
|
data-testid="advanced-fields-container"
|
|
39
|
-
class="rounded-lg border border-
|
|
39
|
+
class="rounded-lg border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_60%,transparent)] bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_40%,transparent)] p-3 space-y-3"
|
|
40
40
|
>
|
|
41
|
-
<div class="flex items-center gap-2 text-[10px] uppercase tracking-widest text-
|
|
41
|
+
<div class="flex items-center gap-2 text-[10px] uppercase tracking-widest text-[var(--ks-fg-muted,#a1a1aa)] font-semibold">
|
|
42
42
|
<span>Advanced</span>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
-
<div class="rounded-lg border border-
|
|
45
|
+
<div class="rounded-lg border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-bg,#0a0a0a)_40%,transparent)] p-3">
|
|
46
46
|
<FormField label="Tax">
|
|
47
47
|
<SegmentedFilter
|
|
48
48
|
options={[
|
|
@@ -75,7 +75,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
75
75
|
: Math.round(amt * 0.12 * 100) / 100;
|
|
76
76
|
const total = props.taxType === "vat_exclusive" ? sub + vat : amt;
|
|
77
77
|
return (
|
|
78
|
-
<div class="mt-2 text-xs text-
|
|
78
|
+
<div class="mt-2 text-xs text-[var(--ks-fg-subtle,#71717a)] space-y-0.5 border-t border-[var(--ks-border,rgba(39,39,42,0.5))] pt-2">
|
|
79
79
|
<div class="flex justify-between">
|
|
80
80
|
<span>VATtable Sales</span>
|
|
81
81
|
<span>{formatPHP(sub)}</span>
|
|
@@ -84,7 +84,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
84
84
|
<span>VAT (12%)</span>
|
|
85
85
|
<span>{formatPHP(vat)}</span>
|
|
86
86
|
</div>
|
|
87
|
-
<div class="flex justify-between font-medium text-
|
|
87
|
+
<div class="flex justify-between font-medium text-[var(--ks-fg-muted,#a1a1aa)]">
|
|
88
88
|
<span>Total</span>
|
|
89
89
|
<span>{formatPHP(total)}</span>
|
|
90
90
|
</div>
|
|
@@ -96,13 +96,13 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
96
96
|
<Show
|
|
97
97
|
when={props.category === "expense" || props.category === "payable"}
|
|
98
98
|
>
|
|
99
|
-
<div class="mt-3 border-t border-
|
|
100
|
-
<label class="flex items-center gap-2 text-xs text-
|
|
99
|
+
<div class="mt-3 border-t border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] pt-3">
|
|
100
|
+
<label class="flex items-center gap-2 text-xs text-[var(--ks-fg-muted,#a1a1aa)] cursor-pointer select-none">
|
|
101
101
|
<input
|
|
102
102
|
type="checkbox"
|
|
103
103
|
checked={props.hasEwt}
|
|
104
104
|
onChange={(e) => props.setHasEwt(e.currentTarget.checked)}
|
|
105
|
-
class="h-4 w-4 accent-
|
|
105
|
+
class="h-4 w-4 accent-[var(--ks-primary,#c9a961)] cursor-pointer"
|
|
106
106
|
/>
|
|
107
107
|
<span>Has Expanded Withholding Tax</span>
|
|
108
108
|
</label>
|
|
@@ -116,7 +116,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
116
116
|
max="100"
|
|
117
117
|
value={props.ewtRate}
|
|
118
118
|
onInput={(e) => props.setEwtRate(e.currentTarget.value)}
|
|
119
|
-
class="w-full px-3 py-2 bg-
|
|
119
|
+
class="w-full px-3 py-2 bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_50%,transparent)] border border-[var(--ks-border-strong,#3f3f46)] text-[var(--ks-fg,#ffffff)] text-sm rounded-md focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_50%,transparent)]"
|
|
120
120
|
placeholder="e.g. 1, 2, 5, 10, 15"
|
|
121
121
|
/>
|
|
122
122
|
</FormField>
|
|
@@ -133,7 +133,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
133
133
|
const rate = parseFloat(props.ewtRate);
|
|
134
134
|
const ewt = Math.round(amt * rate) / 100;
|
|
135
135
|
return (
|
|
136
|
-
<div class="text-xs text-
|
|
136
|
+
<div class="text-xs text-[var(--ks-fg-subtle,#71717a)] border-t border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] pt-2">
|
|
137
137
|
<div class="flex justify-between">
|
|
138
138
|
<span>EWT ({rate}%)</span>
|
|
139
139
|
<span>{formatPHP(ewt)}</span>
|
|
@@ -149,7 +149,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
149
149
|
</div>
|
|
150
150
|
|
|
151
151
|
<div
|
|
152
|
-
class="rounded-lg border border-
|
|
152
|
+
class="rounded-lg border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-bg,#0a0a0a)_40%,transparent)] p-4 relative"
|
|
153
153
|
classList={{ "opacity-60": !props.canShare }}
|
|
154
154
|
title={
|
|
155
155
|
props.canShare
|
|
@@ -159,10 +159,10 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
159
159
|
>
|
|
160
160
|
<div class="flex items-center justify-between min-h-[44px]">
|
|
161
161
|
<div class="flex items-center gap-2">
|
|
162
|
-
<Lock size={14} class="text-
|
|
162
|
+
<Lock size={14} class="text-[var(--ks-fg-subtle,#71717a)]" />
|
|
163
163
|
<div>
|
|
164
|
-
<span class="text-sm text-
|
|
165
|
-
<p class="text-[10px] text-
|
|
164
|
+
<span class="text-sm text-[var(--ks-fg-muted,#a1a1aa)]">Private transaction</span>
|
|
165
|
+
<p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)]">
|
|
166
166
|
{props.canShare
|
|
167
167
|
? "Hidden from others unless shared"
|
|
168
168
|
: "Locked — needs members.list_basic permission"}
|
|
@@ -195,15 +195,15 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
195
195
|
</div>
|
|
196
196
|
|
|
197
197
|
<Show when={props.isPrivate && props.canShare}>
|
|
198
|
-
<div class="mt-4 pt-3 border-t border-
|
|
199
|
-
<p class="text-[10px] text-
|
|
198
|
+
<div class="mt-4 pt-3 border-t border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)]">
|
|
199
|
+
<p class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] mb-3">
|
|
200
200
|
Always visible to:{" "}
|
|
201
|
-
<span class="text-
|
|
201
|
+
<span class="text-[var(--ks-fg-muted,#a1a1aa)]">
|
|
202
202
|
you (creator), org admins, superusers
|
|
203
203
|
</span>
|
|
204
204
|
</p>
|
|
205
205
|
|
|
206
|
-
<span class="text-xs text-
|
|
206
|
+
<span class="text-xs text-[var(--ks-fg-subtle,#71717a)] block mb-2">
|
|
207
207
|
Share with role
|
|
208
208
|
</span>
|
|
209
209
|
<div class="flex gap-2 mb-3 flex-wrap">
|
|
@@ -240,15 +240,15 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
240
240
|
}}
|
|
241
241
|
class="px-3 py-2 text-xs rounded-lg border cursor-pointer min-h-[36px] capitalize transition-colors active:opacity-80"
|
|
242
242
|
classList={{
|
|
243
|
-
"border-
|
|
243
|
+
"border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_40%,transparent)] bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_10%,transparent)] text-[var(--ks-accent,#fbbf24)]":
|
|
244
244
|
selected(),
|
|
245
|
-
"border-
|
|
245
|
+
"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)]":
|
|
246
246
|
!selected(),
|
|
247
247
|
}}
|
|
248
248
|
>
|
|
249
249
|
All {role.label}s
|
|
250
250
|
<Show when={membersInRole().length > 0}>
|
|
251
|
-
<span class="text-
|
|
251
|
+
<span class="text-[var(--ks-fg-subtle,#71717a)] ml-1">
|
|
252
252
|
({membersInRole().length})
|
|
253
253
|
</span>
|
|
254
254
|
</Show>
|
|
@@ -258,7 +258,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
258
258
|
</For>
|
|
259
259
|
</div>
|
|
260
260
|
|
|
261
|
-
<span class="text-xs text-
|
|
261
|
+
<span class="text-xs text-[var(--ks-fg-subtle,#71717a)] block mb-2">
|
|
262
262
|
Or select people
|
|
263
263
|
</span>
|
|
264
264
|
<div class="space-y-0.5 max-h-[150px] overflow-y-auto">
|
|
@@ -281,25 +281,25 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
281
281
|
<label
|
|
282
282
|
class="flex items-center gap-3 text-sm py-2 px-2 rounded-lg min-h-[40px] transition-colors"
|
|
283
283
|
classList={{
|
|
284
|
-
"text-
|
|
284
|
+
"text-[var(--ks-fg-muted,#a1a1aa)] cursor-pointer hover:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_30%,transparent)] active:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_50%,transparent)]":
|
|
285
285
|
!coveringRole(),
|
|
286
|
-
"text-
|
|
286
|
+
"text-[var(--ks-fg-subtle,#71717a)] cursor-not-allowed bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_40%,transparent)]":
|
|
287
287
|
!!coveringRole(),
|
|
288
288
|
}}
|
|
289
289
|
>
|
|
290
290
|
<div
|
|
291
291
|
class="w-5 h-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors"
|
|
292
292
|
classList={{
|
|
293
|
-
"border-
|
|
293
|
+
"border-[var(--ks-primary,#c9a961)] bg-[var(--ks-primary,#c9a961)]":
|
|
294
294
|
checked() && !coveringRole(),
|
|
295
|
-
"border-
|
|
295
|
+
"border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_40%,transparent)] bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_40%,transparent)]":
|
|
296
296
|
checked() && !!coveringRole(),
|
|
297
|
-
"border-
|
|
297
|
+
"border-[var(--ks-border-strong,#3f3f46)] bg-transparent": !checked(),
|
|
298
298
|
}}
|
|
299
299
|
>
|
|
300
300
|
<Show when={checked()}>
|
|
301
301
|
<svg
|
|
302
|
-
class="w-3 h-3 text-
|
|
302
|
+
class="w-3 h-3 text-[var(--ks-fg-on-primary,#0a0a0a)]"
|
|
303
303
|
fill="none"
|
|
304
304
|
viewBox="0 0 24 24"
|
|
305
305
|
stroke="currentColor"
|
|
@@ -336,12 +336,12 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
336
336
|
<Show
|
|
337
337
|
when={coveringRole()}
|
|
338
338
|
fallback={
|
|
339
|
-
<span class="text-[10px] text-
|
|
339
|
+
<span class="text-[10px] text-[var(--ks-fg-subtle,#71717a)] capitalize">
|
|
340
340
|
{m.role}
|
|
341
341
|
</span>
|
|
342
342
|
}
|
|
343
343
|
>
|
|
344
|
-
<span class="text-[10px] text-
|
|
344
|
+
<span class="text-[10px] text-[color-mix(in_srgb,var(--ks-primary,#c9a961)_70%,transparent)]">
|
|
345
345
|
via All {coveringRole()!.label}s
|
|
346
346
|
</span>
|
|
347
347
|
</Show>
|
|
@@ -355,7 +355,7 @@ export default function FormAdvancedSection(props: FormAdvancedSectionProps) {
|
|
|
355
355
|
props.orgMembers.length === 0
|
|
356
356
|
}
|
|
357
357
|
>
|
|
358
|
-
<p class="text-xs text-
|
|
358
|
+
<p class="text-xs text-[var(--ks-fg-subtle,#71717a)] py-2">Loading members...</p>
|
|
359
359
|
</Show>
|
|
360
360
|
</div>
|
|
361
361
|
</div>
|
|
@@ -223,7 +223,7 @@ const SCENARIO_CONFIGS: Record<Scenario, ScenarioConfig> = {
|
|
|
223
223
|
},
|
|
224
224
|
[SCENARIO_COMPLETED]: {
|
|
225
225
|
position: "left",
|
|
226
|
-
colorClass: "border border-
|
|
226
|
+
colorClass: "border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_60%,transparent)] text-[var(--ks-fg-muted,#a1a1aa)] hover:border-[var(--ks-fg-subtle,#71717a)]",
|
|
227
227
|
label: "Completed",
|
|
228
228
|
hidePercentage: false,
|
|
229
229
|
shimmer: false,
|
|
@@ -238,8 +238,8 @@ const SCENARIO_CONFIGS: Record<Scenario, ScenarioConfig> = {
|
|
|
238
238
|
};
|
|
239
239
|
|
|
240
240
|
const COLOR_GREEN = "border border-green-600/60 text-green-400 hover:border-green-500";
|
|
241
|
-
const COLOR_AMBER = "border border-
|
|
242
|
-
const COLOR_RED = "border border-red-600/60 text-
|
|
241
|
+
const COLOR_AMBER = "border border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_60%,transparent)] text-[var(--ks-accent,#fbbf24)] hover:border-[var(--ks-accent-hover,#fcd34d)]";
|
|
242
|
+
const COLOR_RED = "border border-red-600/60 text-[var(--ks-danger-fg,#f87171)] hover:border-[var(--ks-danger,#ef4444)]";
|
|
243
243
|
|
|
244
244
|
const LiveTimer: Component<LiveTimerProps> = (props) => {
|
|
245
245
|
const [local, others] = splitProps(props, [
|
|
@@ -288,7 +288,7 @@ function MentionHoverCard(props: { clientId: number; name: string }): JSX.Elemen
|
|
|
288
288
|
<span class="relative inline-block" onMouseEnter={handleEnter} onMouseLeave={handleLeave}>
|
|
289
289
|
<a
|
|
290
290
|
href={`/clients/${props.clientId}`}
|
|
291
|
-
class="inline-flex items-center rounded bg-
|
|
291
|
+
class="inline-flex items-center rounded bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_50%,transparent)] px-1.5 py-0.5 text-[0.85em] text-[var(--ks-success-fg,#34d399)] hover:text-[var(--ks-success,#10b981)] hover:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_70%,transparent)] transition-colors"
|
|
292
292
|
data-testid="mention-chip"
|
|
293
293
|
aria-describedby={open() && data() !== null ? hoverCardId : undefined}
|
|
294
294
|
onFocus={handleFocus}
|
|
@@ -301,26 +301,26 @@ function MentionHoverCard(props: { clientId: number; name: string }): JSX.Elemen
|
|
|
301
301
|
id={hoverCardId}
|
|
302
302
|
role="tooltip"
|
|
303
303
|
data-testid="mention-hover-card"
|
|
304
|
-
class="absolute left-0 top-full z-50 mt-1 inline-block min-w-[14rem] max-w-xs rounded-md border border-
|
|
304
|
+
class="absolute left-0 top-full z-50 mt-1 inline-block min-w-[14rem] max-w-xs rounded-md border border-[var(--ks-border-strong,#3f3f46)] bg-[var(--ks-overlay-surface,#18181b)] px-3 py-2 text-xs text-[var(--ks-fg,#ffffff)] shadow-lg"
|
|
305
305
|
onMouseEnter={() => clearTimers()}
|
|
306
306
|
onMouseLeave={handleLeave}
|
|
307
307
|
>
|
|
308
|
-
<Show when={!loading() && data()} fallback={<span class="text-
|
|
308
|
+
<Show when={!loading() && data()} fallback={<span class="text-[var(--ks-fg-muted,#a1a1aa)]">Loading…</span>}>
|
|
309
309
|
{(c) => (
|
|
310
310
|
<span class="block">
|
|
311
|
-
<span class="block font-medium text-
|
|
311
|
+
<span class="block font-medium text-[var(--ks-fg,#ffffff)]">{c().name_raw}</span>
|
|
312
312
|
<Show when={c().email}>
|
|
313
|
-
<span class="mt-1 block text-
|
|
313
|
+
<span class="mt-1 block text-[var(--ks-fg-muted,#a1a1aa)]" data-testid="mention-hover-email">
|
|
314
314
|
{c().email}
|
|
315
315
|
</span>
|
|
316
316
|
</Show>
|
|
317
317
|
<Show when={c().phone}>
|
|
318
|
-
<span class="block text-
|
|
318
|
+
<span class="block text-[var(--ks-fg-muted,#a1a1aa)]" data-testid="mention-hover-phone">
|
|
319
319
|
{c().phone}
|
|
320
320
|
</span>
|
|
321
321
|
</Show>
|
|
322
322
|
<Show when={!c().email && !c().phone}>
|
|
323
|
-
<span class="mt-1 block text-
|
|
323
|
+
<span class="mt-1 block text-[var(--ks-fg-subtle,#71717a)]">No contact details on file.</span>
|
|
324
324
|
</Show>
|
|
325
325
|
</span>
|
|
326
326
|
)}
|
|
@@ -339,7 +339,7 @@ function MentionChip(props: { name: string; clientId: number | null }): JSX.Elem
|
|
|
339
339
|
when={props.clientId !== null}
|
|
340
340
|
fallback={
|
|
341
341
|
<span
|
|
342
|
-
class="inline-flex items-center rounded bg-
|
|
342
|
+
class="inline-flex items-center rounded bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_40%,transparent)] px-1.5 py-0.5 text-[0.85em] text-[var(--ks-fg-subtle,#71717a)]"
|
|
343
343
|
data-testid="mention-chip-unresolved"
|
|
344
344
|
>
|
|
345
345
|
@{props.name}
|
|
@@ -351,7 +351,7 @@ function MentionChip(props: { name: string; clientId: number | null }): JSX.Elem
|
|
|
351
351
|
fallback={
|
|
352
352
|
<a
|
|
353
353
|
href={`/clients/${props.clientId}`}
|
|
354
|
-
class="inline-flex items-center rounded bg-
|
|
354
|
+
class="inline-flex items-center rounded bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_50%,transparent)] px-1.5 py-0.5 text-[0.85em] text-[var(--ks-success-fg,#34d399)] hover:text-[var(--ks-success,#10b981)] hover:bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_70%,transparent)] transition-colors"
|
|
355
355
|
data-testid="mention-chip"
|
|
356
356
|
>
|
|
357
357
|
@{props.name}
|
|
@@ -384,7 +384,7 @@ function RenderInline(props: { tokens: InlineToken[]; searchQuery?: string }): J
|
|
|
384
384
|
);
|
|
385
385
|
if (t.kind === "code")
|
|
386
386
|
return (
|
|
387
|
-
<code class="px-1 py-0.5 rounded bg-
|
|
387
|
+
<code class="px-1 py-0.5 rounded bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_60%,transparent)] text-[0.9em] text-[var(--ks-fg-muted,#a1a1aa)]">
|
|
388
388
|
{props.searchQuery ? highlightMatch(t.value, props.searchQuery) : t.value}
|
|
389
389
|
</code>
|
|
390
390
|
);
|
|
@@ -394,7 +394,7 @@ function RenderInline(props: { tokens: InlineToken[]; searchQuery?: string }): J
|
|
|
394
394
|
href={t.href}
|
|
395
395
|
target="_blank"
|
|
396
396
|
rel="noopener noreferrer"
|
|
397
|
-
class="text-
|
|
397
|
+
class="text-[var(--ks-success-fg,#34d399)] hover:text-[var(--ks-success,#10b981)] underline"
|
|
398
398
|
>
|
|
399
399
|
<RenderInline tokens={t.children} searchQuery={props.searchQuery} />
|
|
400
400
|
</a>
|
|
@@ -35,9 +35,9 @@ export interface MentionTextareaProps {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const CHIP_BASE_CLASSES =
|
|
38
|
-
"inline-flex items-baseline align-baseline rounded bg-
|
|
38
|
+
"inline-flex items-baseline align-baseline rounded bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_15%,transparent)] text-[var(--ks-accent-hover,#fcd34d)] px-1.5 py-px text-[0.9em] mx-px";
|
|
39
39
|
const CHIP_UNRESOLVED_CLASSES =
|
|
40
|
-
"inline-flex items-baseline align-baseline rounded bg-
|
|
40
|
+
"inline-flex items-baseline align-baseline rounded bg-[color-mix(in_srgb,var(--ks-input-border,#3f3f46)_40%,transparent)] text-[var(--ks-fg-muted,#a1a1aa)] px-1.5 py-px text-[0.9em] mx-px";
|
|
41
41
|
|
|
42
42
|
function buildMentionChip(name: string, idStr: string): HTMLSpanElement {
|
|
43
43
|
const span = document.createElement("span");
|
|
@@ -418,7 +418,7 @@ export default function MentionTextarea(props: MentionTextareaProps): JSX.Elemen
|
|
|
418
418
|
<Show when={isEmpty() && props.placeholder}>
|
|
419
419
|
<div
|
|
420
420
|
aria-hidden="true"
|
|
421
|
-
class={`${props.class ?? ""} pointer-events-none absolute inset-0 text-sm !text-
|
|
421
|
+
class={`${props.class ?? ""} pointer-events-none absolute inset-0 text-sm !text-[var(--ks-fg-subtle,#71717a)]`}
|
|
422
422
|
>
|
|
423
423
|
{props.placeholder}
|
|
424
424
|
</div>
|
|
@@ -430,17 +430,17 @@ export default function MentionTextarea(props: MentionTextareaProps): JSX.Elemen
|
|
|
430
430
|
<div
|
|
431
431
|
ref={popupRef}
|
|
432
432
|
data-testid="mention-popup"
|
|
433
|
-
class="z-[120] rounded-md border border-
|
|
433
|
+
class="z-[120] rounded-md border border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_95%,transparent)] backdrop-blur shadow-xl overflow-hidden flex flex-col"
|
|
434
434
|
style={popupStyle()}
|
|
435
435
|
>
|
|
436
436
|
<Show when={loading() && results().length === 0}>
|
|
437
|
-
<div class="px-3 py-3 text-xs text-
|
|
437
|
+
<div class="px-3 py-3 text-xs text-[var(--ks-fg-subtle,#71717a)] flex items-center gap-2">
|
|
438
438
|
<Loader2 size={12} class="animate-spin" />
|
|
439
439
|
Searching clients…
|
|
440
440
|
</div>
|
|
441
441
|
</Show>
|
|
442
442
|
<Show when={!loading() && results().length === 0}>
|
|
443
|
-
<div class="px-3 py-3 text-xs text-
|
|
443
|
+
<div class="px-3 py-3 text-xs text-[var(--ks-fg-subtle,#71717a)]" data-testid="mention-popup-empty">
|
|
444
444
|
{query() ? `No clients match "${query()}"` : "Type to search clients…"}
|
|
445
445
|
</div>
|
|
446
446
|
</Show>
|
|
@@ -464,14 +464,14 @@ export default function MentionTextarea(props: MentionTextareaProps): JSX.Elemen
|
|
|
464
464
|
onMouseEnter={() => setActiveIdx(idx())}
|
|
465
465
|
onClick={() => insertSelected(c)}
|
|
466
466
|
class={`w-full text-left px-3 py-2 transition-colors flex items-start gap-2 cursor-pointer ${
|
|
467
|
-
idx() === activeIdx() ? "bg-
|
|
467
|
+
idx() === activeIdx() ? "bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_15%,transparent)]" : "hover:bg-[color-mix(in_srgb,var(--ks-warning,#f59e0b)_10%,transparent)]"
|
|
468
468
|
}`}
|
|
469
469
|
>
|
|
470
|
-
<UserRound size={14} class="text-
|
|
470
|
+
<UserRound size={14} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0 mt-0.5" />
|
|
471
471
|
<span class="flex-1 min-w-0">
|
|
472
|
-
<span class="block text-sm text-
|
|
472
|
+
<span class="block text-sm text-[var(--ks-fg,#ffffff)] truncate">{c.name_raw}</span>
|
|
473
473
|
<Show when={c.email || c.phone}>
|
|
474
|
-
<span class="block text-[11px] text-
|
|
474
|
+
<span class="block text-[11px] text-[var(--ks-fg-subtle,#71717a)] truncate">
|
|
475
475
|
{[c.email, c.phone].filter(Boolean).join(" · ")}
|
|
476
476
|
</span>
|
|
477
477
|
</Show>
|
|
@@ -63,10 +63,10 @@ export default function NotFound(props: NotFoundProps): JSX.Element {
|
|
|
63
63
|
const showTitle = () => local.title !== "";
|
|
64
64
|
const buttonClass =
|
|
65
65
|
"inline-flex items-center justify-center gap-2 rounded-md border " +
|
|
66
|
-
"border-
|
|
67
|
-
"text-
|
|
68
|
-
"hover:bg-
|
|
69
|
-
"focus-visible:ring-
|
|
66
|
+
"border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_60%,transparent)] bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_20%,transparent)] px-5 py-2.5 text-sm font-semibold " +
|
|
67
|
+
"text-[var(--ks-accent,#fbbf24)] transition-colors hover:border-[var(--ks-accent,#fbbf24)] " +
|
|
68
|
+
"hover:bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_30%,transparent)] focus:outline-none focus-visible:ring-2 " +
|
|
69
|
+
"focus-visible:ring-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_60%,transparent)]";
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
72
|
<div
|
|
@@ -83,16 +83,16 @@ export default function NotFound(props: NotFoundProps): JSX.Element {
|
|
|
83
83
|
</Show>
|
|
84
84
|
|
|
85
85
|
<Show when={showTitle()}>
|
|
86
|
-
<h1 class="text-4xl md:text-6xl font-bold text-
|
|
86
|
+
<h1 class="text-4xl md:text-6xl font-bold text-[var(--ks-accent,#fbbf24)] mb-4">
|
|
87
87
|
{local.title || "404"}
|
|
88
88
|
</h1>
|
|
89
89
|
</Show>
|
|
90
90
|
|
|
91
|
-
<h2 class="text-xl md:text-2xl font-bold text-
|
|
91
|
+
<h2 class="text-xl md:text-2xl font-bold text-[var(--ks-fg,#ffffff)] mb-3">
|
|
92
92
|
{local.heading || "Page Not Found"}
|
|
93
93
|
</h2>
|
|
94
94
|
|
|
95
|
-
<p class="text-sm text-
|
|
95
|
+
<p class="text-sm text-[var(--ks-fg-muted,#a1a1aa)] max-w-md mb-8">
|
|
96
96
|
{local.message ||
|
|
97
97
|
"The page you're looking for doesn't exist or has been moved."}
|
|
98
98
|
</p>
|
|
@@ -197,12 +197,12 @@ export default function PaymentAccountPicker(props: PaymentAccountPickerProps):
|
|
|
197
197
|
? "Ask an admin to grant access to a financial account before charging."
|
|
198
198
|
: undefined
|
|
199
199
|
}
|
|
200
|
-
class="w-full flex items-center gap-2 px-3 py-2.5 rounded-lg bg-
|
|
200
|
+
class="w-full flex items-center gap-2 px-3 py-2.5 rounded-lg bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_30%,transparent)] border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] hover:border-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_40%,transparent)] hover:bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_5%,transparent)] transition-colors text-sm text-left cursor-pointer disabled:cursor-not-allowed disabled:opacity-60"
|
|
201
201
|
aria-haspopup="listbox"
|
|
202
202
|
aria-expanded={open()}
|
|
203
203
|
>
|
|
204
204
|
<Show when={loading()} fallback={null}>
|
|
205
|
-
<Loader2 size={14} class="animate-spin text-
|
|
205
|
+
<Loader2 size={14} class="animate-spin text-[var(--ks-fg-subtle,#71717a)] shrink-0" aria-hidden="true" />
|
|
206
206
|
</Show>
|
|
207
207
|
<Show when={!loading()}>
|
|
208
208
|
<Show
|
|
@@ -212,13 +212,13 @@ export default function PaymentAccountPicker(props: PaymentAccountPickerProps):
|
|
|
212
212
|
when={noAccounts()}
|
|
213
213
|
fallback={
|
|
214
214
|
<>
|
|
215
|
-
<Banknote size={16} class="shrink-0 text-
|
|
216
|
-
<span class="text-
|
|
215
|
+
<Banknote size={16} class="shrink-0 text-[var(--ks-fg-muted,#a1a1aa)]" aria-hidden="true" />
|
|
216
|
+
<span class="text-[var(--ks-fg-subtle,#71717a)] italic">Choose payment account</span>
|
|
217
217
|
</>
|
|
218
218
|
}
|
|
219
219
|
>
|
|
220
|
-
<Banknote size={16} class="shrink-0 text-
|
|
221
|
-
<span class="text-
|
|
220
|
+
<Banknote size={16} class="shrink-0 text-[var(--ks-fg-subtle,#71717a)]" aria-hidden="true" />
|
|
221
|
+
<span class="text-[var(--ks-fg-subtle,#71717a)] italic">No accessible accounts</span>
|
|
222
222
|
</Show>
|
|
223
223
|
}
|
|
224
224
|
>
|
|
@@ -226,8 +226,8 @@ export default function PaymentAccountPicker(props: PaymentAccountPickerProps):
|
|
|
226
226
|
<>
|
|
227
227
|
<AccountAvatar account={acct()} size={28} />
|
|
228
228
|
<span class="flex-1 min-w-0">
|
|
229
|
-
<span class="block truncate text-
|
|
230
|
-
<span class="block truncate text-[11px] text-
|
|
229
|
+
<span class="block truncate text-[var(--ks-fg,#ffffff)] font-medium">{acct().name}</span>
|
|
230
|
+
<span class="block truncate text-[11px] text-[var(--ks-fg-subtle,#71717a)]">
|
|
231
231
|
{TYPE_LABELS[acct().type] ?? acct().type}
|
|
232
232
|
</span>
|
|
233
233
|
</span>
|
|
@@ -242,33 +242,33 @@ export default function PaymentAccountPicker(props: PaymentAccountPickerProps):
|
|
|
242
242
|
<div
|
|
243
243
|
ref={popupRef}
|
|
244
244
|
data-testid="payment-account-picker-popup"
|
|
245
|
-
class="z-[100] rounded-md border border-
|
|
245
|
+
class="z-[100] rounded-md border border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_95%,transparent)] backdrop-blur shadow-xl overflow-hidden flex flex-col"
|
|
246
246
|
style={popupStyle()}
|
|
247
247
|
>
|
|
248
|
-
<div class="px-3 py-2 border-b border-
|
|
249
|
-
<Banknote size={14} class="text-
|
|
250
|
-
<span class="text-xs uppercase tracking-widest text-
|
|
248
|
+
<div class="px-3 py-2 border-b border-[var(--ks-border,rgba(39,39,42,0.5))] flex items-center gap-2">
|
|
249
|
+
<Banknote size={14} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0" aria-hidden="true" />
|
|
250
|
+
<span class="text-xs uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-bold">
|
|
251
251
|
Payment account
|
|
252
252
|
</span>
|
|
253
253
|
<Show when={loading()}>
|
|
254
|
-
<Loader2 size={14} class="animate-spin text-
|
|
254
|
+
<Loader2 size={14} class="animate-spin text-[var(--ks-fg-subtle,#71717a)] ml-auto shrink-0" />
|
|
255
255
|
</Show>
|
|
256
256
|
</div>
|
|
257
257
|
<div class="flex-1 overflow-y-auto">
|
|
258
258
|
<Show when={error()}>
|
|
259
|
-
<div role="status" class="px-3 py-2 text-xs text-
|
|
259
|
+
<div role="status" class="px-3 py-2 text-xs text-[var(--ks-danger-fg,#f87171)]">
|
|
260
260
|
{error()}
|
|
261
261
|
</div>
|
|
262
262
|
</Show>
|
|
263
263
|
<Show when={!loading() && !error() && accounts().length === 0}>
|
|
264
|
-
<div role="status" class="px-3 py-4 text-xs text-
|
|
264
|
+
<div role="status" class="px-3 py-4 text-xs text-[var(--ks-fg-subtle,#71717a)] text-center">
|
|
265
265
|
No accessible accounts. Ask an admin to grant access.
|
|
266
266
|
</div>
|
|
267
267
|
</Show>
|
|
268
268
|
<For each={sections()}>
|
|
269
269
|
{([type, list]) => (
|
|
270
270
|
<div>
|
|
271
|
-
<div class="px-3 pt-3 pb-1 text-[10px] uppercase tracking-widest text-
|
|
271
|
+
<div class="px-3 pt-3 pb-1 text-[10px] uppercase tracking-widest text-[var(--ks-fg-subtle,#71717a)] font-semibold">
|
|
272
272
|
{TYPE_LABELS[type] ?? type}
|
|
273
273
|
</div>
|
|
274
274
|
<ul
|
|
@@ -285,14 +285,14 @@ export default function PaymentAccountPicker(props: PaymentAccountPickerProps):
|
|
|
285
285
|
type="button"
|
|
286
286
|
data-testid={`payment-account-picker-result-${a.id}`}
|
|
287
287
|
onClick={() => select(a)}
|
|
288
|
-
class="w-full text-left px-3 py-2 hover:bg-
|
|
288
|
+
class="w-full text-left px-3 py-2 hover:bg-[color-mix(in_srgb,var(--ks-accent,#fbbf24)_10%,transparent)] transition-colors flex items-center gap-2 cursor-pointer"
|
|
289
289
|
>
|
|
290
290
|
<AccountAvatar account={a} size={28} />
|
|
291
|
-
<span class="flex-1 min-w-0 text-sm text-
|
|
291
|
+
<span class="flex-1 min-w-0 text-sm text-[var(--ks-fg,#ffffff)] truncate">
|
|
292
292
|
{a.name}
|
|
293
293
|
</span>
|
|
294
294
|
<Show when={selected()}>
|
|
295
|
-
<span class="text-
|
|
295
|
+
<span class="text-[var(--ks-accent,#fbbf24)] shrink-0">✓</span>
|
|
296
296
|
</Show>
|
|
297
297
|
</button>
|
|
298
298
|
</li>
|