@valentinkolb/cloud 0.3.1 → 0.5.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 +18 -8
- package/scripts/preload.ts +78 -23
- package/src/_internal/define-app.ts +119 -47
- package/src/_internal/runtime-context.ts +1 -0
- package/src/api/accounts-entities.ts +4 -0
- package/src/api/admin-core-settings.ts +98 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/auth/schemas.ts +24 -0
- package/src/api/auth.ts +113 -10
- package/src/api/index.ts +15 -25
- package/src/api/me.ts +203 -14
- package/src/api/search/schemas.ts +1 -0
- package/src/api/search.ts +62 -8
- package/src/config/ssr.ts +2 -9
- package/src/contracts/announcements.test.ts +37 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app.ts +4 -0
- package/src/contracts/index.ts +3 -2
- package/src/contracts/registry.ts +4 -0
- package/src/contracts/shared.ts +108 -1
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/server/api/index.ts +1 -1
- package/src/server/api/respond.ts +50 -10
- package/src/server/index.ts +44 -38
- package/src/server/middleware/auth.ts +98 -9
- package/src/server/middleware/index.ts +2 -1
- package/src/server/middleware/settings.ts +26 -0
- package/src/server/services/access.test.ts +197 -0
- package/src/server/services/access.ts +254 -6
- package/src/server/services/index.ts +14 -11
- package/src/server/services/pagination.ts +22 -0
- package/src/server/time.ts +45 -0
- package/src/services/account-lifecycle/index.ts +142 -18
- package/src/services/accounts/app.ts +658 -170
- package/src/services/accounts/authz.test.ts +77 -0
- package/src/services/accounts/authz.ts +22 -0
- package/src/services/accounts/entities.ts +84 -5
- package/src/services/accounts/groups.ts +30 -24
- package/src/services/accounts/model.test.ts +30 -0
- package/src/services/accounts/switching.test.ts +14 -0
- package/src/services/accounts/switching.ts +15 -6
- package/src/services/accounts/users.ts +75 -52
- package/src/services/announcements/index.test.ts +32 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/audit/index.test.ts +84 -0
- package/src/services/audit/index.ts +431 -0
- package/src/services/auth-flows/index.ts +9 -2
- package/src/services/auth-flows/ipa.ts +0 -2
- package/src/services/auth-flows/magic-link.ts +3 -2
- package/src/services/auth-flows/password-reset.ts +284 -0
- package/src/services/auth-flows/proxy-return.test.ts +24 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/gateway.ts +162 -0
- package/src/services/index.ts +44 -2
- package/src/services/ipa/effective-groups.test.ts +33 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/profile.ts +45 -3
- package/src/services/ipa/search.ts +3 -5
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.test.ts +32 -0
- package/src/services/ipa/sync-planning.ts +22 -0
- package/src/services/ipa/sync.ts +110 -38
- package/src/services/oauth-tokens.ts +104 -0
- package/src/services/postgres.ts +21 -6
- package/src/services/providers/local/auth.test.ts +22 -0
- package/src/services/providers/local/auth.ts +46 -3
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.test.ts +210 -0
- package/src/services/service-account-credentials.ts +715 -0
- package/src/services/service-accounts.ts +188 -0
- package/src/services/session/index.ts +7 -8
- package/src/services/settings/app.ts +4 -20
- package/src/services/settings/defaults.ts +64 -22
- package/src/services/settings/store.ts +47 -0
- package/src/services/weather/forecast.ts +40 -7
- package/src/services/webauthn.test.ts +36 -0
- package/src/services/webauthn.ts +384 -0
- package/src/shared/icons.ts +391 -100
- package/src/shared/index.ts +7 -0
- package/src/shared/markdown/extensions/code.ts +38 -1
- package/src/shared/markdown/extensions/images.ts +39 -3
- package/src/shared/markdown/extensions/info-blocks.ts +5 -5
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +79 -58
- package/src/shared/markdown/formula.test.ts +1089 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +76 -2
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/redirect.test.ts +49 -0
- package/src/shared/redirect.ts +52 -0
- package/src/shared/theme.test.ts +24 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +7 -3
- package/src/ssr/AdminSidebar.tsx +115 -49
- package/src/ssr/AppLaunchpad.island.tsx +176 -0
- package/src/ssr/Footer.island.tsx +3 -8
- package/src/ssr/GlobalAnnouncements.island.tsx +141 -0
- package/src/ssr/GlobalSearchDialog.tsx +545 -117
- package/src/ssr/HotkeysHelpRail.island.tsx +3 -70
- package/src/ssr/Layout.tsx +74 -66
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutHelp.tsx +266 -0
- package/src/ssr/NavMenu.island.tsx +0 -39
- package/src/ssr/ThemeToggleRail.island.tsx +3 -3
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/islands/index.ts +13 -0
- package/src/styles/base-popover.css +5 -2
- package/src/styles/effects.css +87 -6
- package/src/styles/global.css +146 -9
- package/src/styles/input.css +3 -1
- package/src/styles/utilities-buttons.css +133 -27
- package/src/styles/utilities-code-display.css +67 -0
- package/src/styles/utilities-completion.css +223 -0
- package/src/styles/utilities-detail.css +73 -0
- package/src/styles/utilities-feedback.css +16 -15
- package/src/styles/utilities-layout.css +42 -2
- package/src/styles/utilities-markdown-editor.css +472 -0
- package/src/styles/utilities-navigation.css +63 -8
- package/src/styles/utilities-script.css +84 -0
- package/src/styles/utilities-table-tile.css +229 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/ui/completion/behaviors.test.ts +95 -0
- package/src/ui/completion/behaviors.ts +205 -0
- package/src/ui/completion/engine.ts +368 -0
- package/src/ui/completion/index.ts +40 -0
- package/src/ui/completion/overlay.ts +92 -0
- package/src/ui/dialog-core.ts +173 -45
- package/src/ui/filter/FilterChip.tsx +42 -40
- package/src/ui/index.ts +11 -12
- package/src/ui/input/AutocompleteEditor.tsx +656 -0
- package/src/ui/input/CheckboxCard.tsx +91 -0
- package/src/ui/input/Combobox.tsx +375 -0
- package/src/ui/input/DatePicker.tsx +846 -0
- package/src/ui/input/DateTimeInput.tsx +29 -4
- package/src/ui/input/FileDropzone.tsx +116 -0
- package/src/ui/input/IconInput.tsx +116 -0
- package/src/ui/input/ImageInput.tsx +19 -2
- package/src/ui/input/MultiSelectInput.tsx +448 -0
- package/src/ui/input/NumberInput.tsx +417 -61
- package/src/ui/input/SegmentedControl.tsx +2 -2
- package/src/ui/input/Select.tsx +172 -10
- package/src/ui/input/Slider.tsx +3 -4
- package/src/ui/input/Switch.tsx +3 -2
- package/src/ui/input/TemplateEditor.tsx +212 -0
- package/src/ui/input/TextInput.tsx +144 -13
- package/src/ui/input/index.ts +53 -8
- package/src/ui/input/markdown/MarkdownEditor.tsx +774 -0
- package/src/ui/input/markdown/Toolbar.tsx +90 -0
- package/src/ui/input/markdown/actions.ts +233 -0
- package/src/ui/input/markdown/active-formats.ts +94 -0
- package/src/ui/input/markdown/behaviors.ts +193 -0
- package/src/ui/input/markdown/code-zone.ts +23 -0
- package/src/ui/input/markdown/highlight.ts +316 -0
- package/src/ui/layout.ts +22 -0
- package/src/ui/misc/AppOverview.tsx +105 -0
- package/src/ui/misc/AppWorkspace.tsx +607 -0
- package/src/ui/misc/Calendar.tsx +1291 -0
- package/src/ui/misc/Chart.tsx +162 -0
- package/src/ui/misc/CodeDisplay.tsx +54 -0
- package/src/ui/misc/ContextMenu.tsx +2 -2
- package/src/ui/misc/DataTable.tsx +269 -0
- package/src/ui/misc/DockWorkspace.tsx +425 -0
- package/src/ui/misc/Docs.tsx +153 -0
- package/src/ui/misc/Dropdown.tsx +2 -2
- package/src/ui/misc/EntitySearch.tsx +260 -129
- package/src/ui/misc/LinkCard.tsx +14 -2
- package/src/ui/misc/LogEntriesTable.tsx +34 -31
- package/src/ui/misc/Pagination.tsx +31 -12
- package/src/ui/misc/PanelDialog.tsx +109 -0
- package/src/ui/misc/Panes.tsx +873 -0
- package/src/ui/misc/PermissionEditor.tsx +358 -262
- package/src/ui/misc/Placeholder.tsx +40 -0
- package/src/ui/misc/ProgressBar.tsx +1 -1
- package/src/ui/misc/ResourceApiKeys.tsx +260 -0
- package/src/ui/misc/SettingsModal.tsx +150 -0
- package/src/ui/misc/StatCell.tsx +182 -40
- package/src/ui/misc/StatGrid.tsx +149 -0
- package/src/ui/misc/StructuredDataPreview.tsx +107 -0
- package/src/ui/misc/code-highlight.ts +213 -0
- package/src/ui/misc/index.ts +93 -12
- package/src/ui/prompts.tsx +362 -312
- package/src/ui/toast.ts +384 -0
- package/src/ui/widgets/Widget.tsx +12 -4
- package/src/ssr/MoreAppsDropdown.island.tsx +0 -61
- package/src/ui/ipa/GroupView.tsx +0 -36
- package/src/ui/ipa/LoginBtn.tsx +0 -16
- package/src/ui/ipa/UserView.tsx +0 -58
- package/src/ui/ipa/index.ts +0 -4
- package/src/ui/navigation.ts +0 -32
- package/src/ui/sidebar.tsx +0 -468
- /package/src/ui/{ipa → misc}/Avatar.tsx +0 -0
package/src/ui/prompts.tsx
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* @module prompt-lib
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { mutation, timed } from "@valentinkolb/stdlib/solid";
|
|
7
|
+
import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, Show } from "solid-js";
|
|
8
|
+
import { createStore } from "solid-js/store";
|
|
9
|
+
import { dialogCore } from "./dialog-core";
|
|
6
10
|
import CheckboxInput from "./input/Checkbox";
|
|
7
|
-
import
|
|
11
|
+
import { DatePicker, DateTimePicker } from "./input/DatePicker";
|
|
8
12
|
import ImageInput from "./input/ImageInput";
|
|
9
13
|
import NumberInput from "./input/NumberInput";
|
|
10
14
|
import PinInput from "./input/PinInput";
|
|
11
15
|
import SelectInput from "./input/Select";
|
|
12
16
|
import TagsInput from "./input/TagsInput";
|
|
13
17
|
import TextInput from "./input/TextInput";
|
|
14
|
-
import { mutation, timed } from "@valentinkolb/stdlib/solid";
|
|
15
|
-
import { For, Show, createEffect, createMemo, createSignal, onCleanup, type JSX } from "solid-js";
|
|
16
|
-
import { createStore } from "solid-js/store";
|
|
17
|
-
import { dialogCore } from "./dialog-core";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Configuration options for dialog appearance and behavior
|
|
@@ -31,7 +31,11 @@ export interface DialogOptions {
|
|
|
31
31
|
/** Visual variant affecting button and outline colors */
|
|
32
32
|
variant?: "danger" | "primary" | "success";
|
|
33
33
|
/** Dialog size preset (default: "medium") */
|
|
34
|
-
size?: "small" | "medium" | "large";
|
|
34
|
+
size?: "small" | "medium" | "large" | "wide";
|
|
35
|
+
/** Dialog surface preset. "bare" keeps overlay/focus handling but leaves the visible panel to the content. */
|
|
36
|
+
surface?: "default" | "bare";
|
|
37
|
+
/** Hide the default title row. Mainly useful together with surface: "bare". */
|
|
38
|
+
header?: false;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export type PromptSearchItem<T = unknown> = {
|
|
@@ -77,11 +81,14 @@ export type FieldSchema =
|
|
|
77
81
|
| (BaseField<string> & {
|
|
78
82
|
type: "text";
|
|
79
83
|
multiline?: boolean;
|
|
84
|
+
/** Approximate visible lines for multiline mode. Overrides default height. */
|
|
85
|
+
lines?: number;
|
|
80
86
|
maxLength?: number;
|
|
81
87
|
minLength?: number;
|
|
82
88
|
icon?: string;
|
|
83
89
|
activeIcon?: string;
|
|
84
90
|
password?: boolean;
|
|
91
|
+
markdown?: boolean;
|
|
85
92
|
})
|
|
86
93
|
| (BaseField<number> & {
|
|
87
94
|
type: "number";
|
|
@@ -241,8 +248,13 @@ export const createFormState = <T extends Record<string, FieldSchema>>(schema: T
|
|
|
241
248
|
|
|
242
249
|
export const DialogHeader = (props: { close: () => void; title?: string; icon?: string }) => {
|
|
243
250
|
const { title, icon, close } = props || {};
|
|
251
|
+
// Hierarchy comes from the font-semibold title + per-section
|
|
252
|
+
// padding. The previous `border-b border-zinc-200` violated the
|
|
253
|
+
// project's "no horizontal dividers between header/body/footer"
|
|
254
|
+
// rule. `pb-2` is kept so the title still has breathing room
|
|
255
|
+
// before whatever the body renders.
|
|
244
256
|
return (
|
|
245
|
-
<div class="flex flex-row items-center justify-start gap-4
|
|
257
|
+
<div class="flex flex-row items-center justify-start gap-4 pb-2">
|
|
246
258
|
{icon && <i class={`${icon}`} />}
|
|
247
259
|
{title && <p class="truncate font-semibold">{title}</p>}
|
|
248
260
|
<button type="button" onClick={() => close()} class="ti ti-x ml-auto" aria-label="close dialog" />
|
|
@@ -252,6 +264,7 @@ export const DialogHeader = (props: { close: () => void; title?: string; icon?:
|
|
|
252
264
|
|
|
253
265
|
const getSizeClassName = (size: DialogOptions["size"] = "medium") => {
|
|
254
266
|
if (size === "small") return "w-[min(90vw,22rem)] max-h-[72vh]";
|
|
267
|
+
if (size === "wide") return "w-[min(96vw,64rem)] max-h-[90vh]";
|
|
255
268
|
if (size === "large") return "w-[min(96vw,48rem)] max-h-[86vh]";
|
|
256
269
|
return "w-[min(94vw,28rem)] max-h-[90vh]";
|
|
257
270
|
};
|
|
@@ -262,12 +275,17 @@ const getVariantClassName = (variant?: DialogOptions["variant"]) => {
|
|
|
262
275
|
return "ring-zinc-300/60 dark:ring-zinc-700/60";
|
|
263
276
|
};
|
|
264
277
|
|
|
265
|
-
const getPanelClassName = (options?: Pick<DialogOptions, "variant" | "size">) => {
|
|
278
|
+
const getPanelClassName = (options?: Pick<DialogOptions, "variant" | "size" | "surface">) => {
|
|
266
279
|
const sizeClass = getSizeClassName(options?.size);
|
|
280
|
+
if (options?.surface === "bare") {
|
|
281
|
+
return `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 ${sizeClass} overflow-visible border-0 bg-transparent p-0 text-zinc-900 shadow-none backdrop:bg-black/45 dark:backdrop:bg-black/35 backdrop:backdrop-blur-sm dark:text-zinc-100`;
|
|
282
|
+
}
|
|
267
283
|
const variantClass = getVariantClassName(options?.variant);
|
|
268
284
|
return `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 ${sizeClass} overflow-x-hidden overflow-y-auto rounded-2xl border-0 bg-white/95 p-4 text-zinc-900 shadow-none ring-1 ring-inset ${variantClass} backdrop:bg-black/45 dark:backdrop:bg-black/35 backdrop:backdrop-blur-sm dark:bg-zinc-950/95 dark:text-zinc-100`;
|
|
269
285
|
};
|
|
270
286
|
|
|
287
|
+
const getContentClassName = (surface?: DialogOptions["surface"]) => (surface === "bare" ? "" : undefined);
|
|
288
|
+
|
|
271
289
|
const getSearchPanelClassName = () =>
|
|
272
290
|
"fixed left-1/2 top-[25vh] -translate-x-1/2 m-0 w-[min(96vw,46rem)] h-[50vh] border-0 bg-transparent p-0 text-zinc-900 shadow-none backdrop:bg-black/45 dark:backdrop:bg-black/35 backdrop:backdrop-blur-sm dark:text-zinc-100 [@media(min-height:1100px)]:top-[33vh] [@media(min-height:1100px)]:h-[33vh]";
|
|
273
291
|
|
|
@@ -277,229 +295,226 @@ const openSearchPrompt = <T = unknown>(
|
|
|
277
295
|
resolver: (input: PromptSearchInput) => Promise<PromptSearchItem<T>[]> | PromptSearchItem<T>[],
|
|
278
296
|
options?: PromptSearchOptions,
|
|
279
297
|
) =>
|
|
280
|
-
dialogCore.open<PromptSearchItem<T>>(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
? "bg-blue-50/80 text-blue-900 dark:bg-blue-950/45 dark:text-blue-100"
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}, debounceMs);
|
|
347
|
-
|
|
348
|
-
const execute = async (item?: PromptSearchItem<T>) => {
|
|
349
|
-
if (!item) return;
|
|
350
|
-
if (item.onClick) await item.onClick(item);
|
|
351
|
-
close(item);
|
|
352
|
-
};
|
|
298
|
+
dialogCore.open<PromptSearchItem<T>>(
|
|
299
|
+
(close) => {
|
|
300
|
+
const [query, setQuery] = createSignal(options?.initialQuery ?? "");
|
|
301
|
+
const [items, setItems] = createSignal<PromptSearchItem<T>[]>([]);
|
|
302
|
+
const [activeIndex, setActiveIndex] = createSignal(0);
|
|
303
|
+
const [hasLoaded, setHasLoaded] = createSignal(false);
|
|
304
|
+
const [failedPreviews, setFailedPreviews] = createStore<Record<number, true>>({});
|
|
305
|
+
const [activeSearchQuery, setActiveSearchQuery] = createSignal("");
|
|
306
|
+
|
|
307
|
+
const rowRefs = new Map<number, HTMLButtonElement>();
|
|
308
|
+
let inputRef: HTMLInputElement | undefined;
|
|
309
|
+
|
|
310
|
+
const minQueryLength = options?.minQueryLength ?? 0;
|
|
311
|
+
const debounceMs = options?.debounceMs ?? 180;
|
|
312
|
+
const searchMutation = mutation.create<
|
|
313
|
+
{
|
|
314
|
+
query: string;
|
|
315
|
+
items: PromptSearchItem<T>[];
|
|
316
|
+
},
|
|
317
|
+
string,
|
|
318
|
+
{ requestQuery: string }
|
|
319
|
+
>({
|
|
320
|
+
onBefore: (requestQuery) => ({ requestQuery }),
|
|
321
|
+
mutation: async (requestQuery, ctx) => {
|
|
322
|
+
const result = await resolver({
|
|
323
|
+
query: requestQuery,
|
|
324
|
+
abortSignal: ctx.abortSignal,
|
|
325
|
+
});
|
|
326
|
+
return { query: requestQuery, items: (result ?? []).slice() };
|
|
327
|
+
},
|
|
328
|
+
onSuccess: (result, ctx) => {
|
|
329
|
+
if (!ctx || ctx.requestQuery !== activeSearchQuery()) return;
|
|
330
|
+
setItems(result.items);
|
|
331
|
+
setActiveIndex(0);
|
|
332
|
+
setHasLoaded(true);
|
|
333
|
+
},
|
|
334
|
+
onError: (err, ctx) => {
|
|
335
|
+
if (!ctx || ctx.requestQuery !== activeSearchQuery()) return;
|
|
336
|
+
if (err.name === "AbortError") return;
|
|
337
|
+
setItems([]);
|
|
338
|
+
setActiveIndex(0);
|
|
339
|
+
setHasLoaded(true);
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const searchError = createMemo(() => {
|
|
343
|
+
const err = searchMutation.error();
|
|
344
|
+
if (!err || err.name === "AbortError") return null;
|
|
345
|
+
return err.message || "Search failed.";
|
|
346
|
+
});
|
|
347
|
+
const shouldShowResults = createMemo(() => {
|
|
348
|
+
if (query().trim().length < minQueryLength) return false;
|
|
349
|
+
return hasLoaded() || searchError() !== null || items().length > 0;
|
|
350
|
+
});
|
|
351
|
+
const emptyStateText = createMemo(() => {
|
|
352
|
+
if (!hasLoaded()) return options?.emptyText ?? "Type to search.";
|
|
353
|
+
return options?.noResultsText ?? "No results.";
|
|
354
|
+
});
|
|
355
|
+
const getItemClassName = (isActive: boolean) =>
|
|
356
|
+
`flex w-full items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors ${
|
|
357
|
+
isActive ? "bg-blue-50/80 text-blue-900 dark:bg-blue-950/45 dark:text-blue-100" : "hover:bg-zinc-200/65 dark:hover:bg-zinc-800/70"
|
|
358
|
+
}`;
|
|
359
|
+
const { debouncedFn: debounceSearch, cancel: cancelDebounce } = timed.debounce((nextQuery: string) => {
|
|
360
|
+
setActiveSearchQuery(nextQuery);
|
|
361
|
+
searchMutation.abort();
|
|
362
|
+
void searchMutation.mutate(nextQuery);
|
|
363
|
+
}, debounceMs);
|
|
353
364
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
};
|
|
365
|
+
const execute = async (item?: PromptSearchItem<T>) => {
|
|
366
|
+
if (!item) return;
|
|
367
|
+
if (item.onClick) await item.onClick(item);
|
|
368
|
+
close(item);
|
|
369
|
+
};
|
|
360
370
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
setActiveIndex(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
const moveSelection = (delta: -1 | 1) => {
|
|
372
|
+
const list = items();
|
|
373
|
+
if (list.length === 0) return;
|
|
374
|
+
const next = (activeIndex() + delta + list.length) % list.length;
|
|
375
|
+
setActiveIndex(next);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
createEffect(() => {
|
|
379
|
+
const list = items();
|
|
380
|
+
const maxIndex = list.length - 1;
|
|
381
|
+
if (maxIndex < 0) {
|
|
382
|
+
setActiveIndex(0);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (activeIndex() > maxIndex) setActiveIndex(maxIndex);
|
|
386
|
+
rowRefs.get(activeIndex())?.scrollIntoView({ block: "nearest" });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
createEffect(() => {
|
|
390
|
+
const nextQuery = query().trim();
|
|
391
|
+
setFailedPreviews({});
|
|
392
|
+
|
|
393
|
+
if (nextQuery.length < minQueryLength) {
|
|
394
|
+
cancelDebounce();
|
|
395
|
+
searchMutation.abort();
|
|
396
|
+
setItems([]);
|
|
397
|
+
setActiveIndex(0);
|
|
398
|
+
setHasLoaded(false);
|
|
399
|
+
setActiveSearchQuery("");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
371
402
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
setFailedPreviews({});
|
|
403
|
+
debounceSearch(nextQuery);
|
|
404
|
+
});
|
|
375
405
|
|
|
376
|
-
|
|
406
|
+
onCleanup(() => {
|
|
377
407
|
cancelDebounce();
|
|
378
408
|
searchMutation.abort();
|
|
379
|
-
|
|
380
|
-
setActiveIndex(0);
|
|
381
|
-
setHasLoaded(false);
|
|
382
|
-
setActiveSearchQuery("");
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
debounceSearch(nextQuery);
|
|
387
|
-
});
|
|
409
|
+
});
|
|
388
410
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
411
|
+
return (
|
|
412
|
+
<div class="flex h-full min-h-0 flex-col gap-2 pb-1 [--search-body-max:calc(50vh-3.5rem)] [@media(min-height:1100px)]:[--search-body-max:calc(33vh-3.5rem)]">
|
|
413
|
+
<Show when={options?.title}>
|
|
414
|
+
{(title) => <p class="px-1 text-base font-semibold text-white dark:text-zinc-100">{title()}</p>}
|
|
415
|
+
</Show>
|
|
416
|
+
|
|
417
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl bg-white/95 text-zinc-900 shadow-none ring-1 ring-inset ring-zinc-300/60 dark:bg-zinc-950/95 dark:text-zinc-100 dark:ring-zinc-700/60">
|
|
418
|
+
<label class="flex items-center gap-2 px-3 py-2.5">
|
|
419
|
+
<i class={`${options?.icon ?? "ti ti-search"} text-dimmed`} />
|
|
420
|
+
<input
|
|
421
|
+
ref={inputRef}
|
|
422
|
+
type="search"
|
|
423
|
+
value={query()}
|
|
424
|
+
onInput={(event) => setQuery(event.currentTarget.value)}
|
|
425
|
+
onKeyDown={(event) => {
|
|
426
|
+
if (event.key === "ArrowDown") {
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
moveSelection(1);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (event.key === "ArrowUp") {
|
|
432
|
+
event.preventDefault();
|
|
433
|
+
moveSelection(-1);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (event.key === "Enter") {
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
void execute(items()[activeIndex()]);
|
|
439
|
+
}
|
|
440
|
+
}}
|
|
441
|
+
placeholder={options?.placeholder ?? "Search..."}
|
|
442
|
+
class="w-full border-0 bg-transparent text-sm outline-none placeholder:text-dimmed"
|
|
443
|
+
spellcheck={false}
|
|
444
|
+
autocapitalize="off"
|
|
445
|
+
autocomplete="off"
|
|
446
|
+
autocorrect="off"
|
|
447
|
+
/>
|
|
448
|
+
<Show when={searchMutation.loading()}>
|
|
449
|
+
<i class="ti ti-loader-2 animate-spin text-dimmed" />
|
|
450
|
+
</Show>
|
|
451
|
+
</label>
|
|
393
452
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
{title()}
|
|
400
|
-
</p>
|
|
401
|
-
)}
|
|
402
|
-
</Show>
|
|
403
|
-
|
|
404
|
-
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl bg-white/95 text-zinc-900 shadow-none ring-1 ring-inset ring-zinc-300/60 dark:bg-zinc-950/95 dark:text-zinc-100 dark:ring-zinc-700/60">
|
|
405
|
-
<label class="flex items-center gap-2 px-3 py-2.5">
|
|
406
|
-
<i class={`${options?.icon ?? "ti ti-search"} text-dimmed`} />
|
|
407
|
-
<input
|
|
408
|
-
ref={inputRef}
|
|
409
|
-
type="search"
|
|
410
|
-
value={query()}
|
|
411
|
-
onInput={(event) => setQuery(event.currentTarget.value)}
|
|
412
|
-
onKeyDown={(event) => {
|
|
413
|
-
if (event.key === "ArrowDown") {
|
|
414
|
-
event.preventDefault();
|
|
415
|
-
moveSelection(1);
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (event.key === "ArrowUp") {
|
|
419
|
-
event.preventDefault();
|
|
420
|
-
moveSelection(-1);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (event.key === "Enter") {
|
|
424
|
-
event.preventDefault();
|
|
425
|
-
void execute(items()[activeIndex()]);
|
|
426
|
-
}
|
|
453
|
+
<div
|
|
454
|
+
class="overflow-hidden transition-[height,opacity] duration-200 ease-out"
|
|
455
|
+
style={{
|
|
456
|
+
height: shouldShowResults() ? "var(--search-body-max)" : "0px",
|
|
457
|
+
opacity: shouldShowResults() ? "1" : "0",
|
|
427
458
|
}}
|
|
428
|
-
|
|
429
|
-
class="
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
onMouseEnter={() => setActiveIndex(index())}
|
|
464
|
-
onClick={() => void execute(item)}
|
|
465
|
-
class={getItemClassName(activeIndex() === index())}
|
|
466
|
-
>
|
|
467
|
-
<Show when={isPreviewUrl(item.previewUrl) || item.icon}>
|
|
468
|
-
<span class="mt-0.5 grid h-7 w-7 shrink-0 place-items-center overflow-hidden rounded-md bg-zinc-200/80 dark:bg-zinc-800/80">
|
|
469
|
-
<Show
|
|
470
|
-
when={isPreviewUrl(item.previewUrl) && !failedPreviews[index()]}
|
|
471
|
-
fallback={<i class={`${item.icon ?? "ti ti-file"} text-xs text-dimmed`} />}
|
|
472
|
-
>
|
|
473
|
-
<img
|
|
474
|
-
src={item.previewUrl}
|
|
475
|
-
alt={item.label}
|
|
476
|
-
class="h-full w-full object-cover"
|
|
477
|
-
onError={() => setFailedPreviews(index(), true)}
|
|
478
|
-
/>
|
|
479
|
-
</Show>
|
|
480
|
-
</span>
|
|
481
|
-
</Show>
|
|
482
|
-
|
|
483
|
-
<div class="min-w-0 flex-1">
|
|
484
|
-
<p class="truncate text-sm leading-5">{item.label}</p>
|
|
485
|
-
<Show when={item.desc}>
|
|
486
|
-
<p class="mt-0.5 truncate text-xs leading-4 text-dimmed">{item.desc}</p>
|
|
459
|
+
>
|
|
460
|
+
<div class="h-full min-h-0 overflow-y-auto overscroll-y-contain px-2 pb-2" onWheel={(event) => event.stopPropagation()}>
|
|
461
|
+
<Show when={searchError()}>{(message) => <div class="info-block-danger mb-2 text-xs">{message()}</div>}</Show>
|
|
462
|
+
|
|
463
|
+
<Show when={items().length > 0} fallback={<p class="px-1.5 py-2 text-xs text-dimmed">{emptyStateText()}</p>}>
|
|
464
|
+
<div class="flex flex-col gap-1">
|
|
465
|
+
<For each={items()}>
|
|
466
|
+
{(item, index) => (
|
|
467
|
+
<button
|
|
468
|
+
ref={(element) => {
|
|
469
|
+
if (!element) {
|
|
470
|
+
rowRefs.delete(index());
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
rowRefs.set(index(), element);
|
|
474
|
+
}}
|
|
475
|
+
type="button"
|
|
476
|
+
onMouseEnter={() => setActiveIndex(index())}
|
|
477
|
+
onClick={() => void execute(item)}
|
|
478
|
+
class={getItemClassName(activeIndex() === index())}
|
|
479
|
+
>
|
|
480
|
+
<Show when={isPreviewUrl(item.previewUrl) || item.icon}>
|
|
481
|
+
<span class="mt-0.5 grid h-7 w-7 shrink-0 place-items-center overflow-hidden rounded-md bg-zinc-200/80 dark:bg-zinc-800/80">
|
|
482
|
+
<Show
|
|
483
|
+
when={isPreviewUrl(item.previewUrl) && !failedPreviews[index()]}
|
|
484
|
+
fallback={<i class={`${item.icon ?? "ti ti-file"} text-xs text-dimmed`} />}
|
|
485
|
+
>
|
|
486
|
+
<img
|
|
487
|
+
src={item.previewUrl}
|
|
488
|
+
alt={item.label}
|
|
489
|
+
class="h-full w-full object-cover"
|
|
490
|
+
onError={() => setFailedPreviews(index(), true)}
|
|
491
|
+
/>
|
|
492
|
+
</Show>
|
|
493
|
+
</span>
|
|
487
494
|
</Show>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
495
|
+
|
|
496
|
+
<div class="min-w-0 flex-1">
|
|
497
|
+
<p class="truncate text-sm leading-5">{item.label}</p>
|
|
498
|
+
<Show when={item.desc}>
|
|
499
|
+
<p class="mt-0.5 truncate text-xs leading-4 text-dimmed">{item.desc}</p>
|
|
500
|
+
</Show>
|
|
501
|
+
</div>
|
|
502
|
+
</button>
|
|
503
|
+
)}
|
|
504
|
+
</For>
|
|
505
|
+
</div>
|
|
506
|
+
</Show>
|
|
507
|
+
</div>
|
|
494
508
|
</div>
|
|
495
509
|
</div>
|
|
496
510
|
</div>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
511
|
+
);
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
panelClassName: getSearchPanelClassName(),
|
|
515
|
+
contentClassName: "h-full min-h-0 p-0",
|
|
516
|
+
},
|
|
517
|
+
);
|
|
503
518
|
|
|
504
519
|
/**
|
|
505
520
|
* Simple dialog utilities for user interactions
|
|
@@ -575,6 +590,7 @@ export const prompts = {
|
|
|
575
590
|
),
|
|
576
591
|
{
|
|
577
592
|
panelClassName: getPanelClassName(options),
|
|
593
|
+
contentClassName: getContentClassName(options?.surface),
|
|
578
594
|
},
|
|
579
595
|
),
|
|
580
596
|
|
|
@@ -605,7 +621,7 @@ export const prompts = {
|
|
|
605
621
|
|
|
606
622
|
<div class="flex justify-end gap-3">
|
|
607
623
|
<button type="button" onClick={() => close(false)} class="btn-secondary btn-sm">
|
|
608
|
-
{options?.cancelText || "
|
|
624
|
+
{options?.cancelText || "Cancel"}
|
|
609
625
|
</button>
|
|
610
626
|
<button
|
|
611
627
|
type="button"
|
|
@@ -614,13 +630,14 @@ export const prompts = {
|
|
|
614
630
|
options?.variant === "danger" ? "btn-danger" : options?.variant === "success" ? "btn-success" : "btn-primary"
|
|
615
631
|
} btn-sm`}
|
|
616
632
|
>
|
|
617
|
-
{options?.confirmText || "
|
|
633
|
+
{options?.confirmText || "Confirm"}
|
|
618
634
|
</button>
|
|
619
635
|
</div>
|
|
620
636
|
</div>
|
|
621
637
|
),
|
|
622
638
|
{
|
|
623
639
|
panelClassName: getPanelClassName(options),
|
|
640
|
+
contentClassName: getContentClassName(options?.surface),
|
|
624
641
|
},
|
|
625
642
|
),
|
|
626
643
|
|
|
@@ -707,93 +724,118 @@ export const prompts = {
|
|
|
707
724
|
confirmText?: string;
|
|
708
725
|
cancelText?: string | false;
|
|
709
726
|
variant?: "danger" | "primary" | "success";
|
|
710
|
-
size?: "
|
|
727
|
+
size?: DialogOptions["size"];
|
|
711
728
|
}): Promise<InferFormValues<T> | null> => {
|
|
712
|
-
return dialogCore.open<InferFormValues<T> | null>(
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
729
|
+
return dialogCore.open<InferFormValues<T> | null>(
|
|
730
|
+
(close) => {
|
|
731
|
+
const state = createFormState(config.fields);
|
|
732
|
+
|
|
733
|
+
// Field renderer map
|
|
734
|
+
const fieldRenderers: Record<string, (props: any, field: any) => JSX.Element> = {
|
|
735
|
+
text: (props, field) => (
|
|
736
|
+
<TextInput
|
|
737
|
+
{...props}
|
|
738
|
+
multiline={field.multiline}
|
|
739
|
+
lines={field.lines}
|
|
740
|
+
icon={field.icon}
|
|
741
|
+
activeIcon={field.activeIcon}
|
|
742
|
+
password={field.password}
|
|
743
|
+
markdown={field.markdown}
|
|
744
|
+
/>
|
|
745
|
+
),
|
|
746
|
+
number: (props, field) => <NumberInput {...props} min={field.min} max={field.max} step={field.step} />,
|
|
747
|
+
image: (props, field) => <ImageInput {...props} round={field.round} ariaLabel={field.ariaLabel} />,
|
|
748
|
+
pin: (props, field) => <PinInput {...props} length={field.length} stretch={field.stretch} />,
|
|
749
|
+
select: (props, field) => (
|
|
750
|
+
<SelectInput {...props} options={field.options} icon={field.icon} activeIcon={field.activeIcon} clearable={field.clearable} />
|
|
751
|
+
),
|
|
752
|
+
tags: (props, field) => <TagsInput {...props} icon={field.icon} activeIcon={field.activeIcon} />,
|
|
753
|
+
boolean: (props) => <CheckboxInput {...props} />,
|
|
754
|
+
datetime: (props, field) => {
|
|
755
|
+
const pickerProps = {
|
|
756
|
+
...props,
|
|
757
|
+
value: () => props.value?.() || null,
|
|
758
|
+
onChange: (value: string | null) => props.onChange?.(value ?? ""),
|
|
759
|
+
clearable: true,
|
|
760
|
+
};
|
|
761
|
+
return field.dateOnly ? <DatePicker {...pickerProps} /> : <DateTimePicker {...pickerProps} />;
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const submit = () => {
|
|
766
|
+
if (state.validateAll()) {
|
|
767
|
+
close(state.values as InferFormValues<T>);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// Handle form submission
|
|
772
|
+
const handleSubmit = (e: Event) => {
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
submit();
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
// Determine button variant class
|
|
778
|
+
const submitButtonClass = config.variant === "danger" ? "btn-danger" : config.variant === "success" ? "btn-success" : "btn-primary";
|
|
779
|
+
|
|
780
|
+
return (
|
|
781
|
+
<form onSubmit={handleSubmit} class="flex flex-col gap-4">
|
|
782
|
+
<DialogHeader title={config.title} icon={config.icon} close={() => close(null)} />
|
|
783
|
+
|
|
784
|
+
<div class="flex flex-col gap-4">
|
|
785
|
+
<For each={Object.entries(config.fields)}>
|
|
786
|
+
{([key, field]) => {
|
|
787
|
+
// Info field - just display content
|
|
788
|
+
if (field.type === "info") {
|
|
789
|
+
return (
|
|
790
|
+
<div>
|
|
791
|
+
{typeof field.content === "string" ? (
|
|
792
|
+
<p class="text-sm text-zinc-600 dark:text-zinc-400">{field.content}</p>
|
|
793
|
+
) : typeof field.content === "function" ? (
|
|
794
|
+
field.content()
|
|
795
|
+
) : (
|
|
796
|
+
field.content
|
|
797
|
+
)}
|
|
798
|
+
</div>
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Regular input fields
|
|
803
|
+
// Handle label: false or undefined means no label, otherwise use provided label
|
|
804
|
+
const label = field.label || undefined;
|
|
805
|
+
const commonProps = {
|
|
806
|
+
label,
|
|
807
|
+
description: field.description,
|
|
808
|
+
placeholder: field.placeholder,
|
|
809
|
+
required: field.required,
|
|
810
|
+
value: () => state.values[key],
|
|
811
|
+
onChange: (v: any) => state.updateField(key, v),
|
|
812
|
+
onSubmit: submit,
|
|
813
|
+
error: () => state.errors[key],
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
return fieldRenderers[field.type]?.(commonProps, field);
|
|
817
|
+
}}
|
|
818
|
+
</For>
|
|
819
|
+
</div>
|
|
781
820
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
821
|
+
<div class="flex justify-end gap-3">
|
|
822
|
+
<Show when={config.cancelText !== false}>
|
|
823
|
+
<button type="button" onClick={() => close(null)} class="btn-secondary btn-sm">
|
|
824
|
+
{config.cancelText || "Cancel"}
|
|
825
|
+
</button>
|
|
826
|
+
</Show>
|
|
827
|
+
<button type="submit" class={`${submitButtonClass} btn-sm`}>
|
|
828
|
+
{config.confirmText || "Save"}
|
|
786
829
|
</button>
|
|
787
|
-
</
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}) as Promise<InferFormValues<T> | null>;
|
|
830
|
+
</div>
|
|
831
|
+
</form>
|
|
832
|
+
);
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
panelClassName: getPanelClassName(config),
|
|
836
|
+
contentClassName: getContentClassName(),
|
|
837
|
+
},
|
|
838
|
+
) as Promise<InferFormValues<T> | null>;
|
|
797
839
|
},
|
|
798
840
|
|
|
799
841
|
/**
|
|
@@ -811,14 +853,21 @@ export const prompts = {
|
|
|
811
853
|
*/
|
|
812
854
|
dialog: <T = any>(component: (close: (result?: T) => void) => JSX.Element, options?: DialogOptions) =>
|
|
813
855
|
dialogCore.open<T>(
|
|
814
|
-
(close: (result?: T) => void) =>
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
856
|
+
(close: (result?: T) => void) => {
|
|
857
|
+
const body = component(close);
|
|
858
|
+
if (options?.surface === "bare" && options.header === false) return body;
|
|
859
|
+
return (
|
|
860
|
+
<div class="flex flex-col gap-4">
|
|
861
|
+
<Show when={options?.header !== false}>
|
|
862
|
+
<DialogHeader title={options?.title} icon={options?.icon} close={() => close(undefined)} />
|
|
863
|
+
</Show>
|
|
864
|
+
{body}
|
|
865
|
+
</div>
|
|
866
|
+
);
|
|
867
|
+
},
|
|
820
868
|
{
|
|
821
869
|
panelClassName: getPanelClassName(options),
|
|
870
|
+
contentClassName: getContentClassName(options?.surface),
|
|
822
871
|
},
|
|
823
872
|
),
|
|
824
873
|
|
|
@@ -834,19 +883,20 @@ export const prompts = {
|
|
|
834
883
|
dialogCore.open(
|
|
835
884
|
(close) => (
|
|
836
885
|
<div>
|
|
837
|
-
<DialogHeader title={options?.title ?? "
|
|
886
|
+
<DialogHeader title={options?.title ?? "Error"} icon={options?.icon ?? "ti ti-alert-circle"} close={close} />
|
|
838
887
|
|
|
839
888
|
<div class="font-xs p-4 text-sm">{content}</div>
|
|
840
889
|
|
|
841
890
|
<div class="flex justify-end gap-3">
|
|
842
891
|
<button onClick={() => close()} class="btn-primary btn-sm">
|
|
843
|
-
{options?.confirmText || "
|
|
892
|
+
{options?.confirmText || "Close"}
|
|
844
893
|
</button>
|
|
845
894
|
</div>
|
|
846
895
|
</div>
|
|
847
896
|
),
|
|
848
897
|
{
|
|
849
898
|
panelClassName: getPanelClassName({ ...options, variant: "danger" }),
|
|
899
|
+
contentClassName: getContentClassName(options?.surface),
|
|
850
900
|
},
|
|
851
901
|
),
|
|
852
902
|
|