@svadmin/lite 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -6
- package/package.json +6 -2
- package/src/components/LiteArrayField.svelte +112 -0
- package/src/components/LiteAuditLog.svelte +104 -0
- package/src/components/LiteBreadcrumbs.svelte +39 -0
- package/src/components/LiteChatDialog.svelte +101 -0
- package/src/components/LiteConfirmDialog.svelte +60 -0
- package/src/components/LiteEmptyState.svelte +39 -0
- package/src/components/LiteLayout.svelte +58 -10
- package/src/components/LitePermissionMatrix.svelte +147 -0
- package/src/components/LiteShow.svelte +3 -38
- package/src/components/LiteShowField.svelte +51 -0
- package/src/components/LiteStatsCard.svelte +70 -0
- package/src/components/LiteTabs.svelte +57 -0
- package/src/components/advanced/LiteAutoSaveIndicator.svelte +26 -0
- package/src/components/advanced/LiteDraggableHeader.svelte +33 -0
- package/src/components/advanced/LiteDrawerForm.svelte +42 -0
- package/src/components/advanced/LiteInlineEdit.svelte +32 -0
- package/src/components/advanced/LiteModalForm.svelte +44 -0
- package/src/components/advanced/LiteToast.svelte +34 -0
- package/src/components/advanced/LiteUndoableNotification.svelte +25 -0
- package/src/components/advanced/LiteVirtualTable.svelte +44 -0
- package/src/components/advanced/index.ts +8 -0
- package/src/components/buttons/LiteCloneButton.svelte +33 -0
- package/src/components/buttons/LiteCreateButton.svelte +31 -0
- package/src/components/buttons/LiteDeleteButton.svelte +57 -0
- package/src/components/buttons/LiteEditButton.svelte +33 -0
- package/src/components/buttons/LiteExportButton.svelte +31 -0
- package/src/components/buttons/LiteImportButton.svelte +50 -0
- package/src/components/buttons/LiteListButton.svelte +31 -0
- package/src/components/buttons/LiteRefreshButton.svelte +27 -0
- package/src/components/buttons/LiteSaveButton.svelte +27 -0
- package/src/components/buttons/LiteShowButton.svelte +33 -0
- package/src/components/buttons/index.ts +10 -0
- package/src/components/fields/LiteBooleanField.svelte +41 -0
- package/src/components/fields/LiteDateField.svelte +51 -0
- package/src/components/fields/LiteEmailField.svelte +40 -0
- package/src/components/fields/LiteFileField.svelte +53 -0
- package/src/components/fields/LiteImageField.svelte +57 -0
- package/src/components/fields/LiteJsonField.svelte +43 -0
- package/src/components/fields/LiteMarkdownField.svelte +33 -0
- package/src/components/fields/LiteMultiSelectField.svelte +57 -0
- package/src/components/fields/LiteNumberField.svelte +34 -0
- package/src/components/fields/LiteRelationField.svelte +66 -0
- package/src/components/fields/LiteRichTextField.svelte +45 -0
- package/src/components/fields/LiteSelectField.svelte +47 -0
- package/src/components/fields/LiteTagField.svelte +44 -0
- package/src/components/fields/LiteTextField.svelte +34 -0
- package/src/components/fields/LiteUrlField.svelte +40 -0
- package/src/components/fields/index.ts +15 -0
- package/src/components/layout/LiteAuthenticated.svelte +23 -0
- package/src/components/layout/LiteCanAccess.svelte +27 -0
- package/src/components/layout/LiteCatchAllNavigate.svelte +20 -0
- package/src/components/layout/LiteErrorBoundary.svelte +20 -0
- package/src/components/layout/LiteErrorComponent.svelte +37 -0
- package/src/components/layout/LiteHeader.svelte +19 -0
- package/src/components/layout/LiteNavigateToResource.svelte +25 -0
- package/src/components/layout/LiteSidebar.svelte +93 -0
- package/src/components/layout/index.ts +8 -0
- package/src/components/pages/LiteCreatePage.svelte +39 -0
- package/src/components/pages/LiteEditPage.svelte +54 -0
- package/src/components/pages/LiteForgotPasswordPage.svelte +60 -0
- package/src/components/pages/LiteListPage.svelte +77 -0
- package/src/components/pages/LiteProfilePage.svelte +61 -0
- package/src/components/pages/LiteRegisterPage.svelte +64 -0
- package/src/components/pages/LiteShowPage.svelte +61 -0
- package/src/components/pages/LiteUpdatePasswordPage.svelte +51 -0
- package/src/components/pages/index.ts +8 -0
- package/src/components/widgets/LiteAnomalyBadge.svelte +40 -0
- package/src/components/widgets/LiteBarChart.svelte +45 -0
- package/src/components/widgets/LiteInsightCard.svelte +28 -0
- package/src/components/widgets/LiteLineChart.svelte +48 -0
- package/src/components/widgets/LitePieChart.svelte +44 -0
- package/src/components/widgets/index.ts +5 -0
- package/src/index.ts +28 -0
- package/src/lite.css +372 -124
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Copy } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
recordItemId: string | number;
|
|
8
|
+
basePath?: string;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
size?: 'sm' | 'default' | 'icon';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
resource,
|
|
16
|
+
recordItemId,
|
|
17
|
+
basePath = '/lite',
|
|
18
|
+
hideText = false,
|
|
19
|
+
class: className = '',
|
|
20
|
+
size = 'default'
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<a
|
|
25
|
+
href="{basePath}/{resource}/create?cloneId={recordItemId}"
|
|
26
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
27
|
+
title={t('common.clone') || 'Clone'}
|
|
28
|
+
>
|
|
29
|
+
<Copy class="h-4 w-4" />
|
|
30
|
+
{#if !hideText}
|
|
31
|
+
<span style="marginLeft: 4px">{t('common.clone') || 'Clone'}</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</a>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Plus } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
basePath?: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
size?: 'sm' | 'default' | 'icon';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
resource,
|
|
15
|
+
basePath = '/lite',
|
|
16
|
+
hideText = false,
|
|
17
|
+
class: className = '',
|
|
18
|
+
size = 'default'
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<a
|
|
23
|
+
href="{basePath}/{resource}/create"
|
|
24
|
+
class="lite-btn lite-btn-primary {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
25
|
+
title={t('common.create') || 'Create'}
|
|
26
|
+
>
|
|
27
|
+
<Plus class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}
|
|
29
|
+
<span style="marginLeft: 4px">{t('common.create') || 'Create'}</span>
|
|
30
|
+
{/if}
|
|
31
|
+
</a>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Trash2 } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
recordItemId: string | number;
|
|
8
|
+
basePath?: string;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
size?: 'sm' | 'default' | 'icon';
|
|
12
|
+
redirectUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
resource,
|
|
17
|
+
recordItemId,
|
|
18
|
+
basePath = '/lite',
|
|
19
|
+
hideText = false,
|
|
20
|
+
class: className = '',
|
|
21
|
+
size = 'default',
|
|
22
|
+
redirectUrl = ''
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<details class="lite-confirm-details {className}">
|
|
27
|
+
<summary
|
|
28
|
+
class="lite-btn lite-btn-danger {size === 'sm' ? 'lite-btn-sm' : ''}"
|
|
29
|
+
title={t('common.delete') || 'Delete'}
|
|
30
|
+
>
|
|
31
|
+
<Trash2 class="h-4 w-4" />
|
|
32
|
+
{#if !hideText}
|
|
33
|
+
<span style="marginLeft: 4px">{t('common.delete') || 'Delete'}</span>
|
|
34
|
+
{/if}
|
|
35
|
+
</summary>
|
|
36
|
+
<div class="lite-confirm-panel" style="right: 0; left: auto;">
|
|
37
|
+
<p style="margin: 0 0 8px; font-size: 13px; color: #111827;">
|
|
38
|
+
{t('common.areYouSure') || 'Are you sure?'}
|
|
39
|
+
</p>
|
|
40
|
+
<form method="POST" action="?/{resource}_delete" style="display:inline-flex; gap: 8px;">
|
|
41
|
+
<input type="hidden" name="id" value={String(recordItemId)} />
|
|
42
|
+
{#if redirectUrl}
|
|
43
|
+
<input type="hidden" name="redirect" value={redirectUrl} />
|
|
44
|
+
{/if}
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="lite-btn lite-btn-sm"
|
|
48
|
+
onclick="this.closest('details').removeAttribute('open')"
|
|
49
|
+
>
|
|
50
|
+
{t('common.cancel') || 'Cancel'}
|
|
51
|
+
</button>
|
|
52
|
+
<button type="submit" class="lite-btn lite-btn-sm lite-btn-danger">
|
|
53
|
+
{t('common.confirm') || 'Confirm'}
|
|
54
|
+
</button>
|
|
55
|
+
</form>
|
|
56
|
+
</div>
|
|
57
|
+
</details>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Pencil } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
recordItemId: string | number;
|
|
8
|
+
basePath?: string;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
size?: 'sm' | 'default' | 'icon';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
resource,
|
|
16
|
+
recordItemId,
|
|
17
|
+
basePath = '/lite',
|
|
18
|
+
hideText = false,
|
|
19
|
+
class: className = '',
|
|
20
|
+
size = 'default'
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<a
|
|
25
|
+
href="{basePath}/{resource}/edit/{recordItemId}"
|
|
26
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
27
|
+
title={t('common.edit') || 'Edit'}
|
|
28
|
+
>
|
|
29
|
+
<Pencil class="h-4 w-4" />
|
|
30
|
+
{#if !hideText}
|
|
31
|
+
<span style="marginLeft: 4px">{t('common.edit') || 'Edit'}</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</a>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Download } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
basePath?: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
size?: 'sm' | 'default' | 'icon';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
resource,
|
|
15
|
+
basePath = '/lite',
|
|
16
|
+
hideText = false,
|
|
17
|
+
class: className = '',
|
|
18
|
+
size = 'default'
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<a
|
|
23
|
+
href="?action=export"
|
|
24
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
25
|
+
title={t('common.export') || 'Export'}
|
|
26
|
+
>
|
|
27
|
+
<Download class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}
|
|
29
|
+
<span style="marginLeft: 4px">{t('common.export') || 'Export'}</span>
|
|
30
|
+
{/if}
|
|
31
|
+
</a>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Upload } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
basePath?: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
size?: 'sm' | 'default' | 'icon';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
resource,
|
|
15
|
+
basePath = '/lite',
|
|
16
|
+
hideText = false,
|
|
17
|
+
class: className = '',
|
|
18
|
+
size = 'default'
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<details class="lite-confirm-details {className}">
|
|
23
|
+
<summary
|
|
24
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''}"
|
|
25
|
+
title={t('common.import') || 'Import'}
|
|
26
|
+
>
|
|
27
|
+
<Upload class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}
|
|
29
|
+
<span style="marginLeft: 4px">{t('common.import') || 'Import'}</span>
|
|
30
|
+
{/if}
|
|
31
|
+
</summary>
|
|
32
|
+
<div class="lite-confirm-panel">
|
|
33
|
+
<p style="margin: 0 0 8px; font-size: 13px;">{t('common.importData') || 'Import data (CSV/JSON)'}</p>
|
|
34
|
+
<form method="POST" action="?/{resource}_import" enctype="multipart/form-data" style="display:flex; flex-direction: column; gap: 8px;">
|
|
35
|
+
<input type="file" name="file" accept=".csv,.json" required style="font-size: 13px;" />
|
|
36
|
+
<div style="display:flex; gap: 8px; justify-content: flex-end;">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
class="lite-btn lite-btn-sm"
|
|
40
|
+
onclick="this.closest('details').removeAttribute('open')"
|
|
41
|
+
>
|
|
42
|
+
{t('common.cancel') || 'Cancel'}
|
|
43
|
+
</button>
|
|
44
|
+
<button type="submit" class="lite-btn lite-btn-sm lite-btn-primary">
|
|
45
|
+
{t('common.upload') || 'Upload'}
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
</form>
|
|
49
|
+
</div>
|
|
50
|
+
</details>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { List } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
basePath?: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
size?: 'sm' | 'default' | 'icon';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
resource,
|
|
15
|
+
basePath = '/lite',
|
|
16
|
+
hideText = false,
|
|
17
|
+
class: className = '',
|
|
18
|
+
size = 'default'
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<a
|
|
23
|
+
href="{basePath}/{resource}"
|
|
24
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
25
|
+
title={t('common.list') || 'List'}
|
|
26
|
+
>
|
|
27
|
+
<List class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}
|
|
29
|
+
<span style="marginLeft: 4px">{t('common.list') || 'List'}</span>
|
|
30
|
+
{/if}
|
|
31
|
+
</a>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { RefreshCw } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
hideText?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
size?: 'sm' | 'default' | 'icon';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
hideText = false,
|
|
13
|
+
class: className = '',
|
|
14
|
+
size = 'default'
|
|
15
|
+
}: Props = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<a
|
|
19
|
+
href="?"
|
|
20
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
21
|
+
title={t('common.refresh') || 'Refresh'}
|
|
22
|
+
>
|
|
23
|
+
<RefreshCw class="h-4 w-4" />
|
|
24
|
+
{#if !hideText}
|
|
25
|
+
<span style="marginLeft: 4px">{t('common.refresh') || 'Refresh'}</span>
|
|
26
|
+
{/if}
|
|
27
|
+
</a>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Save } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
hideText?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
size?: 'sm' | 'default' | 'icon';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
hideText = false,
|
|
13
|
+
class: className = '',
|
|
14
|
+
size = 'default'
|
|
15
|
+
}: Props = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<button
|
|
19
|
+
type="submit"
|
|
20
|
+
class="lite-btn lite-btn-primary {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
21
|
+
title={t('common.save') || 'Save'}
|
|
22
|
+
>
|
|
23
|
+
<Save class="h-4 w-4" />
|
|
24
|
+
{#if !hideText}
|
|
25
|
+
<span style="marginLeft: 4px">{t('common.save') || 'Save'}</span>
|
|
26
|
+
{/if}
|
|
27
|
+
</button>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Eye } from 'lucide-svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
resource: string;
|
|
7
|
+
recordItemId: string | number;
|
|
8
|
+
basePath?: string;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
size?: 'sm' | 'default' | 'icon';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
resource,
|
|
16
|
+
recordItemId,
|
|
17
|
+
basePath = '/lite',
|
|
18
|
+
hideText = false,
|
|
19
|
+
class: className = '',
|
|
20
|
+
size = 'default'
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<a
|
|
25
|
+
href="{basePath}/{resource}/show/{recordItemId}"
|
|
26
|
+
class="lite-btn {size === 'sm' ? 'lite-btn-sm' : ''} {className}"
|
|
27
|
+
title={t('common.show') || 'Show'}
|
|
28
|
+
>
|
|
29
|
+
<Eye class="h-4 w-4" />
|
|
30
|
+
{#if !hideText}
|
|
31
|
+
<span style="marginLeft: 4px">{t('common.show') || 'Show'}</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</a>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as LiteCreateButton } from './LiteCreateButton.svelte';
|
|
2
|
+
export { default as LiteEditButton } from './LiteEditButton.svelte';
|
|
3
|
+
export { default as LiteShowButton } from './LiteShowButton.svelte';
|
|
4
|
+
export { default as LiteListButton } from './LiteListButton.svelte';
|
|
5
|
+
export { default as LiteDeleteButton } from './LiteDeleteButton.svelte';
|
|
6
|
+
export { default as LiteCloneButton } from './LiteCloneButton.svelte';
|
|
7
|
+
export { default as LiteExportButton } from './LiteExportButton.svelte';
|
|
8
|
+
export { default as LiteImportButton } from './LiteImportButton.svelte';
|
|
9
|
+
export { default as LiteRefreshButton } from './LiteRefreshButton.svelte';
|
|
10
|
+
export { default as LiteSaveButton } from './LiteSaveButton.svelte';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#if mode === 'show'}
|
|
16
|
+
<div>
|
|
17
|
+
<span class="lite-bool {value ? 'lite-bool-true' : ''}"></span>
|
|
18
|
+
{value ? '✓ Yes' : '✗ No'}
|
|
19
|
+
</div>
|
|
20
|
+
{:else}
|
|
21
|
+
<div class="lite-checkbox-group">
|
|
22
|
+
<input
|
|
23
|
+
type="checkbox"
|
|
24
|
+
name={field.key}
|
|
25
|
+
id={field.key}
|
|
26
|
+
checked={!!value}
|
|
27
|
+
value="true"
|
|
28
|
+
/>
|
|
29
|
+
<!-- Fallback hidden input so unchecked boxes submit "false" -->
|
|
30
|
+
<input type="hidden" name={field.key} value="false" />
|
|
31
|
+
|
|
32
|
+
<label for={field.key}>
|
|
33
|
+
{field.placeholder ?? (field.label || 'Yes')}
|
|
34
|
+
</label>
|
|
35
|
+
{#if hasError}
|
|
36
|
+
{#each error as err}
|
|
37
|
+
<div class="lite-error-text">{err}</div>
|
|
38
|
+
{/each}
|
|
39
|
+
{/if}
|
|
40
|
+
</div>
|
|
41
|
+
{/if}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
|
|
14
|
+
function formatDate(v: unknown): string {
|
|
15
|
+
if (!v) return '';
|
|
16
|
+
try {
|
|
17
|
+
return new Date(v as string).toISOString().substring(0, 16); // Formats for html datetime-local
|
|
18
|
+
} catch {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function displayDate(v: unknown): string {
|
|
24
|
+
if (!v) return '—';
|
|
25
|
+
try {
|
|
26
|
+
return new Date(v as string).toLocaleString();
|
|
27
|
+
} catch {
|
|
28
|
+
return String(v);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if mode === 'show'}
|
|
34
|
+
<span>{displayDate(value)}</span>
|
|
35
|
+
{:else}
|
|
36
|
+
<div>
|
|
37
|
+
<input
|
|
38
|
+
type="datetime-local"
|
|
39
|
+
name={field.key}
|
|
40
|
+
id={field.key}
|
|
41
|
+
value={formatDate(value)}
|
|
42
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
43
|
+
{...field.required ? { required: true } : {}}
|
|
44
|
+
/>
|
|
45
|
+
{#if hasError}
|
|
46
|
+
{#each error as err}
|
|
47
|
+
<div class="lite-error-text">{err}</div>
|
|
48
|
+
{/each}
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#if mode === 'show'}
|
|
16
|
+
<span>
|
|
17
|
+
{#if value}
|
|
18
|
+
<a href="mailto:{value}">{value}</a>
|
|
19
|
+
{:else}
|
|
20
|
+
—
|
|
21
|
+
{/if}
|
|
22
|
+
</span>
|
|
23
|
+
{:else}
|
|
24
|
+
<div>
|
|
25
|
+
<input
|
|
26
|
+
type="email"
|
|
27
|
+
name={field.key}
|
|
28
|
+
id={field.key}
|
|
29
|
+
value={String(value ?? '')}
|
|
30
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
31
|
+
placeholder={field.placeholder ?? field.label}
|
|
32
|
+
{...field.required ? { required: true } : {}}
|
|
33
|
+
/>
|
|
34
|
+
{#if hasError}
|
|
35
|
+
{#each error as err}
|
|
36
|
+
<div class="lite-error-text">{err}</div>
|
|
37
|
+
{/each}
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
|
|
14
|
+
function getFiles(v: unknown): string[] {
|
|
15
|
+
if (!v) return [];
|
|
16
|
+
if (Array.isArray(v)) return v.map(String);
|
|
17
|
+
if (typeof v === 'string') return [v];
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if mode === 'show'}
|
|
23
|
+
{@const files = getFiles(value)}
|
|
24
|
+
<div style="display:flex; flex-direction: column; gap: 4px;">
|
|
25
|
+
{#each files as f}
|
|
26
|
+
<a href={f} target="_blank" rel="noopener noreferrer">{f.split('/').pop() || 'Download'}</a>
|
|
27
|
+
{:else}
|
|
28
|
+
<span>—</span>
|
|
29
|
+
{/each}
|
|
30
|
+
</div>
|
|
31
|
+
{:else}
|
|
32
|
+
<div>
|
|
33
|
+
{@const files = getFiles(value)}
|
|
34
|
+
{#if files.length > 0 && mode === 'edit'}
|
|
35
|
+
<div style="margin-bottom: 8px;">
|
|
36
|
+
<span style="font-size: 12px;">Current files: {files.length}</span>
|
|
37
|
+
</div>
|
|
38
|
+
{/if}
|
|
39
|
+
<input
|
|
40
|
+
type="file"
|
|
41
|
+
name={field.key}
|
|
42
|
+
id={field.key}
|
|
43
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
44
|
+
{...field.type === 'files' ? { multiple: true } : {}}
|
|
45
|
+
{...field.required && !files.length ? { required: true } : {}}
|
|
46
|
+
/>
|
|
47
|
+
{#if hasError}
|
|
48
|
+
{#each error as err}
|
|
49
|
+
<div class="lite-error-text">{err}</div>
|
|
50
|
+
{/each}
|
|
51
|
+
{/if}
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
|
|
14
|
+
function getUrls(v: unknown): string[] {
|
|
15
|
+
if (!v) return [];
|
|
16
|
+
if (Array.isArray(v)) return v.map(String);
|
|
17
|
+
if (typeof v === 'string') return [v];
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if mode === 'show'}
|
|
23
|
+
{@const urls = getUrls(value)}
|
|
24
|
+
<div style="display:flex; gap: 8px; flex-wrap: wrap;">
|
|
25
|
+
{#each urls as url}
|
|
26
|
+
<img src={url} alt={field.label} style="max-height: 100px; border-radius: 4px; border: 1px solid #e5e7eb;" />
|
|
27
|
+
{:else}
|
|
28
|
+
<span>—</span>
|
|
29
|
+
{/each}
|
|
30
|
+
</div>
|
|
31
|
+
{:else}
|
|
32
|
+
<div>
|
|
33
|
+
{@const urls = getUrls(value)}
|
|
34
|
+
{#if urls.length > 0 && mode === 'edit'}
|
|
35
|
+
<div style="margin-bottom: 8px; display:flex; gap: 8px;">
|
|
36
|
+
{#each urls as url}
|
|
37
|
+
<img src={url} alt="Current" style="height: 60px; border-radius: 4px; border: 1px solid #e5e7eb; opacity: 0.6;" />
|
|
38
|
+
{/each}
|
|
39
|
+
</div>
|
|
40
|
+
<p style="font-size: 11px; color:#6b7280; margin: 0 0 4px;">Uploading new ones will overwrite.</p>
|
|
41
|
+
{/if}
|
|
42
|
+
<input
|
|
43
|
+
type="file"
|
|
44
|
+
name={field.key}
|
|
45
|
+
id={field.key}
|
|
46
|
+
accept="image/*"
|
|
47
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
48
|
+
{...field.type === 'images' ? { multiple: true } : {}}
|
|
49
|
+
{...field.required && !urls.length ? { required: true } : {}}
|
|
50
|
+
/>
|
|
51
|
+
{#if hasError}
|
|
52
|
+
{#each error as err}
|
|
53
|
+
<div class="lite-error-text">{err}</div>
|
|
54
|
+
{/each}
|
|
55
|
+
{/if}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
field: FieldDefinition;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
error?: string[];
|
|
8
|
+
mode?: 'show' | 'edit' | 'create';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { field, value, error = [], mode = 'show' }: Props = $props();
|
|
12
|
+
const hasError = error.length > 0;
|
|
13
|
+
|
|
14
|
+
function getJsonString(v: unknown): string {
|
|
15
|
+
if (v == null) return '';
|
|
16
|
+
if (typeof v === 'string') return v;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(v, null, 2);
|
|
19
|
+
} catch {
|
|
20
|
+
return String(v);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if mode === 'show'}
|
|
26
|
+
<pre style="padding: 12px; background: #1e293b; color: #f8fafc; border-radius: 4px; overflow-x: auto; font-size: 13px;">{getJsonString(value)}</pre>
|
|
27
|
+
{:else}
|
|
28
|
+
<div>
|
|
29
|
+
<textarea
|
|
30
|
+
name={field.key}
|
|
31
|
+
id={field.key}
|
|
32
|
+
class="lite-input {hasError ? 'lite-input-error' : ''}"
|
|
33
|
+
style="min-height: 150px; font-family: monospace;"
|
|
34
|
+
placeholder="{}"
|
|
35
|
+
{...field.required ? { required: true } : {}}
|
|
36
|
+
>{getJsonString(value)}</textarea>
|
|
37
|
+
{#if hasError}
|
|
38
|
+
{#each error as err}
|
|
39
|
+
<div class="lite-error-text">{err}</div>
|
|
40
|
+
{/each}
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
43
|
+
{/if}
|