@svadmin/lite 0.3.7 → 0.3.12
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/README.md +19 -13
- package/dist/components/LiteAlert.svelte +19 -0
- package/dist/components/LiteAlert.svelte.d.ts +11 -0
- package/dist/components/LiteArrayField.svelte +60 -0
- package/dist/components/LiteArrayField.svelte.d.ts +17 -0
- package/dist/components/LiteArrayItem.svelte +146 -0
- package/dist/components/LiteArrayItem.svelte.d.ts +13 -0
- package/dist/components/LiteAuditLog.svelte +104 -0
- package/dist/components/LiteAuditLog.svelte.d.ts +23 -0
- package/dist/components/LiteBreadcrumbs.svelte +39 -0
- package/dist/components/LiteBreadcrumbs.svelte.d.ts +16 -0
- package/dist/components/LiteChatDialog.svelte +101 -0
- package/dist/components/LiteChatDialog.svelte.d.ts +20 -0
- package/dist/components/LiteConfirmDialog.svelte +60 -0
- package/dist/components/LiteConfirmDialog.svelte.d.ts +22 -0
- package/dist/components/LiteEmptyState.svelte +39 -0
- package/dist/components/LiteEmptyState.svelte.d.ts +16 -0
- package/dist/components/LiteForm.svelte +165 -0
- package/dist/components/LiteForm.svelte.d.ts +26 -0
- package/dist/components/LiteLayout.svelte +147 -0
- package/dist/components/LiteLayout.svelte.d.ts +19 -0
- package/dist/components/LiteLogin.svelte +41 -0
- package/dist/components/LiteLogin.svelte.d.ts +8 -0
- package/dist/components/LitePagination.svelte +73 -0
- package/dist/components/LitePagination.svelte.d.ts +11 -0
- package/dist/components/LitePermissionMatrix.svelte +147 -0
- package/dist/components/LitePermissionMatrix.svelte.d.ts +32 -0
- package/dist/components/LiteSearch.svelte +41 -0
- package/dist/components/LiteSearch.svelte.d.ts +9 -0
- package/dist/components/LiteShow.svelte +61 -0
- package/dist/components/LiteShow.svelte.d.ts +14 -0
- package/dist/components/LiteShowField.svelte +62 -0
- package/dist/components/LiteShowField.svelte.d.ts +12 -0
- package/dist/components/LiteStatsCard.svelte +70 -0
- package/dist/components/LiteStatsCard.svelte.d.ts +24 -0
- package/dist/components/LiteTable.svelte +132 -0
- package/dist/components/LiteTable.svelte.d.ts +19 -0
- package/dist/components/LiteTabs.svelte +57 -0
- package/dist/components/LiteTabs.svelte.d.ts +16 -0
- package/dist/components/advanced/LiteAutoSaveIndicator.svelte +26 -0
- package/dist/components/advanced/LiteAutoSaveIndicator.svelte.d.ts +12 -0
- package/dist/components/advanced/LiteDraggableHeader.svelte +33 -0
- package/dist/components/advanced/LiteDraggableHeader.svelte.d.ts +15 -0
- package/dist/components/advanced/LiteDrawerForm.svelte +42 -0
- package/dist/components/advanced/LiteDrawerForm.svelte.d.ts +16 -0
- package/dist/components/advanced/LiteInlineEdit.svelte +32 -0
- package/dist/components/advanced/LiteInlineEdit.svelte.d.ts +15 -0
- package/dist/components/advanced/LiteModalForm.svelte +44 -0
- package/dist/components/advanced/LiteModalForm.svelte.d.ts +17 -0
- package/dist/components/advanced/LiteToast.svelte +34 -0
- package/dist/components/advanced/LiteToast.svelte.d.ts +14 -0
- package/dist/components/advanced/LiteUndoableNotification.svelte +25 -0
- package/dist/components/advanced/LiteUndoableNotification.svelte.d.ts +13 -0
- package/dist/components/advanced/LiteVirtualTable.svelte +44 -0
- package/dist/components/advanced/LiteVirtualTable.svelte.d.ts +20 -0
- package/dist/components/advanced/index.js +8 -0
- package/dist/components/buttons/LiteCloneButton.svelte +33 -0
- package/dist/components/buttons/LiteCloneButton.svelte.d.ts +11 -0
- package/dist/components/buttons/LiteCreateButton.svelte +31 -0
- package/dist/components/buttons/LiteCreateButton.svelte.d.ts +10 -0
- package/dist/components/buttons/LiteDeleteButton.svelte +57 -0
- package/dist/components/buttons/LiteDeleteButton.svelte.d.ts +12 -0
- package/dist/components/buttons/LiteEditButton.svelte +33 -0
- package/dist/components/buttons/LiteEditButton.svelte.d.ts +11 -0
- package/dist/components/buttons/LiteExportButton.svelte +31 -0
- package/dist/components/buttons/LiteExportButton.svelte.d.ts +10 -0
- package/dist/components/buttons/LiteImportButton.svelte +50 -0
- package/dist/components/buttons/LiteImportButton.svelte.d.ts +10 -0
- package/dist/components/buttons/LiteListButton.svelte +31 -0
- package/dist/components/buttons/LiteListButton.svelte.d.ts +10 -0
- package/dist/components/buttons/LiteRefreshButton.svelte +34 -0
- package/dist/components/buttons/LiteRefreshButton.svelte.d.ts +8 -0
- package/dist/components/buttons/LiteSaveButton.svelte +27 -0
- package/dist/components/buttons/LiteSaveButton.svelte.d.ts +8 -0
- package/dist/components/buttons/LiteShowButton.svelte +33 -0
- package/dist/components/buttons/LiteShowButton.svelte.d.ts +11 -0
- package/dist/components/buttons/index.js +10 -0
- package/dist/components/fields/LiteBooleanField.svelte +42 -0
- package/dist/components/fields/LiteBooleanField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteDateField.svelte +51 -0
- package/dist/components/fields/LiteDateField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteEmailField.svelte +41 -0
- package/dist/components/fields/LiteEmailField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteFileField.svelte +59 -0
- package/dist/components/fields/LiteFileField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteImageField.svelte +57 -0
- package/dist/components/fields/LiteImageField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteJsonField.svelte +43 -0
- package/dist/components/fields/LiteJsonField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteMarkdownField.svelte +33 -0
- package/dist/components/fields/LiteMarkdownField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteMultiSelectField.svelte +57 -0
- package/dist/components/fields/LiteMultiSelectField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteNumberField.svelte +35 -0
- package/dist/components/fields/LiteNumberField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteRelationField.svelte +67 -0
- package/dist/components/fields/LiteRelationField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteRichTextField.svelte +44 -0
- package/dist/components/fields/LiteRichTextField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteSelectField.svelte +47 -0
- package/dist/components/fields/LiteSelectField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteTagField.svelte +44 -0
- package/dist/components/fields/LiteTagField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteTextField.svelte +35 -0
- package/dist/components/fields/LiteTextField.svelte.d.ts +10 -0
- package/dist/components/fields/LiteUrlField.svelte +52 -0
- package/dist/components/fields/LiteUrlField.svelte.d.ts +10 -0
- package/dist/components/fields/index.js +15 -0
- package/dist/components/layout/LiteAuthenticated.svelte +23 -0
- package/dist/components/layout/LiteAuthenticated.svelte.d.ts +10 -0
- package/dist/components/layout/LiteCanAccess.svelte +27 -0
- package/dist/components/layout/LiteCanAccess.svelte.d.ts +10 -0
- package/dist/components/layout/LiteCatchAllNavigate.svelte +20 -0
- package/dist/components/layout/LiteCatchAllNavigate.svelte.d.ts +7 -0
- package/dist/components/layout/LiteErrorBoundary.svelte +20 -0
- package/dist/components/layout/LiteErrorBoundary.svelte.d.ts +13 -0
- package/dist/components/layout/LiteErrorComponent.svelte +37 -0
- package/dist/components/layout/LiteErrorComponent.svelte.d.ts +9 -0
- package/dist/components/layout/LiteHeader.svelte +19 -0
- package/dist/components/layout/LiteHeader.svelte.d.ts +8 -0
- package/dist/components/layout/LiteNavigateToResource.svelte +25 -0
- package/dist/components/layout/LiteNavigateToResource.svelte.d.ts +7 -0
- package/dist/components/layout/LiteSidebar.svelte +93 -0
- package/dist/components/layout/LiteSidebar.svelte.d.ts +12 -0
- package/dist/components/layout/index.js +8 -0
- package/dist/components/pages/LiteCreatePage.svelte +39 -0
- package/dist/components/pages/LiteCreatePage.svelte.d.ts +10 -0
- package/dist/components/pages/LiteEditPage.svelte +54 -0
- package/dist/components/pages/LiteEditPage.svelte.d.ts +12 -0
- package/dist/components/pages/LiteForgotPasswordPage.svelte +60 -0
- package/dist/components/pages/LiteForgotPasswordPage.svelte.d.ts +11 -0
- package/dist/components/pages/LiteListPage.svelte +81 -0
- package/dist/components/pages/LiteListPage.svelte.d.ts +20 -0
- package/dist/components/pages/LiteProfilePage.svelte +61 -0
- package/dist/components/pages/LiteProfilePage.svelte.d.ts +14 -0
- package/dist/components/pages/LiteRegisterPage.svelte +63 -0
- package/dist/components/pages/LiteRegisterPage.svelte.d.ts +10 -0
- package/dist/components/pages/LiteShowPage.svelte +61 -0
- package/dist/components/pages/LiteShowPage.svelte.d.ts +11 -0
- package/dist/components/pages/LiteUpdatePasswordPage.svelte +51 -0
- package/dist/components/pages/LiteUpdatePasswordPage.svelte.d.ts +9 -0
- package/dist/components/pages/index.js +8 -0
- package/dist/components/widgets/LiteAnomalyBadge.svelte +40 -0
- package/dist/components/widgets/LiteAnomalyBadge.svelte.d.ts +15 -0
- package/dist/components/widgets/LiteBarChart.svelte +45 -0
- package/dist/components/widgets/LiteBarChart.svelte.d.ts +17 -0
- package/dist/components/widgets/LiteInsightCard.svelte +31 -0
- package/dist/components/widgets/LiteInsightCard.svelte.d.ts +13 -0
- package/dist/components/widgets/LiteLineChart.svelte +48 -0
- package/dist/components/widgets/LiteLineChart.svelte.d.ts +17 -0
- package/dist/components/widgets/LitePieChart.svelte +44 -0
- package/dist/components/widgets/LitePieChart.svelte.d.ts +17 -0
- package/dist/components/widgets/index.js +5 -0
- package/dist/enhance.js +112 -0
- package/dist/index.d.ts +27 -0
- package/{src/index.ts → dist/index.js} +3 -26
- package/dist/lite.css +817 -0
- package/dist/schema-generator.d.ts +29 -0
- package/dist/schema-generator.js +226 -0
- package/dist/security.d.ts +14 -0
- package/dist/security.js +59 -0
- package/dist/server-adapter.d.ts +111 -0
- package/dist/server-adapter.js +422 -0
- package/package.json +57 -20
- package/src/schema-generator.ts +0 -147
- package/src/server-adapter.ts +0 -307
- /package/{src/components/advanced/index.ts → dist/components/advanced/index.d.ts} +0 -0
- /package/{src/components/buttons/index.ts → dist/components/buttons/index.d.ts} +0 -0
- /package/{src/components/fields/index.ts → dist/components/fields/index.d.ts} +0 -0
- /package/{src/components/layout/index.ts → dist/components/layout/index.d.ts} +0 -0
- /package/{src/components/pages/index.ts → dist/components/pages/index.d.ts} +0 -0
- /package/{src/components/widgets/index.ts → dist/components/widgets/index.d.ts} +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LiteConfirmDialog — SSR-compatible confirmation dialog.
|
|
4
|
+
* Utilizes pure HTML <details> and <summary> to show a popup.
|
|
5
|
+
* No client-side JS required.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
confirmLabel?: string;
|
|
12
|
+
cancelLabel?: string;
|
|
13
|
+
/** The summary element (button) that triggers the dropdown */
|
|
14
|
+
triggerLabel: string;
|
|
15
|
+
triggerClass?: string;
|
|
16
|
+
/** Form action to submit on confirm */
|
|
17
|
+
action: string;
|
|
18
|
+
/** Any hidden inputs to include in the form */
|
|
19
|
+
hiddenInputs?: Record<string, string>;
|
|
20
|
+
align?: 'left' | 'right';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
title = 'Are you sure?',
|
|
25
|
+
description = 'This action cannot be undone.',
|
|
26
|
+
confirmLabel = 'Confirm',
|
|
27
|
+
cancelLabel = 'Cancel',
|
|
28
|
+
triggerLabel,
|
|
29
|
+
triggerClass = 'lite-btn lite-btn-danger lite-btn-sm',
|
|
30
|
+
action,
|
|
31
|
+
hiddenInputs = {},
|
|
32
|
+
align = 'right'
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<details class="lite-confirm-details">
|
|
37
|
+
<summary class={triggerClass}>{triggerLabel}</summary>
|
|
38
|
+
<div class="lite-confirm-panel" style="text-align:left; {align === 'right' ? 'right:0;' : 'left:0;'}">
|
|
39
|
+
<div style="font-weight:600;margin-bottom:8px;color:#0f172a;">{title}</div>
|
|
40
|
+
{#if description}
|
|
41
|
+
<div style="font-size:12px;color:#64748b;margin-bottom:16px;">{description}</div>
|
|
42
|
+
{/if}
|
|
43
|
+
<form method="POST" {action} style="display:flex;margin:0;justify-content:flex-end;">
|
|
44
|
+
{#each Object.entries(hiddenInputs) as [key, val] (key)}
|
|
45
|
+
<input type="hidden" name={key} value={val} />
|
|
46
|
+
{/each}
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
class="lite-btn lite-btn-sm"
|
|
50
|
+
style="margin-right:8px;"
|
|
51
|
+
onclick={(e) => (e.currentTarget as HTMLElement).closest('details')?.removeAttribute('open')}
|
|
52
|
+
>
|
|
53
|
+
{cancelLabel}
|
|
54
|
+
</button>
|
|
55
|
+
<button type="submit" class="lite-btn lite-btn-primary lite-btn-sm lite-btn-danger">
|
|
56
|
+
{confirmLabel}
|
|
57
|
+
</button>
|
|
58
|
+
</form>
|
|
59
|
+
</div>
|
|
60
|
+
</details>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteConfirmDialog — SSR-compatible confirmation dialog.
|
|
3
|
+
* Utilizes pure HTML <details> and <summary> to show a popup.
|
|
4
|
+
* No client-side JS required.
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
confirmLabel?: string;
|
|
10
|
+
cancelLabel?: string;
|
|
11
|
+
/** The summary element (button) that triggers the dropdown */
|
|
12
|
+
triggerLabel: string;
|
|
13
|
+
triggerClass?: string;
|
|
14
|
+
/** Form action to submit on confirm */
|
|
15
|
+
action: string;
|
|
16
|
+
/** Any hidden inputs to include in the form */
|
|
17
|
+
hiddenInputs?: Record<string, string>;
|
|
18
|
+
align?: 'left' | 'right';
|
|
19
|
+
}
|
|
20
|
+
declare const LiteConfirmDialog: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type LiteConfirmDialog = ReturnType<typeof LiteConfirmDialog>;
|
|
22
|
+
export default LiteConfirmDialog;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LiteEmptyState — SSR-compatible empty state indicator.
|
|
4
|
+
* Renders a centered box with an icon, title, description, and link.
|
|
5
|
+
* No client-side JS required.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Optional HTML entity or text icon */
|
|
12
|
+
icon?: string;
|
|
13
|
+
actionLabel?: string;
|
|
14
|
+
actionUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
icon,
|
|
21
|
+
actionLabel,
|
|
22
|
+
actionUrl,
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class="lite-empty-state lite-card" style="text-align:center;padding:48px 24px;">
|
|
27
|
+
{#if icon}
|
|
28
|
+
<div style="font-size:32px;color:#94a3b8;margin-bottom:16px;">{icon}</div>
|
|
29
|
+
{/if}
|
|
30
|
+
<h3 style="font-size:16px;font-weight:600;color:#0f172a;margin:0 0 8px 0;">{title}</h3>
|
|
31
|
+
{#if description}
|
|
32
|
+
<p style="font-size:14px;color:#64748b;margin:0 0 24px 0;">{description}</p>
|
|
33
|
+
{/if}
|
|
34
|
+
{#if actionLabel && actionUrl}
|
|
35
|
+
<a href={actionUrl} class="lite-btn lite-btn-primary">
|
|
36
|
+
{actionLabel}
|
|
37
|
+
</a>
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteEmptyState — SSR-compatible empty state indicator.
|
|
3
|
+
* Renders a centered box with an icon, title, description, and link.
|
|
4
|
+
* No client-side JS required.
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Optional HTML entity or text icon */
|
|
10
|
+
icon?: string;
|
|
11
|
+
actionLabel?: string;
|
|
12
|
+
actionUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const LiteEmptyState: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LiteEmptyState = ReturnType<typeof LiteEmptyState>;
|
|
16
|
+
export default LiteEmptyState;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LiteForm — Server-rendered form driven by FieldDefinitions.
|
|
4
|
+
* Uses a native <form> with method="POST" for no-JavaScript submission.
|
|
5
|
+
* Integrates with sveltekit-superforms for server-side validation.
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldDefinition, ResourceDefinition } from '@svadmin/core';
|
|
8
|
+
import { t } from '@svadmin/core/i18n';
|
|
9
|
+
import { fieldToInputType, fieldToPlaceholder } from '../schema-generator';
|
|
10
|
+
import LiteArrayField from './LiteArrayField.svelte';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
fields: FieldDefinition[];
|
|
14
|
+
/** Resource definition (used for primaryKey) */
|
|
15
|
+
resource?: ResourceDefinition;
|
|
16
|
+
/** Existing values (for edit mode) */
|
|
17
|
+
values?: Record<string, unknown>;
|
|
18
|
+
/** Validation errors from server */
|
|
19
|
+
errors?: Record<string, string[]>;
|
|
20
|
+
/** 'create' or 'edit' */
|
|
21
|
+
mode?: 'create' | 'edit';
|
|
22
|
+
/** Form action URL */
|
|
23
|
+
action?: string;
|
|
24
|
+
/** Submit button label */
|
|
25
|
+
submitLabel?: string;
|
|
26
|
+
/** Cancel URL */
|
|
27
|
+
cancelUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
fields,
|
|
32
|
+
resource,
|
|
33
|
+
values = {},
|
|
34
|
+
errors = {},
|
|
35
|
+
mode = 'create',
|
|
36
|
+
action = '',
|
|
37
|
+
submitLabel,
|
|
38
|
+
cancelUrl,
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const formFields = $derived(
|
|
42
|
+
fields.filter(f => {
|
|
43
|
+
if (f.showInForm === false) return false;
|
|
44
|
+
if (mode === 'create' && f.showInCreate === false) return false;
|
|
45
|
+
if (mode === 'edit' && f.showInEdit === false) return false;
|
|
46
|
+
return true;
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
function hasExistingUpload(value: unknown): boolean {
|
|
51
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
52
|
+
if (Array.isArray(value)) return value.some(hasExistingUpload);
|
|
53
|
+
return typeof File !== 'undefined'
|
|
54
|
+
&& value instanceof File
|
|
55
|
+
&& value.size > 0
|
|
56
|
+
&& value.name !== '';
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<form method="POST" action={action || undefined} class="lite-card" enctype="multipart/form-data">
|
|
61
|
+
{#if mode === 'edit'}
|
|
62
|
+
{@const pk = resource?.primaryKey ?? 'id'}
|
|
63
|
+
{#if values[pk] != null}
|
|
64
|
+
<input type="hidden" name="_id" value={String(values[pk])} />
|
|
65
|
+
{/if}
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
{#each formFields as field, _i (_i)}
|
|
69
|
+
{@const inputType = fieldToInputType(field)}
|
|
70
|
+
{@const placeholder = fieldToPlaceholder(field)}
|
|
71
|
+
{@const value = values[field.key]}
|
|
72
|
+
{@const fieldErrors = errors[field.key]}
|
|
73
|
+
{@const hasError = fieldErrors && fieldErrors.length > 0}
|
|
74
|
+
|
|
75
|
+
<div class="lite-form-group">
|
|
76
|
+
{#if field.type === 'array'}
|
|
77
|
+
<LiteArrayField
|
|
78
|
+
{field}
|
|
79
|
+
{mode}
|
|
80
|
+
values={Array.isArray(value)
|
|
81
|
+
? value.filter((item): item is Record<string, unknown> => typeof item === 'object' && item !== null)
|
|
82
|
+
: []}
|
|
83
|
+
/>
|
|
84
|
+
{:else if inputType === 'checkbox'}
|
|
85
|
+
<div class="lite-checkbox-group">
|
|
86
|
+
<input
|
|
87
|
+
type="checkbox"
|
|
88
|
+
name={field.key}
|
|
89
|
+
id={field.key}
|
|
90
|
+
checked={!!value}
|
|
91
|
+
/>
|
|
92
|
+
<label for={field.key}>{field.label}</label>
|
|
93
|
+
</div>
|
|
94
|
+
{:else}
|
|
95
|
+
<label for={field.key}>
|
|
96
|
+
{field.label}
|
|
97
|
+
{#if field.required}<span class="required">*</span>{/if}
|
|
98
|
+
</label>
|
|
99
|
+
|
|
100
|
+
{#if inputType === 'textarea'}
|
|
101
|
+
<textarea
|
|
102
|
+
name={field.key}
|
|
103
|
+
id={field.key}
|
|
104
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
105
|
+
placeholder={placeholder}
|
|
106
|
+
>{value ?? ''}</textarea>
|
|
107
|
+
{:else if inputType === 'select'}
|
|
108
|
+
<select
|
|
109
|
+
name={field.key}
|
|
110
|
+
id={field.key}
|
|
111
|
+
class="lite-select {hasError ? 'lite-input-error' : ''}"
|
|
112
|
+
{...field.type === 'multiselect' ? { multiple: true } : {}}
|
|
113
|
+
>
|
|
114
|
+
{#if field.type !== 'multiselect'}
|
|
115
|
+
<option value="">-- {t('common.select') || 'Select'} --</option>
|
|
116
|
+
{/if}
|
|
117
|
+
{#if field.options}
|
|
118
|
+
{#each field.options as opt, _i (_i)}
|
|
119
|
+
<option
|
|
120
|
+
value={String(opt.value)}
|
|
121
|
+
selected={field.type === 'multiselect' ? (Array.isArray(value) && value.includes(opt.value)) : value === opt.value}
|
|
122
|
+
>
|
|
123
|
+
{opt.label}
|
|
124
|
+
</option>
|
|
125
|
+
{/each}
|
|
126
|
+
{/if}
|
|
127
|
+
</select>
|
|
128
|
+
{:else if inputType === 'file'}
|
|
129
|
+
<input
|
|
130
|
+
type="file"
|
|
131
|
+
name={field.key}
|
|
132
|
+
id={field.key}
|
|
133
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
134
|
+
required={field.required && (mode === 'create' || !hasExistingUpload(value))}
|
|
135
|
+
multiple={field.type === 'images'}
|
|
136
|
+
/>
|
|
137
|
+
{:else}
|
|
138
|
+
<input
|
|
139
|
+
type={inputType}
|
|
140
|
+
name={field.key}
|
|
141
|
+
id={field.key}
|
|
142
|
+
value={String(value ?? '')}
|
|
143
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
144
|
+
placeholder={placeholder}
|
|
145
|
+
{... field.required ? { required: true } : {}}
|
|
146
|
+
/>
|
|
147
|
+
{/if}
|
|
148
|
+
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
{#if hasError}
|
|
152
|
+
{#each fieldErrors as err (err)}
|
|
153
|
+
<div class="lite-error-text">{err}</div>
|
|
154
|
+
{/each}
|
|
155
|
+
{/if}
|
|
156
|
+
</div>
|
|
157
|
+
{/each}
|
|
158
|
+
|
|
159
|
+
<div style="display:flex;align-items:center;margin-top:20px;">
|
|
160
|
+
<button type="submit" class="lite-btn lite-btn-primary">{submitLabel || t('common.save') || 'Save'}</button>
|
|
161
|
+
{#if cancelUrl}
|
|
162
|
+
<a href={cancelUrl} class="lite-btn" style="margin-left:8px;">{t('common.cancel') || 'Cancel'}</a>
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
165
|
+
</form>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteForm — Server-rendered form driven by FieldDefinitions.
|
|
3
|
+
* Uses a native <form> with method="POST" for no-JavaScript submission.
|
|
4
|
+
* Integrates with sveltekit-superforms for server-side validation.
|
|
5
|
+
*/
|
|
6
|
+
import type { FieldDefinition, ResourceDefinition } from '@svadmin/core';
|
|
7
|
+
interface Props {
|
|
8
|
+
fields: FieldDefinition[];
|
|
9
|
+
/** Resource definition (used for primaryKey) */
|
|
10
|
+
resource?: ResourceDefinition;
|
|
11
|
+
/** Existing values (for edit mode) */
|
|
12
|
+
values?: Record<string, unknown>;
|
|
13
|
+
/** Validation errors from server */
|
|
14
|
+
errors?: Record<string, string[]>;
|
|
15
|
+
/** 'create' or 'edit' */
|
|
16
|
+
mode?: 'create' | 'edit';
|
|
17
|
+
/** Form action URL */
|
|
18
|
+
action?: string;
|
|
19
|
+
/** Submit button label */
|
|
20
|
+
submitLabel?: string;
|
|
21
|
+
/** Cancel URL */
|
|
22
|
+
cancelUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
declare const LiteForm: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type LiteForm = ReturnType<typeof LiteForm>;
|
|
26
|
+
export default LiteForm;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LiteLayout — Minimal server-rendered sidebar + main layout.
|
|
4
|
+
* No client-side JS required. Supports multi-level menus via native HTML.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResourceDefinition, MenuItem } from '@svadmin/core';
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
resources: ResourceDefinition[];
|
|
11
|
+
currentResource?: string;
|
|
12
|
+
brandName?: string;
|
|
13
|
+
userName?: string;
|
|
14
|
+
basePath?: string;
|
|
15
|
+
/** Optional multi-level menu configuration */
|
|
16
|
+
menu?: MenuItem[];
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
resources,
|
|
22
|
+
currentResource = '',
|
|
23
|
+
brandName = 'Admin',
|
|
24
|
+
userName = '',
|
|
25
|
+
basePath = '/lite',
|
|
26
|
+
menu,
|
|
27
|
+
children,
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const menuResources = $derived(
|
|
31
|
+
resources.filter((r: ResourceDefinition) => r.showInMenu !== false)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const mobileItems = $derived.by(() => {
|
|
35
|
+
if (!menu?.length) {
|
|
36
|
+
return menuResources.map((resource): MenuItem => ({
|
|
37
|
+
name: resource.name,
|
|
38
|
+
label: resource.label,
|
|
39
|
+
href: `${basePath}/${resource.name}`,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result: MenuItem[] = [];
|
|
44
|
+
const visit = (items: MenuItem[]) => {
|
|
45
|
+
for (const item of items) {
|
|
46
|
+
if (item.href || !item.children?.length) result.push(item);
|
|
47
|
+
if (item.children?.length) visit(item.children);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
visit(menu);
|
|
51
|
+
return result;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function isActive(href: string | undefined): boolean {
|
|
55
|
+
if (!href) return false;
|
|
56
|
+
return currentResource === href.replace(basePath + '/', '');
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div class="lite-wrapper">
|
|
61
|
+
<!-- Sidebar -->
|
|
62
|
+
<nav class="lite-sidebar">
|
|
63
|
+
<div class="lite-sidebar-brand">{brandName}</div>
|
|
64
|
+
{#if menu && menu.length > 0}
|
|
65
|
+
{#each menu as item, _i (_i)}
|
|
66
|
+
{#if item.children && item.children.length > 0}
|
|
67
|
+
<details class="lite-menu-group">
|
|
68
|
+
<summary class="lite-menu-parent">{item.label ?? item.name}</summary>
|
|
69
|
+
{#each item.children as child, _i (_i)}
|
|
70
|
+
{#if child.children && child.children.length > 0}
|
|
71
|
+
<details class="lite-menu-group" style="margin-left:12px">
|
|
72
|
+
<summary class="lite-menu-parent">{child.label ?? child.name}</summary>
|
|
73
|
+
{#each child.children as grandchild, _i (_i)}
|
|
74
|
+
<a
|
|
75
|
+
href={grandchild.href ?? `${basePath}/${grandchild.name}`}
|
|
76
|
+
class={isActive(grandchild.href ?? `${basePath}/${grandchild.name}`) ? 'active' : ''}
|
|
77
|
+
target={grandchild.target === '_blank' ? '_blank' : undefined}
|
|
78
|
+
style="padding-left:40px"
|
|
79
|
+
>{grandchild.label ?? grandchild.name}</a>
|
|
80
|
+
{/each}
|
|
81
|
+
</details>
|
|
82
|
+
{:else}
|
|
83
|
+
<a
|
|
84
|
+
href={child.href ?? `${basePath}/${child.name}`}
|
|
85
|
+
class={isActive(child.href ?? `${basePath}/${child.name}`) ? 'active' : ''}
|
|
86
|
+
target={child.target === '_blank' ? '_blank' : undefined}
|
|
87
|
+
style="padding-left:28px"
|
|
88
|
+
>{child.label ?? child.name}</a>
|
|
89
|
+
{/if}
|
|
90
|
+
{/each}
|
|
91
|
+
</details>
|
|
92
|
+
{:else}
|
|
93
|
+
<a
|
|
94
|
+
href={item.href ?? `${basePath}/${item.name}`}
|
|
95
|
+
class={isActive(item.href ?? `${basePath}/${item.name}`) ? 'active' : ''}
|
|
96
|
+
target={item.target === '_blank' ? '_blank' : undefined}
|
|
97
|
+
>{item.label ?? item.name}</a>
|
|
98
|
+
{/if}
|
|
99
|
+
{/each}
|
|
100
|
+
{:else}
|
|
101
|
+
{#each menuResources as res, _i (_i)}
|
|
102
|
+
<a
|
|
103
|
+
href={`${basePath}/${res.name}`}
|
|
104
|
+
class={res.name === currentResource ? 'active' : ''}
|
|
105
|
+
>
|
|
106
|
+
{res.label}
|
|
107
|
+
</a>
|
|
108
|
+
{/each}
|
|
109
|
+
{/if}
|
|
110
|
+
{#if userName}
|
|
111
|
+
<div style="position:absolute;bottom:0;left:0;right:0;padding:12px 16px;border-top:1px solid #334155;font-size:12px;color:#94a3b8;">
|
|
112
|
+
{userName}
|
|
113
|
+
<form method="POST" action={`${basePath}/login?/logout`} style="display:inline;margin-left:8px;">
|
|
114
|
+
<button type="submit" class="lite-btn lite-btn-sm" style="color:#94a3b8;border-color:#475569;background:transparent;">Logout</button>
|
|
115
|
+
</form>
|
|
116
|
+
</div>
|
|
117
|
+
{/if}
|
|
118
|
+
</nav>
|
|
119
|
+
|
|
120
|
+
<nav class="lite-mobile-nav" aria-label="Mobile navigation">
|
|
121
|
+
<details>
|
|
122
|
+
<summary>{brandName}</summary>
|
|
123
|
+
<div class="lite-mobile-nav-links">
|
|
124
|
+
{#each mobileItems as item (item.name)}
|
|
125
|
+
<a
|
|
126
|
+
href={item.href ?? `${basePath}/${item.name}`}
|
|
127
|
+
class={isActive(item.href ?? `${basePath}/${item.name}`) ? 'active' : ''}
|
|
128
|
+
target={item.target === '_blank' ? '_blank' : undefined}
|
|
129
|
+
>{item.label ?? item.name}</a>
|
|
130
|
+
{/each}
|
|
131
|
+
{#if userName}
|
|
132
|
+
<div class="lite-mobile-user">
|
|
133
|
+
<span>{userName}</span>
|
|
134
|
+
<form method="POST" action={`${basePath}/login?/logout`}>
|
|
135
|
+
<button type="submit" class="lite-btn lite-btn-sm">Logout</button>
|
|
136
|
+
</form>
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
</details>
|
|
141
|
+
</nav>
|
|
142
|
+
|
|
143
|
+
<!-- Main Content -->
|
|
144
|
+
<main class="lite-main">
|
|
145
|
+
{@render children()}
|
|
146
|
+
</main>
|
|
147
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteLayout — Minimal server-rendered sidebar + main layout.
|
|
3
|
+
* No client-side JS required. Supports multi-level menus via native HTML.
|
|
4
|
+
*/
|
|
5
|
+
import type { ResourceDefinition, MenuItem } from '@svadmin/core';
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
interface Props {
|
|
8
|
+
resources: ResourceDefinition[];
|
|
9
|
+
currentResource?: string;
|
|
10
|
+
brandName?: string;
|
|
11
|
+
userName?: string;
|
|
12
|
+
basePath?: string;
|
|
13
|
+
/** Optional multi-level menu configuration */
|
|
14
|
+
menu?: MenuItem[];
|
|
15
|
+
children: Snippet;
|
|
16
|
+
}
|
|
17
|
+
declare const LiteLayout: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type LiteLayout = ReturnType<typeof LiteLayout>;
|
|
19
|
+
export default LiteLayout;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LiteLogin — Simple login form for SSR.
|
|
4
|
+
* Posts to SvelteKit form action, no JS needed.
|
|
5
|
+
*/
|
|
6
|
+
import { t } from '@svadmin/core/i18n';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
error?: string;
|
|
10
|
+
brandName?: string;
|
|
11
|
+
action?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { error, brandName = 'Admin', action = '?/login' }: Props = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="lite-login-wrapper">
|
|
18
|
+
<div class="lite-login-card">
|
|
19
|
+
<div class="lite-card">
|
|
20
|
+
<h2 style="text-align:center;margin:0 0 20px;font-size:20px;font-weight:600;">{brandName}</h2>
|
|
21
|
+
|
|
22
|
+
{#if error}
|
|
23
|
+
<div class="lite-alert lite-alert-error">{error}</div>
|
|
24
|
+
{/if}
|
|
25
|
+
|
|
26
|
+
<form method="POST" action={action}>
|
|
27
|
+
<div class="lite-form-group">
|
|
28
|
+
<label for="email">{t('auth.email') || 'Email'}</label>
|
|
29
|
+
<input type="text" name="email" id="email" class="lite-input" placeholder="user@example.com" required />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="lite-form-group">
|
|
32
|
+
<label for="password">{t('auth.password') || 'Password'}</label>
|
|
33
|
+
<input type="password" name="password" id="password" class="lite-input" required />
|
|
34
|
+
</div>
|
|
35
|
+
<button type="submit" class="lite-btn lite-btn-primary" style="width:100%;padding:10px;">
|
|
36
|
+
{t('auth.signIn') || 'Sign In'}
|
|
37
|
+
</button>
|
|
38
|
+
</form>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LitePagination — Pure <a> tag pagination for SSR.
|
|
4
|
+
* No JavaScript. All page transitions are full page navigations.
|
|
5
|
+
*/
|
|
6
|
+
import { t } from '@svadmin/core/i18n';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
page: number;
|
|
10
|
+
totalPages: number;
|
|
11
|
+
/** Base URL that page numbers will be appended to */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Additional parameters to preserve (like sort, order, q) */
|
|
14
|
+
preserveParams?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { page, totalPages, baseUrl = '', preserveParams = {} }: Props = $props();
|
|
18
|
+
|
|
19
|
+
// Show max 7 page links
|
|
20
|
+
const visiblePages = $derived.by(() => {
|
|
21
|
+
const pages: (number | '...')[] = [];
|
|
22
|
+
if (totalPages <= 7) {
|
|
23
|
+
for (let i = 1; i <= totalPages; i++) pages.push(i);
|
|
24
|
+
} else {
|
|
25
|
+
pages.push(1);
|
|
26
|
+
if (page > 3) pages.push('...');
|
|
27
|
+
for (let i = Math.max(2, page - 1); i <= Math.min(totalPages - 1, page + 1); i++) {
|
|
28
|
+
pages.push(i);
|
|
29
|
+
}
|
|
30
|
+
if (page < totalPages - 2) pages.push('...');
|
|
31
|
+
pages.push(totalPages);
|
|
32
|
+
}
|
|
33
|
+
return pages;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function pageUrl(p: number): string {
|
|
37
|
+
const params = new URLSearchParams(preserveParams);
|
|
38
|
+
params.set('page', String(p));
|
|
39
|
+
const qs = params.toString();
|
|
40
|
+
const sep = baseUrl.includes('?') ? '&' : '?';
|
|
41
|
+
return `${baseUrl}${baseUrl && !baseUrl.includes('?') ? '?' : sep}${qs}`;
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if totalPages > 1}
|
|
46
|
+
<nav class="lite-pagination">
|
|
47
|
+
{#if page > 1}
|
|
48
|
+
<a href={pageUrl(page - 1)}>← {t('common.prev') || 'Prev'}</a>
|
|
49
|
+
{:else}
|
|
50
|
+
<span class="disabled">← {t('common.prev') || 'Prev'}</span>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
{#each visiblePages as p, _i (_i)}
|
|
54
|
+
{#if p === '...'}
|
|
55
|
+
<span class="disabled">…</span>
|
|
56
|
+
{:else if p === page}
|
|
57
|
+
<span class="active">{p}</span>
|
|
58
|
+
{:else}
|
|
59
|
+
<a href={pageUrl(p)}>{p}</a>
|
|
60
|
+
{/if}
|
|
61
|
+
{/each}
|
|
62
|
+
|
|
63
|
+
{#if page < totalPages}
|
|
64
|
+
<a href={pageUrl(page + 1)}>{t('common.next') || 'Next'} →</a>
|
|
65
|
+
{:else}
|
|
66
|
+
<span class="disabled">{t('common.next') || 'Next'} →</span>
|
|
67
|
+
{/if}
|
|
68
|
+
|
|
69
|
+
<span class="lite-pagination-info">
|
|
70
|
+
{t('common.pageOf', { page, total: totalPages }) || `Page ${page} of ${totalPages}`}
|
|
71
|
+
</span>
|
|
72
|
+
</nav>
|
|
73
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
page: number;
|
|
3
|
+
totalPages: number;
|
|
4
|
+
/** Base URL that page numbers will be appended to */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Additional parameters to preserve (like sort, order, q) */
|
|
7
|
+
preserveParams?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
declare const LitePagination: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type LitePagination = ReturnType<typeof LitePagination>;
|
|
11
|
+
export default LitePagination;
|