@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.
- 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 +29 -36
- 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/components/composite/resource/form-spec.ts +141 -0
- package/src/components/composite/resource/route-adapter.ts +63 -0
- package/src/components/composite/resource/route-spec.test.ts +125 -0
- package/src/components/composite/resource/route-spec.ts +158 -0
- package/src/index.ts +8 -0
- 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
|
@@ -13,33 +13,25 @@ import type { JSX } from "solid-js";
|
|
|
13
13
|
import { splitProps } from "solid-js";
|
|
14
14
|
import Sun from "lucide-solid/icons/sun";
|
|
15
15
|
import Moon from "lucide-solid/icons/moon";
|
|
16
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
16
17
|
|
|
17
18
|
const THEME_TOGGLE_STYLE_ID = "ks-theme-toggle-inline-style";
|
|
18
19
|
const THEME_TOGGLE_CSS = `
|
|
19
20
|
.ks-theme-toggle { background: none; border: none; padding: 2px; cursor: pointer; display: flex; align-items: center; }
|
|
20
|
-
.ks-theme-toggle-track { position: relative; display: flex; align-items: center; justify-content: space-between; width: 52px; height: 28px; border-radius: 14px; background-color: #3f3f46; padding: 0 2px; transition: background-color 0.3s ease; }
|
|
21
|
-
.ks-theme-toggle-track[data-active] { background-color: #
|
|
21
|
+
.ks-theme-toggle-track { position: relative; display: flex; align-items: center; justify-content: space-between; width: 52px; height: 28px; border-radius: 14px; background-color: var(--ks-border-strong, #3f3f46); padding: 0 2px; transition: background-color 0.3s ease; }
|
|
22
|
+
.ks-theme-toggle-track[data-active] { background-color: var(--ks-primary-hover, #d4b876); }
|
|
22
23
|
.ks-theme-toggle-icon { position: relative; z-index: 2; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; pointer-events: none; transition: color 0.3s ease; }
|
|
23
|
-
.ks-theme-toggle-track::after { content: ''; position: absolute; left: 2px; top: 2px; width: 24px; height: 24px; border-radius: 50%; background-color: #
|
|
24
|
-
.ks-theme-toggle-track[data-active]::after { transform: translateX(24px); background-color: #ffffff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); }
|
|
25
|
-
.ks-theme-toggle-icon-moon { color: #fbbf24; }
|
|
26
|
-
.ks-theme-toggle-icon-sun { color:
|
|
27
|
-
.ks-theme-toggle-track[data-active] .ks-theme-toggle-icon-moon { color:
|
|
28
|
-
.ks-theme-toggle-track[data-active] .ks-theme-toggle-icon-sun { color: #
|
|
29
|
-
.ks-theme-toggle:hover .ks-theme-toggle-track { background-color: #
|
|
30
|
-
.ks-theme-toggle:hover .ks-theme-toggle-track[data-active] { background-color: #
|
|
24
|
+
.ks-theme-toggle-track::after { content: ''; position: absolute; left: 2px; top: 2px; width: 24px; height: 24px; border-radius: 50%; background-color: var(--ks-border-strong, #3f3f46); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, background-color 0.3s ease; z-index: 1; }
|
|
25
|
+
.ks-theme-toggle-track[data-active]::after { transform: translateX(24px); background-color: var(--ks-fg, #ffffff); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); }
|
|
26
|
+
.ks-theme-toggle-icon-moon { color: var(--ks-accent, #fbbf24); }
|
|
27
|
+
.ks-theme-toggle-icon-sun { color: color-mix(in srgb, var(--ks-fg, #ffffff) 30%, transparent); }
|
|
28
|
+
.ks-theme-toggle-track[data-active] .ks-theme-toggle-icon-moon { color: color-mix(in srgb, var(--ks-accent, #fbbf24) 30%, transparent); }
|
|
29
|
+
.ks-theme-toggle-track[data-active] .ks-theme-toggle-icon-sun { color: var(--ks-accent, #fbbf24); }
|
|
30
|
+
.ks-theme-toggle:hover .ks-theme-toggle-track { background-color: var(--ks-border-strong, #3f3f46); }
|
|
31
|
+
.ks-theme-toggle:hover .ks-theme-toggle-track[data-active] { background-color: var(--ks-primary-hover, #d4b876); }
|
|
31
32
|
@media (prefers-reduced-motion: reduce) { .ks-theme-toggle-track, .ks-theme-toggle-track::after, .ks-theme-toggle-icon { transition: none; } }
|
|
32
33
|
`;
|
|
33
34
|
|
|
34
|
-
function ensureThemeToggleStyle(): void {
|
|
35
|
-
if (typeof document === "undefined") return;
|
|
36
|
-
if (document.getElementById(THEME_TOGGLE_STYLE_ID)) return;
|
|
37
|
-
const el = document.createElement("style");
|
|
38
|
-
el.id = THEME_TOGGLE_STYLE_ID;
|
|
39
|
-
el.textContent = THEME_TOGGLE_CSS;
|
|
40
|
-
document.head.appendChild(el);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
export type ThemeToggleValue = "dark" | "light";
|
|
44
36
|
|
|
45
37
|
export interface ThemeToggleProps
|
|
@@ -69,7 +61,7 @@ const defaultAriaLabel = (next: ThemeToggleValue): string => `Switch to ${next}
|
|
|
69
61
|
* next value through `onToggle`. The parent owns the theme source of truth.
|
|
70
62
|
*/
|
|
71
63
|
export default function ThemeToggle(props: ThemeToggleProps): JSX.Element {
|
|
72
|
-
|
|
64
|
+
injectCSS(THEME_TOGGLE_STYLE_ID, THEME_TOGGLE_CSS);
|
|
73
65
|
|
|
74
66
|
const [local, rest] = splitProps(props, [
|
|
75
67
|
"value",
|
|
@@ -46,7 +46,7 @@ interface TooltipProps {
|
|
|
46
46
|
// prefers describedby on the focusable trigger itself; since the
|
|
47
47
|
// trigger is `props.children` (any element) and Solid has no
|
|
48
48
|
// cloneElement, the trigger-level wiring lives with the caller in
|
|
49
|
-
// specific cases that need it. See
|
|
49
|
+
// specific cases that need it. See issue 627 for the broader sweep that
|
|
50
50
|
// introduces a directive-based wiring API.
|
|
51
51
|
export default function Tooltip(props: TooltipProps): JSX.Element {
|
|
52
52
|
const tooltipId = props.id ?? createUniqueId();
|
|
@@ -62,7 +62,7 @@ export default function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
62
62
|
<span
|
|
63
63
|
role="tooltip"
|
|
64
64
|
id={tooltipId}
|
|
65
|
-
class={`pointer-events-none absolute px-2 py-1 rounded-md bg-
|
|
65
|
+
class={`pointer-events-none absolute px-2 py-1 rounded-md bg-[var(--ks-overlay-surface,#18181b)] border border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_80%,transparent)] text-[11px] text-[var(--ks-fg,#ffffff)] opacity-0 group-hover/tt:opacity-100 group-focus-within/tt:opacity-100 transition-opacity duration-150 delay-150 z-50 shadow-lg ${
|
|
66
66
|
align() === "start" ? "left-0" : "left-1/2 -translate-x-1/2"
|
|
67
67
|
} ${props.wrap ? "whitespace-normal w-max max-w-[260px]" : "whitespace-nowrap"} ${
|
|
68
68
|
placement() === "bottom" ? "top-full mt-1" : "bottom-full mb-1"
|
|
@@ -73,7 +73,7 @@ export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
|
73
73
|
onInput={(e) => props.onChange(e.currentTarget.value)}
|
|
74
74
|
aria-label={props.ariaLabel}
|
|
75
75
|
placeholder="Account ID (financial-accounts module unavailable)"
|
|
76
|
-
class="w-full bg-
|
|
76
|
+
class="w-full bg-[color-mix(in_srgb,var(--ks-surface,#0f0f0f)_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-accent,#fbbf24)_50%,transparent)]"
|
|
77
77
|
/>
|
|
78
78
|
}
|
|
79
79
|
>
|
|
@@ -98,16 +98,20 @@ export default function AccountRadioPicker(props: AccountRadioPickerProps) {
|
|
|
98
98
|
onClick={() => props.onChange(a.id.toString())}
|
|
99
99
|
class="group flex items-center gap-2 rounded-lg border px-3 py-3 text-left text-sm transition-colors cursor-pointer ks-hud-clip-top-left-bottom-right"
|
|
100
100
|
classList={{
|
|
101
|
-
"border-
|
|
101
|
+
"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
102
|
selected(),
|
|
103
|
-
"border-
|
|
103
|
+
"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
104
|
!selected(),
|
|
105
105
|
}}
|
|
106
106
|
>
|
|
107
107
|
<AccountAvatar
|
|
108
108
|
account={a}
|
|
109
109
|
size={24}
|
|
110
|
-
iconClass={
|
|
110
|
+
iconClass={
|
|
111
|
+
selected()
|
|
112
|
+
? "text-[var(--ks-accent-hover,#fcd34d)]"
|
|
113
|
+
: "text-[var(--ks-fg-muted,#a1a1aa)]"
|
|
114
|
+
}
|
|
111
115
|
/>
|
|
112
116
|
<span class="truncate">{a.name}</span>
|
|
113
117
|
</button>
|
|
@@ -178,20 +178,20 @@ function SingleComboBox<T>(props: ComboBoxSingleProps<T>): JSX.Element {
|
|
|
178
178
|
data-testid={tid("trigger")}
|
|
179
179
|
disabled={props.disabled}
|
|
180
180
|
onClick={() => !props.disabled && eng.setOpen((o) => !o)}
|
|
181
|
-
class="w-full flex items-center gap-2 px-3 py-2.5 rounded-lg bg-
|
|
181
|
+
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-primary,#c9a961)_40%,transparent)] hover:bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_5%,transparent)] transition-colors text-sm text-left cursor-pointer disabled:cursor-not-allowed disabled:opacity-60"
|
|
182
182
|
aria-haspopup="listbox"
|
|
183
183
|
aria-expanded={eng.open()}
|
|
184
184
|
>
|
|
185
|
-
<Icon size={16} class="shrink-0 text-
|
|
186
|
-
<Show when={triggerLabel()} fallback={<span class="text-
|
|
185
|
+
<Icon size={16} class="shrink-0 text-[var(--ks-fg-muted,#a1a1aa)]" />
|
|
186
|
+
<Show when={triggerLabel()} fallback={<span class="text-[var(--ks-fg-subtle,#71717a)] italic">{placeholder()}</span>}>
|
|
187
187
|
<span class="flex-1 min-w-0">
|
|
188
|
-
<span class="block truncate text-
|
|
188
|
+
<span class="block truncate text-[var(--ks-fg,#ffffff)] font-medium">{triggerLabel()}</span>
|
|
189
189
|
</span>
|
|
190
190
|
<button
|
|
191
191
|
type="button"
|
|
192
192
|
data-testid={tid("clear")}
|
|
193
193
|
onClick={clear}
|
|
194
|
-
class="shrink-0 p-1 rounded text-
|
|
194
|
+
class="shrink-0 p-1 rounded text-[var(--ks-fg-subtle,#71717a)] hover:text-[var(--ks-danger-fg,#f87171)] hover:bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)] transition-colors cursor-pointer"
|
|
195
195
|
title="Clear"
|
|
196
196
|
aria-label={`Clear ${props.noun}`}
|
|
197
197
|
>
|
|
@@ -205,11 +205,11 @@ function SingleComboBox<T>(props: ComboBoxSingleProps<T>): JSX.Element {
|
|
|
205
205
|
<div
|
|
206
206
|
ref={popupRef}
|
|
207
207
|
data-testid={tid("popup")}
|
|
208
|
-
class="z-[10000] rounded-md border border-
|
|
208
|
+
class="z-[10000] 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"
|
|
209
209
|
style={eng.popupStyle()}
|
|
210
210
|
>
|
|
211
|
-
<div class="px-2 py-2 border-b border-
|
|
212
|
-
<Search size={14} class="text-
|
|
211
|
+
<div class="px-2 py-2 border-b border-[var(--ks-border-subtle,rgba(39,39,42,0.25))] flex items-center gap-2">
|
|
212
|
+
<Search size={14} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0 ml-1" />
|
|
213
213
|
<input
|
|
214
214
|
ref={inputRef}
|
|
215
215
|
type="text"
|
|
@@ -222,20 +222,20 @@ function SingleComboBox<T>(props: ComboBoxSingleProps<T>): JSX.Element {
|
|
|
222
222
|
value={eng.query()}
|
|
223
223
|
onInput={(e) => eng.setQuery(e.currentTarget.value)}
|
|
224
224
|
placeholder={props.onCreate ? `Search or add a new ${props.noun}…` : `Search ${props.noun}s…`}
|
|
225
|
-
class="w-full px-1 py-1 text-sm bg-transparent text-
|
|
225
|
+
class="w-full px-1 py-1 text-sm bg-transparent text-[var(--ks-fg,#ffffff)] placeholder:text-[var(--ks-input-placeholder,#71717a)] focus:outline-none"
|
|
226
226
|
/>
|
|
227
227
|
<Show when={eng.loading()}>
|
|
228
|
-
<Loader2 size={14} class="animate-spin text-
|
|
228
|
+
<Loader2 size={14} class="animate-spin text-[var(--ks-fg-subtle,#71717a)] mr-1 shrink-0" />
|
|
229
229
|
</Show>
|
|
230
230
|
</div>
|
|
231
231
|
<div class="flex-1 overflow-y-auto">
|
|
232
232
|
<Show when={eng.error()}>
|
|
233
|
-
<div role="status" class="px-3 py-2 text-xs text-
|
|
233
|
+
<div role="status" class="px-3 py-2 text-xs text-[var(--ks-danger-fg,#f87171)]">
|
|
234
234
|
{eng.error()}
|
|
235
235
|
</div>
|
|
236
236
|
</Show>
|
|
237
237
|
<Show when={!eng.loading() && eng.results().length === 0 && !showCreateOption() && !eng.error()}>
|
|
238
|
-
<div role="status" class="px-3 py-4 text-xs text-
|
|
238
|
+
<div role="status" class="px-3 py-4 text-xs text-[var(--ks-fg-subtle,#71717a)] text-center">
|
|
239
239
|
{eng.trimmedQuery() ? "No matches" : "Start typing or pick from your list…"}
|
|
240
240
|
</div>
|
|
241
241
|
</Show>
|
|
@@ -258,21 +258,21 @@ function SingleComboBox<T>(props: ComboBoxSingleProps<T>): JSX.Element {
|
|
|
258
258
|
type="button"
|
|
259
259
|
data-testid={`${tid("result")}-${props.idOf(item)}`}
|
|
260
260
|
onClick={() => select(item)}
|
|
261
|
-
class="w-full text-left px-3 py-2 hover:bg-
|
|
261
|
+
class="w-full text-left px-3 py-2 hover:bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_10%,transparent)] transition-colors flex items-start gap-2 cursor-pointer"
|
|
262
262
|
>
|
|
263
|
-
<Icon size={14} class="text-
|
|
263
|
+
<Icon size={14} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0 mt-0.5" />
|
|
264
264
|
<span class="flex-1 min-w-0">
|
|
265
|
-
<span class="block text-sm text-
|
|
265
|
+
<span class="block text-sm text-[var(--ks-fg,#ffffff)] truncate">
|
|
266
266
|
{highlightMatch(props.labelOf(item), eng.debouncedQuery().trim())}
|
|
267
267
|
</span>
|
|
268
268
|
<Show when={secondary}>
|
|
269
|
-
<span class="block text-[11px] text-
|
|
269
|
+
<span class="block text-[11px] text-[var(--ks-fg-subtle,#71717a)] truncate">
|
|
270
270
|
{highlightMatch(secondary!, eng.debouncedQuery().trim())}
|
|
271
271
|
</span>
|
|
272
272
|
</Show>
|
|
273
273
|
</span>
|
|
274
274
|
<Show when={isSel()}>
|
|
275
|
-
<span class="text-
|
|
275
|
+
<span class="text-[var(--ks-accent,#fbbf24)] text-xs shrink-0 mt-0.5">✓</span>
|
|
276
276
|
</Show>
|
|
277
277
|
</button>
|
|
278
278
|
</li>
|
|
@@ -282,21 +282,21 @@ function SingleComboBox<T>(props: ComboBoxSingleProps<T>): JSX.Element {
|
|
|
282
282
|
</ul>
|
|
283
283
|
</Show>
|
|
284
284
|
<Show when={showCreateOption()}>
|
|
285
|
-
<div class="border-t border-
|
|
285
|
+
<div class="border-t border-[var(--ks-border-subtle,rgba(39,39,42,0.25))]">
|
|
286
286
|
<button
|
|
287
287
|
type="button"
|
|
288
288
|
data-testid={tid("create")}
|
|
289
289
|
onClick={createAndSelect}
|
|
290
290
|
disabled={eng.creating()}
|
|
291
|
-
class="w-full text-left px-3 py-2.5 hover:bg-
|
|
291
|
+
class="w-full text-left px-3 py-2.5 hover:bg-[color-mix(in_srgb,var(--ks-success,#10b981)_10%,transparent)] transition-colors flex items-center gap-2 cursor-pointer disabled:cursor-wait disabled:opacity-60"
|
|
292
292
|
>
|
|
293
293
|
<Show
|
|
294
294
|
when={!eng.creating()}
|
|
295
|
-
fallback={<Loader2 size={14} class="animate-spin text-
|
|
295
|
+
fallback={<Loader2 size={14} class="animate-spin text-[var(--ks-success-fg,#34d399)] shrink-0" />}
|
|
296
296
|
>
|
|
297
|
-
<UserPlus size={14} class="text-
|
|
297
|
+
<UserPlus size={14} class="text-[var(--ks-success-fg,#34d399)] shrink-0" />
|
|
298
298
|
</Show>
|
|
299
|
-
<span class="text-sm text-
|
|
299
|
+
<span class="text-sm text-[var(--ks-success-fg,#34d399)]">
|
|
300
300
|
New {props.noun} "<span class="font-medium">{eng.trimmedQuery()}</span>"
|
|
301
301
|
</span>
|
|
302
302
|
</button>
|
|
@@ -435,10 +435,11 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
435
435
|
const isEmpty = () => props.value.length === 0;
|
|
436
436
|
|
|
437
437
|
const wrapperTone = () => {
|
|
438
|
-
if (props.disabled)
|
|
438
|
+
if (props.disabled)
|
|
439
|
+
return "opacity-60 cursor-not-allowed bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_30%,transparent)] border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)]";
|
|
439
440
|
if (props.invalid)
|
|
440
|
-
return "bg-
|
|
441
|
-
return "bg-
|
|
441
|
+
return "bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_5%,transparent)] border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_40%,transparent)] hover:bg-[color-mix(in_srgb,var(--ks-danger,#ef4444)_10%,transparent)] hover:border-[color-mix(in_srgb,var(--ks-danger,#ef4444)_60%,transparent)]";
|
|
442
|
+
return "bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_30%,transparent)] border-[color-mix(in_srgb,var(--ks-border-strong,#3f3f46)_50%,transparent)] hover:border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_40%,transparent)] focus-within:border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_60%,transparent)]";
|
|
442
443
|
};
|
|
443
444
|
|
|
444
445
|
return (
|
|
@@ -469,12 +470,12 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
469
470
|
data-testid={`${tid("chip")}-${idOf(item)}`}
|
|
470
471
|
class={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs ${
|
|
471
472
|
primary()
|
|
472
|
-
? "border-
|
|
473
|
-
: "border-
|
|
473
|
+
? "border-[color-mix(in_srgb,var(--ks-primary,#c9a961)_50%,transparent)] bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_10%,transparent)] text-[var(--ks-accent,#fbbf24)]"
|
|
474
|
+
: "border-[var(--ks-border-strong,#3f3f46)] bg-[color-mix(in_srgb,var(--ks-surface-raised,#1a1a1a)_40%,transparent)] text-[var(--ks-fg,#ffffff)]"
|
|
474
475
|
}`}
|
|
475
476
|
>
|
|
476
477
|
<Show when={primary()}>
|
|
477
|
-
<Star size={10} class="text-
|
|
478
|
+
<Star size={10} class="text-[var(--ks-accent,#fbbf24)] shrink-0" aria-label="Primary" />
|
|
478
479
|
</Show>
|
|
479
480
|
<Show
|
|
480
481
|
when={props.primaryStar}
|
|
@@ -510,7 +511,7 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
510
511
|
removeFromPool(item);
|
|
511
512
|
}}
|
|
512
513
|
aria-label={`Remove ${props.labelOf(item)}`}
|
|
513
|
-
class="shrink-0 rounded-full p-0.5 text-
|
|
514
|
+
class="shrink-0 rounded-full p-0.5 text-[var(--ks-fg-muted,#a1a1aa)] hover:text-[var(--ks-danger-fg,#f87171)] hover:bg-red-500/10 transition-colors cursor-pointer"
|
|
514
515
|
>
|
|
515
516
|
<X size={11} />
|
|
516
517
|
</button>
|
|
@@ -520,7 +521,7 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
520
521
|
}}
|
|
521
522
|
</For>
|
|
522
523
|
<Show when={isEmpty()}>
|
|
523
|
-
<Icon size={14} class={`shrink-0 ml-1 ${props.invalid ? "text-
|
|
524
|
+
<Icon size={14} class={`shrink-0 ml-1 ${props.invalid ? "text-[var(--ks-danger-fg,#f87171)]" : "text-[var(--ks-fg-muted,#a1a1aa)]"}`} />
|
|
524
525
|
</Show>
|
|
525
526
|
<input
|
|
526
527
|
ref={inputRef}
|
|
@@ -535,8 +536,10 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
535
536
|
if (!eng.open()) eng.setOpen(true);
|
|
536
537
|
}}
|
|
537
538
|
onKeyDown={onKeyDown}
|
|
538
|
-
class={`flex-1 min-w-[120px] bg-transparent outline-none text-sm text-
|
|
539
|
-
props.invalid && isEmpty()
|
|
539
|
+
class={`flex-1 min-w-[120px] bg-transparent outline-none text-sm text-[var(--ks-fg,#ffffff)] ${
|
|
540
|
+
props.invalid && isEmpty()
|
|
541
|
+
? "placeholder-[color-mix(in_srgb,var(--ks-danger-fg,#f87171)_80%,transparent)] italic"
|
|
542
|
+
: "placeholder-[var(--ks-input-placeholder,#71717a)]"
|
|
540
543
|
}`}
|
|
541
544
|
/>
|
|
542
545
|
</div>
|
|
@@ -548,20 +551,20 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
548
551
|
data-testid={tid("popup")}
|
|
549
552
|
role="listbox"
|
|
550
553
|
aria-label={`${props.noun} search results`}
|
|
551
|
-
class="z-[110] rounded-md border border-
|
|
554
|
+
class="z-[110] 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"
|
|
552
555
|
style={eng.popupStyle()}
|
|
553
556
|
>
|
|
554
557
|
<Show when={eng.error()}>
|
|
555
|
-
<div role="status" class="px-3 py-2 text-xs text-
|
|
558
|
+
<div role="status" class="px-3 py-2 text-xs text-[var(--ks-danger-fg,#f87171)] border-b border-[var(--ks-border-subtle,rgba(39,39,42,0.25))]">
|
|
556
559
|
{eng.error()}
|
|
557
560
|
</div>
|
|
558
561
|
</Show>
|
|
559
562
|
<div class="flex-1 overflow-y-auto">
|
|
560
563
|
<Show when={eng.loading() && eng.results().length === 0}>
|
|
561
|
-
<div role="status" class="px-3 py-3 text-xs text-
|
|
564
|
+
<div role="status" class="px-3 py-3 text-xs text-[var(--ks-fg-subtle,#71717a)] italic">Searching…</div>
|
|
562
565
|
</Show>
|
|
563
566
|
<Show when={displayOptions().length === 0 && !eng.loading()}>
|
|
564
|
-
<div role="status" class="px-3 py-3 text-xs text-
|
|
567
|
+
<div role="status" class="px-3 py-3 text-xs text-[var(--ks-fg-subtle,#71717a)] italic">
|
|
565
568
|
{eng.trimmedQuery() ? `No matching ${props.noun}s.` : `Start typing to find a ${props.noun}…`}
|
|
566
569
|
</div>
|
|
567
570
|
</Show>
|
|
@@ -580,17 +583,19 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
580
583
|
onMouseEnter={() => setFocusedIdx(0)}
|
|
581
584
|
onClick={() => void createAndAdd()}
|
|
582
585
|
disabled={eng.creating()}
|
|
583
|
-
class={`w-full flex items-start gap-2 px-3 py-2 text-left text-sm transition-colors border-b border-
|
|
584
|
-
focusedIdx() === 0
|
|
586
|
+
class={`w-full flex items-start gap-2 px-3 py-2 text-left text-sm transition-colors border-b border-[var(--ks-border-subtle,rgba(39,39,42,0.25))] ${
|
|
587
|
+
focusedIdx() === 0
|
|
588
|
+
? "bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_15%,transparent)] text-[var(--ks-accent,#fbbf24)]"
|
|
589
|
+
: "text-[var(--ks-fg,#ffffff)] hover:bg-[var(--ks-surface-raised,#1a1a1a)]"
|
|
585
590
|
}`}
|
|
586
591
|
>
|
|
587
592
|
<Show
|
|
588
593
|
when={!eng.creating()}
|
|
589
|
-
fallback={<Loader2 size={14} class="animate-spin text-
|
|
594
|
+
fallback={<Loader2 size={14} class="animate-spin text-[var(--ks-success-fg,#34d399)] shrink-0 mt-0.5" />}
|
|
590
595
|
>
|
|
591
|
-
<UserPlus size={14} class="text-
|
|
596
|
+
<UserPlus size={14} class="text-[var(--ks-success-fg,#34d399)] shrink-0 mt-0.5" />
|
|
592
597
|
</Show>
|
|
593
|
-
<span class="flex-1 text-
|
|
598
|
+
<span class="flex-1 text-[var(--ks-success-fg,#34d399)]">
|
|
594
599
|
New {props.noun} "<span class="font-medium">{eng.trimmedQuery()}</span>"
|
|
595
600
|
</span>
|
|
596
601
|
</button>
|
|
@@ -609,16 +614,18 @@ function MultiComboBox<T>(props: ComboBoxMultiProps<T>): JSX.Element {
|
|
|
609
614
|
onMouseEnter={() => setFocusedIdx(displayIdx())}
|
|
610
615
|
onClick={() => addToPool(item)}
|
|
611
616
|
class={`w-full flex items-start gap-2 px-3 py-2 text-left text-sm transition-colors ${
|
|
612
|
-
isFocused()
|
|
617
|
+
isFocused()
|
|
618
|
+
? "bg-[color-mix(in_srgb,var(--ks-primary,#c9a961)_15%,transparent)] text-[var(--ks-accent,#fbbf24)]"
|
|
619
|
+
: "text-[var(--ks-fg,#ffffff)] hover:bg-[var(--ks-surface-raised,#1a1a1a)]"
|
|
613
620
|
}`}
|
|
614
621
|
>
|
|
615
|
-
<Icon size={14} class="text-
|
|
622
|
+
<Icon size={14} class="text-[var(--ks-fg-subtle,#71717a)] shrink-0 mt-0.5" />
|
|
616
623
|
<span class="flex-1 min-w-0">
|
|
617
624
|
<span class="block truncate font-medium">
|
|
618
625
|
{highlightMatch(props.labelOf(item), eng.debouncedQuery().trim())}
|
|
619
626
|
</span>
|
|
620
627
|
<Show when={secondary()}>
|
|
621
|
-
<span class="block truncate text-[11px] text-
|
|
628
|
+
<span class="block truncate text-[11px] text-[var(--ks-fg-subtle,#71717a)]">
|
|
622
629
|
{highlightMatch(secondary()!, eng.debouncedQuery().trim())}
|
|
623
630
|
</span>
|
|
624
631
|
</Show>
|
|
@@ -16,22 +16,15 @@ import {
|
|
|
16
16
|
validateConsumes,
|
|
17
17
|
type RendererProps,
|
|
18
18
|
} from "../../utils/renderers";
|
|
19
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
19
20
|
|
|
20
21
|
const STYLE_ID = "ksui-custom-renderer-style";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const style = document.createElement("style");
|
|
26
|
-
style.id = STYLE_ID;
|
|
27
|
-
// Unscoped ksui-* classes + CSS custom properties so a host can retint without
|
|
28
|
-
// forking; no Tailwind, no host-brand classes (standalone-library rule).
|
|
29
|
-
style.textContent = `
|
|
30
|
-
.ksui-cr-fallback{display:flex;align-items:center;gap:0.5rem;padding:0.625rem 0.75rem;border-radius:0.5rem;font-size:0.8125rem;background:var(--ksui-cr-fallback-bg,rgba(245,158,11,0.08));border:1px solid var(--ksui-cr-fallback-border,rgba(245,158,11,0.25));color:var(--ksui-cr-fallback-fg,#fbbf24);}
|
|
22
|
+
// Unscoped ksui-* classes + CSS custom properties so a host can retint without
|
|
23
|
+
// forking; no Tailwind, no host-brand classes (standalone-library rule).
|
|
24
|
+
const STYLE_CSS = `
|
|
25
|
+
.ksui-cr-fallback{display:flex;align-items:center;gap:0.5rem;padding:0.625rem 0.75rem;border-radius:0.5rem;font-size:0.8125rem;background:var(--ksui-cr-fallback-bg,rgba(245,158,11,0.08));border:1px solid var(--ksui-cr-fallback-border,rgba(245,158,11,0.25));color:var(--ksui-cr-fallback-fg,var(--ks-warning-fg,#fbbf24));}
|
|
31
26
|
.ksui-cr-fallback svg{flex:0 0 auto;}
|
|
32
27
|
`;
|
|
33
|
-
document.head.appendChild(style);
|
|
34
|
-
}
|
|
35
28
|
|
|
36
29
|
export interface CustomRendererProps {
|
|
37
30
|
/** Registered renderer id to look up (§8). */
|
|
@@ -52,7 +45,7 @@ export interface CustomRendererProps {
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
const DefaultFallback: Component<{ id: string; reason: string }> = (props) => {
|
|
55
|
-
|
|
48
|
+
injectCSS(STYLE_ID, STYLE_CSS);
|
|
56
49
|
return (
|
|
57
50
|
<div class="ksui-cr-fallback" role="status" data-testid="ksui-cr-fallback">
|
|
58
51
|
<AlertTriangle size={14} />
|
|
@@ -19,6 +19,7 @@ import UploadCloud from "lucide-solid/icons/cloud-upload";
|
|
|
19
19
|
import FileIcon from "lucide-solid/icons/file";
|
|
20
20
|
import ImageOff from "lucide-solid/icons/image-off";
|
|
21
21
|
import X from "lucide-solid/icons/x";
|
|
22
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
22
23
|
|
|
23
24
|
/** The opaque asset handle the field stores as its value (§11). */
|
|
24
25
|
export interface AssetHandle {
|
|
@@ -32,29 +33,21 @@ export interface AssetHandle {
|
|
|
32
33
|
export type FileFieldStatus = "empty" | "uploading" | "done" | "failed";
|
|
33
34
|
|
|
34
35
|
const STYLE_ID = "ksui-file-field-style";
|
|
35
|
-
|
|
36
|
-
function ensureStyle(): void {
|
|
37
|
-
if (typeof document === "undefined") return;
|
|
38
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
39
|
-
const style = document.createElement("style");
|
|
40
|
-
style.id = STYLE_ID;
|
|
41
|
-
style.textContent = `
|
|
36
|
+
const STYLE_CSS = `
|
|
42
37
|
.ksui-ff{display:flex;flex-direction:column;gap:0.5rem;}
|
|
43
|
-
.ksui-ff-drop{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.375rem;padding:1.25rem;border-radius:0.625rem;border:1.5px dashed var(--ksui-ff-border,
|
|
44
|
-
.ksui-ff-drop.dragging{border-color:var(--ksui-ff-accent,#c9a961);background:var(--ksui-ff-accent-bg,
|
|
38
|
+
.ksui-ff-drop{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.375rem;padding:1.25rem;border-radius:0.625rem;border:1.5px dashed var(--ksui-ff-border,var(--ks-input-border,#3f3f46));background:var(--ksui-ff-bg,var(--ks-input-bg,#18181b));color:var(--ksui-ff-fg,inherit);cursor:pointer;text-align:center;font-size:0.82rem;transition:border-color .15s,background .15s;}
|
|
39
|
+
.ksui-ff-drop.dragging{border-color:var(--ksui-ff-accent,var(--ks-primary,#c9a961));background:var(--ksui-ff-accent-bg,color-mix(in srgb, var(--ks-primary, #c9a961) 8%, transparent));}
|
|
45
40
|
.ksui-ff-drop:disabled{opacity:0.5;cursor:not-allowed;}
|
|
46
41
|
.ksui-ff-hint{font-size:0.72rem;opacity:0.6;}
|
|
47
|
-
.ksui-ff-card{display:flex;align-items:center;gap:0.625rem;padding:0.5rem 0.625rem;border-radius:0.5rem;border:1px solid var(--ksui-ff-border,
|
|
48
|
-
.ksui-ff-thumb{width:2.5rem;height:2.5rem;border-radius:0.375rem;object-fit:cover;flex:0 0 auto;background:
|
|
42
|
+
.ksui-ff-card{display:flex;align-items:center;gap:0.625rem;padding:0.5rem 0.625rem;border-radius:0.5rem;border:1px solid var(--ksui-ff-border,var(--ks-input-border,#3f3f46));background:var(--ksui-ff-bg,var(--ks-input-bg,#18181b));}
|
|
43
|
+
.ksui-ff-thumb{width:2.5rem;height:2.5rem;border-radius:0.375rem;object-fit:cover;flex:0 0 auto;background:var(--ks-surface-sunken,#141414);display:flex;align-items:center;justify-content:center;}
|
|
49
44
|
.ksui-ff-meta{display:flex;flex-direction:column;min-width:0;flex:1;}
|
|
50
45
|
.ksui-ff-name{font-size:0.82rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
51
46
|
.ksui-ff-sub{font-size:0.72rem;opacity:0.6;}
|
|
52
|
-
.ksui-ff-sub.failed{color:var(--ksui-ff-danger,#f87171);}
|
|
53
|
-
.ksui-ff-remove{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:0.375rem;border:1px solid var(--ksui-ff-border,
|
|
54
|
-
.ksui-ff-retry{font-size:0.72rem;text-decoration:underline;cursor:pointer;color:var(--ksui-ff-accent,#c9a961);background:none;border:none;padding:0;}
|
|
47
|
+
.ksui-ff-sub.failed{color:var(--ksui-ff-danger,var(--ks-danger-fg,#f87171));}
|
|
48
|
+
.ksui-ff-remove{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:0.375rem;border:1px solid var(--ksui-ff-border,var(--ks-input-border,#3f3f46));background:transparent;color:inherit;cursor:pointer;}
|
|
49
|
+
.ksui-ff-retry{font-size:0.72rem;text-decoration:underline;cursor:pointer;color:var(--ksui-ff-accent,var(--ks-primary,#c9a961));background:none;border:none;padding:0;}
|
|
55
50
|
`;
|
|
56
|
-
document.head.appendChild(style);
|
|
57
|
-
}
|
|
58
51
|
|
|
59
52
|
export interface FileFieldProps {
|
|
60
53
|
/** Field label. */
|
|
@@ -89,7 +82,7 @@ function formatSize(bytes: number): string {
|
|
|
89
82
|
}
|
|
90
83
|
|
|
91
84
|
export const FileField: Component<FileFieldProps> = (props) => {
|
|
92
|
-
|
|
85
|
+
injectCSS(STYLE_ID, STYLE_CSS);
|
|
93
86
|
const [status, setStatus] = createSignal<FileFieldStatus>(props.value ? "done" : "empty");
|
|
94
87
|
// The handle currently shown. Seeded from props.value, and updated locally on a
|
|
95
88
|
// fresh upload so the done card shows even when the parent doesn't echo onChange
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type GraphNode,
|
|
36
36
|
type PositionedNode,
|
|
37
37
|
} from "../../utils/graph";
|
|
38
|
+
import { injectCSS } from "../../utils/inject-css";
|
|
38
39
|
|
|
39
40
|
type IconComp = Component<{ size?: number; class?: string }>;
|
|
40
41
|
|
|
@@ -62,58 +63,50 @@ const ZOOM_MIN = 0.3;
|
|
|
62
63
|
const ZOOM_MAX = 3;
|
|
63
64
|
|
|
64
65
|
const STYLE_ID = "ksui-flow-graph-style";
|
|
65
|
-
|
|
66
|
-
function ensureStyle(): void {
|
|
67
|
-
if (typeof document === "undefined") return;
|
|
68
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
69
|
-
const style = document.createElement("style");
|
|
70
|
-
style.id = STYLE_ID;
|
|
71
|
-
style.textContent = `
|
|
66
|
+
const STYLE_CSS = `
|
|
72
67
|
.ksui-fg-wrap{width:100%;overflow:auto;position:relative;user-select:none;-webkit-user-select:none;}
|
|
73
68
|
.ksui-fg-card,.ksui-fg-title,.ksui-fg-kind,.ksui-fg-elabel{user-select:none;-webkit-user-select:none;}
|
|
74
|
-
.ksui-fg-wrap.interactive{overflow:hidden;border:1px solid var(--ksui-fg-node-border,
|
|
69
|
+
.ksui-fg-wrap.interactive{overflow:hidden;border:1px solid var(--ksui-fg-node-border,color-mix(in srgb, var(--ks-fg, #ffffff) 12%, transparent));border-radius:10px;background:var(--ksui-fg-canvas,var(--ks-bg,#0a0a0a));background-image:radial-gradient(var(--ksui-fg-dot,color-mix(in srgb, var(--ks-fg, #ffffff) 5%, transparent)) 1px,transparent 1px);background-size:20px 20px;cursor:grab;touch-action:none;}
|
|
75
70
|
.ksui-fg-wrap.interactive.grabbing{cursor:grabbing;}
|
|
76
|
-
.ksui-fg-wrap.scroll{overflow:auto;border:1px solid var(--ksui-fg-node-border,
|
|
71
|
+
.ksui-fg-wrap.scroll{overflow:auto;border:1px solid var(--ksui-fg-node-border,color-mix(in srgb, var(--ks-fg, #ffffff) 12%, transparent));border-radius:10px;background:var(--ksui-fg-canvas,var(--ks-bg,#0a0a0a));background-image:radial-gradient(var(--ksui-fg-dot,color-mix(in srgb, var(--ks-fg, #ffffff) 5%, transparent)) 1px,transparent 1px);background-size:20px 20px;}
|
|
77
72
|
.ksui-fg-svg{display:block;max-width:100%;height:auto;font-family:inherit;}
|
|
78
73
|
.ksui-fg-wrap.scroll .ksui-fg-svg{margin:0 auto;}
|
|
79
74
|
.ksui-fg-wrap.interactive .ksui-fg-svg{max-width:none;width:100%;height:100%;}
|
|
80
|
-
.ksui-fg-edge{fill:none;stroke:var(--ksui-fg-edge,
|
|
75
|
+
.ksui-fg-edge{fill:none;stroke:var(--ksui-fg-edge,color-mix(in srgb, var(--ks-fg, #ffffff) 28%, transparent));stroke-width:1.75;}
|
|
81
76
|
.ksui-fg-edge.dashed{stroke-dasharray:5 4;}
|
|
82
|
-
.ksui-fg-edge.primary{stroke:var(--ksui-fg-primary,#c9a961);}
|
|
83
|
-
.ksui-fg-edge.info{stroke
|
|
84
|
-
.ksui-fg-edge.success{stroke
|
|
85
|
-
.ksui-fg-edge.danger{stroke
|
|
86
|
-
.ksui-fg-edge.muted{stroke:
|
|
77
|
+
.ksui-fg-edge.primary{stroke:var(--ksui-fg-primary,var(--ks-primary,#c9a961));}
|
|
78
|
+
.ksui-fg-edge.info{stroke:var(--ks-info,#38bdf8);}
|
|
79
|
+
.ksui-fg-edge.success{stroke:var(--ks-success,#10b981);}
|
|
80
|
+
.ksui-fg-edge.danger{stroke:var(--ks-danger,#ef4444);}
|
|
81
|
+
.ksui-fg-edge.muted{stroke:color-mix(in srgb, var(--ks-fg, #ffffff) 20%, transparent);}
|
|
87
82
|
.ksui-fg-edge.flow{stroke-dasharray:6 5;animation:ksui-fg-march .8s linear infinite;}
|
|
88
83
|
@keyframes ksui-fg-march{to{stroke-dashoffset:-11;}}
|
|
89
84
|
@media (prefers-reduced-motion:reduce){.ksui-fg-edge.flow{animation:none;}}
|
|
90
|
-
.ksui-fg-handle{fill:var(--ksui-fg-canvas,#
|
|
85
|
+
.ksui-fg-handle{fill:var(--ksui-fg-canvas,var(--ks-bg,#0a0a0a));stroke:var(--ksui-fg-edge,color-mix(in srgb, var(--ks-fg, #ffffff) 40%, transparent));stroke-width:1.5;}
|
|
91
86
|
.ksui-fg-node,.ksui-fg-edge,.ksui-fg-elabel,.ksui-fg-elabel-bg,.ksui-fg-handle{transition:opacity .12s ease;}
|
|
92
87
|
.ksui-fg-dim{opacity:0.14;}
|
|
93
|
-
.ksui-fg-elabel{fill:var(--ksui-fg-muted,
|
|
94
|
-
.ksui-fg-elabel-bg{fill:var(--ksui-fg-canvas,#
|
|
95
|
-
.ksui-fg-card{box-sizing:border-box;height:100%;display:flex;align-items:center;gap:9px;padding:0 11px;border-radius:9px;background:var(--ksui-fg-card,#
|
|
88
|
+
.ksui-fg-elabel{fill:var(--ksui-fg-muted,color-mix(in srgb, var(--ks-fg, #ffffff) 78%, transparent));font-size:9.5px;}
|
|
89
|
+
.ksui-fg-elabel-bg{fill:var(--ksui-fg-canvas,var(--ks-bg,#0a0a0a));opacity:0.92;}
|
|
90
|
+
.ksui-fg-card{box-sizing:border-box;height:100%;display:flex;align-items:center;gap:9px;padding:0 11px;border-radius:9px;background:var(--ksui-fg-card,var(--ks-surface-raised,#1a1a1a));border:1px solid var(--ksui-fg-node-border,rgba(255,255,255,0.14));border-left-width:3px;overflow:hidden;}
|
|
96
91
|
.ksui-fg-node.clickable .ksui-fg-card{cursor:pointer;}
|
|
97
|
-
.ksui-fg-node.clickable .ksui-fg-card:hover{background
|
|
92
|
+
.ksui-fg-node.clickable .ksui-fg-card:hover{background:var(--ks-surface-raised,#1a1a1a);}
|
|
98
93
|
.ksui-fg-node:focus{outline:none;}
|
|
99
|
-
.ksui-fg-node:focus-visible .ksui-fg-card{border-color:var(--ksui-fg-primary,#c9a961);}
|
|
100
|
-
.ksui-fg-chip{flex:none;width:30px;height:30px;border-radius:7px;display:flex;align-items:center;justify-content:center;color
|
|
94
|
+
.ksui-fg-node:focus-visible .ksui-fg-card{border-color:var(--ksui-fg-primary,var(--ks-primary,#c9a961));}
|
|
95
|
+
.ksui-fg-chip{flex:none;width:30px;height:30px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--ks-fg,#ffffff);}
|
|
101
96
|
.ksui-fg-txt{min-width:0;display:flex;flex-direction:column;line-height:1.15;}
|
|
102
|
-
.ksui-fg-title{font-size:12px;font-weight:600;color:var(--ksui-fg-fg,#
|
|
103
|
-
.ksui-fg-kind{font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--ksui-fg-muted,
|
|
104
|
-
.ksui-fg-card.primary{border-left-color
|
|
105
|
-
.ksui-fg-card.info{border-left-color
|
|
106
|
-
.ksui-fg-card.success{border-left-color
|
|
107
|
-
.ksui-fg-card.danger{border-left-color
|
|
108
|
-
.ksui-fg-card.muted{border-left-color:
|
|
109
|
-
.ksui-fg-empty{padding:1.75rem 1rem;text-align:center;font-size:0.82rem;color:var(--ksui-fg-muted,
|
|
97
|
+
.ksui-fg-title{font-size:12px;font-weight:600;color:var(--ksui-fg-fg,var(--ks-fg,#ffffff));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
98
|
+
.ksui-fg-kind{font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--ksui-fg-muted,color-mix(in srgb, var(--ks-fg, #ffffff) 45%, transparent));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
99
|
+
.ksui-fg-card.primary{border-left-color:var(--ks-primary,#c9a961);} .ksui-fg-card.primary .ksui-fg-chip{background:var(--ks-primary,#c9a961);color:var(--ks-fg-on-primary,#0a0a0a);}
|
|
100
|
+
.ksui-fg-card.info{border-left-color:var(--ks-info,#38bdf8);} .ksui-fg-card.info .ksui-fg-chip{background:var(--ks-info-bg,rgba(56,189,248,0.12));}
|
|
101
|
+
.ksui-fg-card.success{border-left-color:var(--ks-success,#10b981);} .ksui-fg-card.success .ksui-fg-chip{background:var(--ks-success-bg,rgba(16,185,129,0.12));}
|
|
102
|
+
.ksui-fg-card.danger{border-left-color:var(--ks-danger,#ef4444);} .ksui-fg-card.danger .ksui-fg-chip{background:var(--ks-danger-bg,rgba(239,68,68,0.12));}
|
|
103
|
+
.ksui-fg-card.muted{border-left-color:color-mix(in srgb, var(--ks-fg, #ffffff) 30%, transparent);} .ksui-fg-card.muted .ksui-fg-chip{background:var(--ks-border-strong,#3f3f46);}
|
|
104
|
+
.ksui-fg-empty{padding:1.75rem 1rem;text-align:center;font-size:0.82rem;color:var(--ksui-fg-muted,color-mix(in srgb, var(--ks-fg, #ffffff) 55%, transparent));}
|
|
110
105
|
.ksui-fg-controls{position:absolute;right:8px;bottom:8px;display:flex;gap:4px;z-index:1;}
|
|
111
|
-
.ksui-fg-ctrl{width:24px;height:24px;display:grid;place-items:center;border-radius:5px;border:1px solid var(--ksui-fg-node-border,
|
|
112
|
-
.ksui-fg-ctrl:hover{background
|
|
113
|
-
.ksui-fg-hint{position:absolute;left:8px;bottom:8px;font-size:9.5px;color:var(--ksui-fg-muted,
|
|
106
|
+
.ksui-fg-ctrl{width:24px;height:24px;display:grid;place-items:center;border-radius:5px;border:1px solid var(--ksui-fg-node-border,color-mix(in srgb, var(--ks-fg, #ffffff) 18%, transparent));background:var(--ksui-fg-card,var(--ks-surface-raised,#1a1a1a));color:var(--ksui-fg-fg,var(--ks-fg,#ffffff));font-size:14px;line-height:1;cursor:pointer;user-select:none;}
|
|
107
|
+
.ksui-fg-ctrl:hover{background:var(--ks-surface-raised,#1a1a1a);}
|
|
108
|
+
.ksui-fg-hint{position:absolute;left:8px;bottom:8px;font-size:9.5px;color:var(--ksui-fg-muted,color-mix(in srgb, var(--ks-fg, #ffffff) 40%, transparent));pointer-events:none;}
|
|
114
109
|
`;
|
|
115
|
-
document.head.appendChild(style);
|
|
116
|
-
}
|
|
117
110
|
|
|
118
111
|
export interface FlowGraphProps {
|
|
119
112
|
nodes: GraphNode[];
|
|
@@ -146,7 +139,7 @@ function clip(text: string, max: number): string {
|
|
|
146
139
|
}
|
|
147
140
|
|
|
148
141
|
export const FlowGraph: Component<FlowGraphProps> = (props) => {
|
|
149
|
-
|
|
142
|
+
injectCSS(STYLE_ID, STYLE_CSS);
|
|
150
143
|
const tid = (s: string) => (props.testId ? `${props.testId}-${s}` : undefined);
|
|
151
144
|
|
|
152
145
|
const vertical = () => props.direction === "vertical";
|