@svadmin/lite 0.3.3 → 0.3.5
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 -4
- package/src/components/LiteAlert.svelte +0 -19
- package/src/components/LiteArrayField.svelte +0 -113
- package/src/components/LiteAuditLog.svelte +0 -104
- package/src/components/LiteBreadcrumbs.svelte +0 -39
- package/src/components/LiteChatDialog.svelte +0 -101
- package/src/components/LiteConfirmDialog.svelte +0 -60
- package/src/components/LiteEmptyState.svelte +0 -39
- package/src/components/LiteForm.svelte +0 -138
- package/src/components/LiteLayout.svelte +0 -106
- package/src/components/LiteLogin.svelte +0 -41
- package/src/components/LitePagination.svelte +0 -73
- package/src/components/LitePermissionMatrix.svelte +0 -147
- package/src/components/LiteSearch.svelte +0 -41
- package/src/components/LiteShow.svelte +0 -61
- package/src/components/LiteShowField.svelte +0 -51
- package/src/components/LiteStatsCard.svelte +0 -70
- package/src/components/LiteTable.svelte +0 -132
- package/src/components/LiteTabs.svelte +0 -57
- package/src/components/advanced/LiteAutoSaveIndicator.svelte +0 -26
- package/src/components/advanced/LiteDraggableHeader.svelte +0 -33
- package/src/components/advanced/LiteDrawerForm.svelte +0 -42
- package/src/components/advanced/LiteInlineEdit.svelte +0 -32
- package/src/components/advanced/LiteModalForm.svelte +0 -44
- package/src/components/advanced/LiteToast.svelte +0 -34
- package/src/components/advanced/LiteUndoableNotification.svelte +0 -25
- package/src/components/advanced/LiteVirtualTable.svelte +0 -44
- package/src/components/buttons/LiteCloneButton.svelte +0 -33
- package/src/components/buttons/LiteCreateButton.svelte +0 -31
- package/src/components/buttons/LiteDeleteButton.svelte +0 -57
- package/src/components/buttons/LiteEditButton.svelte +0 -33
- package/src/components/buttons/LiteExportButton.svelte +0 -31
- package/src/components/buttons/LiteImportButton.svelte +0 -50
- package/src/components/buttons/LiteListButton.svelte +0 -31
- package/src/components/buttons/LiteRefreshButton.svelte +0 -27
- package/src/components/buttons/LiteSaveButton.svelte +0 -27
- package/src/components/buttons/LiteShowButton.svelte +0 -33
- package/src/components/fields/LiteBooleanField.svelte +0 -41
- package/src/components/fields/LiteDateField.svelte +0 -51
- package/src/components/fields/LiteEmailField.svelte +0 -40
- package/src/components/fields/LiteFileField.svelte +0 -53
- package/src/components/fields/LiteImageField.svelte +0 -57
- package/src/components/fields/LiteJsonField.svelte +0 -43
- package/src/components/fields/LiteMarkdownField.svelte +0 -33
- package/src/components/fields/LiteMultiSelectField.svelte +0 -57
- package/src/components/fields/LiteNumberField.svelte +0 -34
- package/src/components/fields/LiteRelationField.svelte +0 -66
- package/src/components/fields/LiteRichTextField.svelte +0 -45
- package/src/components/fields/LiteSelectField.svelte +0 -47
- package/src/components/fields/LiteTagField.svelte +0 -44
- package/src/components/fields/LiteTextField.svelte +0 -34
- package/src/components/fields/LiteUrlField.svelte +0 -40
- package/src/components/layout/LiteAuthenticated.svelte +0 -23
- package/src/components/layout/LiteCanAccess.svelte +0 -27
- package/src/components/layout/LiteCatchAllNavigate.svelte +0 -20
- package/src/components/layout/LiteErrorBoundary.svelte +0 -20
- package/src/components/layout/LiteErrorComponent.svelte +0 -37
- package/src/components/layout/LiteHeader.svelte +0 -19
- package/src/components/layout/LiteNavigateToResource.svelte +0 -25
- package/src/components/layout/LiteSidebar.svelte +0 -93
- package/src/components/pages/LiteCreatePage.svelte +0 -39
- package/src/components/pages/LiteEditPage.svelte +0 -54
- package/src/components/pages/LiteForgotPasswordPage.svelte +0 -60
- package/src/components/pages/LiteListPage.svelte +0 -81
- package/src/components/pages/LiteProfilePage.svelte +0 -61
- package/src/components/pages/LiteRegisterPage.svelte +0 -64
- package/src/components/pages/LiteShowPage.svelte +0 -61
- package/src/components/pages/LiteUpdatePasswordPage.svelte +0 -51
- package/src/components/widgets/LiteAnomalyBadge.svelte +0 -40
- package/src/components/widgets/LiteBarChart.svelte +0 -45
- package/src/components/widgets/LiteInsightCard.svelte +0 -28
- package/src/components/widgets/LiteLineChart.svelte +0 -48
- package/src/components/widgets/LitePieChart.svelte +0 -44
- package/src/lite.css +0 -708
- package/static/enhance.js +0 -56
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* LiteTable — Pure HTML table with <a> sort links.
|
|
4
|
-
* No JavaScript required — sorting and pagination are URL-driven.
|
|
5
|
-
*/
|
|
6
|
-
import type { ResourceDefinition, FieldDefinition } from '@svadmin/core';
|
|
7
|
-
import { t } from '@svadmin/core/i18n';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
records: Record<string, unknown>[];
|
|
11
|
-
resource: ResourceDefinition;
|
|
12
|
-
currentSort?: string;
|
|
13
|
-
currentOrder?: 'asc' | 'desc';
|
|
14
|
-
currentSearch?: string;
|
|
15
|
-
basePath?: string;
|
|
16
|
-
/** Show edit/delete action buttons */
|
|
17
|
-
canEdit?: boolean;
|
|
18
|
-
canDelete?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let {
|
|
22
|
-
records,
|
|
23
|
-
resource,
|
|
24
|
-
currentSort,
|
|
25
|
-
currentOrder = 'asc',
|
|
26
|
-
currentSearch,
|
|
27
|
-
basePath = '/lite',
|
|
28
|
-
canEdit = true,
|
|
29
|
-
canDelete = true,
|
|
30
|
-
}: Props = $props();
|
|
31
|
-
|
|
32
|
-
let pk = $derived(resource.primaryKey ?? 'id');
|
|
33
|
-
const listFields = $derived(
|
|
34
|
-
resource.fields.filter(f => f.showInList !== false)
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
function sortUrl(field: FieldDefinition): string {
|
|
38
|
-
const newOrder = currentSort === field.key && currentOrder === 'asc' ? 'desc' : 'asc';
|
|
39
|
-
const params = new URLSearchParams({ sort: field.key, order: newOrder });
|
|
40
|
-
if (currentSearch) params.set('q', currentSearch);
|
|
41
|
-
return `?${params.toString()}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function sortIndicator(field: FieldDefinition): string {
|
|
45
|
-
if (currentSort !== field.key) return '⇅';
|
|
46
|
-
return currentOrder === 'asc' ? '↑' : '↓';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function formatValue(value: unknown, field: FieldDefinition): string {
|
|
50
|
-
if (value == null) return '—';
|
|
51
|
-
if (field.type === 'boolean') return ''; // handled in template
|
|
52
|
-
if (field.type === 'date') {
|
|
53
|
-
try { return new Date(value as string).toLocaleDateString(); } catch { return String(value); }
|
|
54
|
-
}
|
|
55
|
-
if (field.type === 'select' && field.options) {
|
|
56
|
-
const opt = field.options.find(o => o.value === value);
|
|
57
|
-
return opt?.label ?? String(value);
|
|
58
|
-
}
|
|
59
|
-
if (Array.isArray(value)) return value.join(', ');
|
|
60
|
-
return String(value);
|
|
61
|
-
}
|
|
62
|
-
</script>
|
|
63
|
-
|
|
64
|
-
<table class="lite-table">
|
|
65
|
-
<thead>
|
|
66
|
-
<tr>
|
|
67
|
-
{#each listFields as field}
|
|
68
|
-
<th>
|
|
69
|
-
{#if field.sortable !== false}
|
|
70
|
-
<a href={sortUrl(field)}>
|
|
71
|
-
{field.label}
|
|
72
|
-
<span class="sort-indicator">{sortIndicator(field)}</span>
|
|
73
|
-
</a>
|
|
74
|
-
{:else}
|
|
75
|
-
{field.label}
|
|
76
|
-
{/if}
|
|
77
|
-
</th>
|
|
78
|
-
{/each}
|
|
79
|
-
{#if canEdit || canDelete}
|
|
80
|
-
<th style="text-align:right;">{t('common.actions') || 'Actions'}</th>
|
|
81
|
-
{/if}
|
|
82
|
-
</tr>
|
|
83
|
-
</thead>
|
|
84
|
-
<tbody>
|
|
85
|
-
{#each records as record}
|
|
86
|
-
{@const id = record[pk]}
|
|
87
|
-
<tr>
|
|
88
|
-
{#each listFields as field}
|
|
89
|
-
<td>
|
|
90
|
-
{#if field.type === 'boolean'}
|
|
91
|
-
<span class="lite-bool {record[field.key] ? 'lite-bool-true' : ''}"></span>
|
|
92
|
-
{:else if field.type === 'tags' && Array.isArray(record[field.key])}
|
|
93
|
-
{#each (record[field.key] as string[]).slice(0, 3) as tag}
|
|
94
|
-
<span class="lite-badge">{tag}</span>
|
|
95
|
-
{/each}
|
|
96
|
-
{:else if field.type === 'select' && field.options}
|
|
97
|
-
<span class="lite-badge">{formatValue(record[field.key], field)}</span>
|
|
98
|
-
{:else}
|
|
99
|
-
{formatValue(record[field.key], field)}
|
|
100
|
-
{/if}
|
|
101
|
-
</td>
|
|
102
|
-
{/each}
|
|
103
|
-
{#if canEdit || canDelete}
|
|
104
|
-
<td class="actions">
|
|
105
|
-
{#if canEdit}
|
|
106
|
-
<a href={`${basePath}/${resource.name}/edit/${id}`} class="lite-btn lite-btn-sm">{t('common.edit') || 'Edit'}</a>
|
|
107
|
-
{/if}
|
|
108
|
-
{#if canDelete}
|
|
109
|
-
<!-- Delete uses <details> for no-JS confirmation -->
|
|
110
|
-
<details class="lite-confirm-details">
|
|
111
|
-
<summary class="lite-btn lite-btn-sm lite-btn-danger">{t('common.delete') || 'Delete'}</summary>
|
|
112
|
-
<div class="lite-confirm-panel">
|
|
113
|
-
<p style="margin:0 0 8px;font-size:13px;">{t('common.areYouSure') || 'Are you sure?'}</p>
|
|
114
|
-
<form method="POST" action="?/delete" style="display:inline;">
|
|
115
|
-
<input type="hidden" name="id" value={String(id)} />
|
|
116
|
-
<button type="submit" class="lite-btn lite-btn-sm lite-btn-danger">{t('common.confirm') || 'Confirm'}</button>
|
|
117
|
-
</form>
|
|
118
|
-
</div>
|
|
119
|
-
</details>
|
|
120
|
-
{/if}
|
|
121
|
-
</td>
|
|
122
|
-
{/if}
|
|
123
|
-
</tr>
|
|
124
|
-
{:else}
|
|
125
|
-
<tr>
|
|
126
|
-
<td colspan={listFields.length + (canEdit || canDelete ? 1 : 0)} style="text-align:center;padding:40px;color:#9ca3af;">
|
|
127
|
-
{t('common.noData') || 'No records found.'}
|
|
128
|
-
</td>
|
|
129
|
-
</tr>
|
|
130
|
-
{/each}
|
|
131
|
-
</tbody>
|
|
132
|
-
</table>
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* LiteTabs — SSR-compatible tab navigation.
|
|
4
|
-
* Uses simple <a> tags and flexbox. No JS required.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
interface TabItem {
|
|
8
|
-
label: string;
|
|
9
|
-
value: string;
|
|
10
|
-
href: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
items: TabItem[];
|
|
15
|
-
activeValue: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let { items, activeValue }: Props = $props();
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<div class="lite-tabs">
|
|
22
|
-
{#each items as item}
|
|
23
|
-
<a href={item.href} class="lite-tab {activeValue === item.value ? 'active' : ''}">
|
|
24
|
-
{item.label}
|
|
25
|
-
</a>
|
|
26
|
-
{/each}
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<style>
|
|
30
|
-
.lite-tabs {
|
|
31
|
-
display: flex;
|
|
32
|
-
flex-wrap: wrap;
|
|
33
|
-
border-bottom: 2px solid #e2e8f0;
|
|
34
|
-
margin-bottom: 24px;
|
|
35
|
-
padding-bottom: 0;
|
|
36
|
-
}
|
|
37
|
-
.lite-tab {
|
|
38
|
-
padding: 8px 16px;
|
|
39
|
-
margin-right: 4px;
|
|
40
|
-
font-size: 14px;
|
|
41
|
-
font-weight: 500;
|
|
42
|
-
color: #64748b;
|
|
43
|
-
border-bottom: 2px solid transparent;
|
|
44
|
-
margin-bottom: -2px;
|
|
45
|
-
text-decoration: none;
|
|
46
|
-
transition: color 0.15s ease, border-color 0.15s ease;
|
|
47
|
-
}
|
|
48
|
-
.lite-tab:hover {
|
|
49
|
-
color: #0f172a;
|
|
50
|
-
text-decoration: none;
|
|
51
|
-
}
|
|
52
|
-
.lite-tab.active {
|
|
53
|
-
color: #4f46e5;
|
|
54
|
-
border-bottom-color: #4f46e5;
|
|
55
|
-
font-weight: 600;
|
|
56
|
-
}
|
|
57
|
-
</style>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR AutoSaveIndicator — Degraded to a static status label.
|
|
4
|
-
* Without JS, there is no auto-save. This component simply renders
|
|
5
|
-
* the last known save status passed from the server.
|
|
6
|
-
*/
|
|
7
|
-
interface Props {
|
|
8
|
-
status?: 'idle' | 'saved' | 'error';
|
|
9
|
-
message?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { status = 'idle', message }: Props = $props();
|
|
13
|
-
|
|
14
|
-
const statusConfig: Record<string, { icon: string; color: string; text: string }> = {
|
|
15
|
-
idle: { icon: '☁', color: '#64748b', text: 'All changes saved' },
|
|
16
|
-
saved: { icon: '✓', color: '#10b981', text: 'Saved' },
|
|
17
|
-
error: { icon: '✕', color: '#ef4444', text: 'Save failed' },
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const cfg = $derived(statusConfig[status] ?? statusConfig.idle);
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<span style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: {cfg.color};">
|
|
24
|
-
<span>{cfg.icon}</span>
|
|
25
|
-
<span>{message ?? cfg.text}</span>
|
|
26
|
-
</span>
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR DraggableHeader — Degraded to a static table header.
|
|
4
|
-
* Column dragging/resizing requires JS mouse/touch events.
|
|
5
|
-
* In lite mode, columns are rendered at natural widths.
|
|
6
|
-
* Sort is achieved via URL query parameters as in LiteTable.
|
|
7
|
-
*/
|
|
8
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
fields: FieldDefinition[];
|
|
12
|
-
currentSort?: string;
|
|
13
|
-
currentOrder?: 'asc' | 'desc';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let { fields, currentSort, currentOrder = 'asc' }: Props = $props();
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<tr>
|
|
20
|
-
{#each fields as field}
|
|
21
|
-
<th style="padding: 10px 12px; text-align: left; font-weight: 600; font-size: 13px; color: #475569; white-space: nowrap;">
|
|
22
|
-
{#if currentSort === field.key}
|
|
23
|
-
<a href="?sort={field.key}&order={currentOrder === 'asc' ? 'desc' : 'asc'}" style="color: #0f172a; text-decoration: none;">
|
|
24
|
-
{field.label} {currentOrder === 'asc' ? '↑' : '↓'}
|
|
25
|
-
</a>
|
|
26
|
-
{:else}
|
|
27
|
-
<a href="?sort={field.key}&order=asc" style="color: inherit; text-decoration: none;">
|
|
28
|
-
{field.label}
|
|
29
|
-
</a>
|
|
30
|
-
{/if}
|
|
31
|
-
</th>
|
|
32
|
-
{/each}
|
|
33
|
-
</tr>
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR DrawerForm — Degraded to a dedicated page redirect.
|
|
4
|
-
* Side drawers require JS for sliding panel rendering. In lite mode,
|
|
5
|
-
* this component works identically to LiteModalForm: a plain link.
|
|
6
|
-
*/
|
|
7
|
-
import type { ResourceDefinition } from '@svadmin/core';
|
|
8
|
-
import { t } from '@svadmin/core/i18n';
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
resource: ResourceDefinition;
|
|
12
|
-
mode?: 'create' | 'edit';
|
|
13
|
-
recordId?: string | number;
|
|
14
|
-
basePath?: string;
|
|
15
|
-
label?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let {
|
|
19
|
-
resource,
|
|
20
|
-
mode = 'create',
|
|
21
|
-
recordId,
|
|
22
|
-
basePath = '/lite',
|
|
23
|
-
label,
|
|
24
|
-
}: Props = $props();
|
|
25
|
-
|
|
26
|
-
const href = $derived(
|
|
27
|
-
mode === 'create'
|
|
28
|
-
? `${basePath}/${resource.name}/create`
|
|
29
|
-
: `${basePath}/${resource.name}/edit/${recordId}`
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const buttonLabel = $derived(
|
|
33
|
-
label ??
|
|
34
|
-
(mode === 'create'
|
|
35
|
-
? `${t('common.create') || 'Create'} ${resource.label || resource.name}`
|
|
36
|
-
: `${t('common.edit') || 'Edit'} ${resource.label || resource.name}`)
|
|
37
|
-
);
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<a href={href} class="lite-btn lite-btn-outline">
|
|
41
|
-
{buttonLabel}
|
|
42
|
-
</a>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR InlineEdit — Degraded to a show-value + edit-link pattern.
|
|
4
|
-
* True inline editing requires JS for focus management, key events,
|
|
5
|
-
* and async save. In lite mode, we display the value as static text
|
|
6
|
-
* and provide a small "edit" link that navigates to the edit page.
|
|
7
|
-
*/
|
|
8
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
field: FieldDefinition;
|
|
12
|
-
value: unknown;
|
|
13
|
-
editUrl?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let { field, value, editUrl }: Props = $props();
|
|
17
|
-
|
|
18
|
-
const displayValue = $derived(
|
|
19
|
-
value == null ? '—' : String(value)
|
|
20
|
-
);
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<span style="display: inline-flex; align-items: center; gap: 6px;">
|
|
24
|
-
<span>{displayValue}</span>
|
|
25
|
-
{#if editUrl}
|
|
26
|
-
<a
|
|
27
|
-
href={editUrl}
|
|
28
|
-
style="font-size: 11px; color: #6366f1; text-decoration: none;"
|
|
29
|
-
title="Edit {field.label}"
|
|
30
|
-
>✎</a>
|
|
31
|
-
{/if}
|
|
32
|
-
</span>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR ModalForm — Degraded to a dedicated page redirect.
|
|
4
|
-
* True modals require JS for overlay rendering. In lite mode,
|
|
5
|
-
* this component simply renders a link that navigates to the
|
|
6
|
-
* create/edit page for the resource (a full page load).
|
|
7
|
-
*/
|
|
8
|
-
import type { ResourceDefinition } from '@svadmin/core';
|
|
9
|
-
import { t } from '@svadmin/core/i18n';
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
resource: ResourceDefinition;
|
|
13
|
-
mode?: 'create' | 'edit';
|
|
14
|
-
recordId?: string | number;
|
|
15
|
-
basePath?: string;
|
|
16
|
-
label?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let {
|
|
20
|
-
resource,
|
|
21
|
-
mode = 'create',
|
|
22
|
-
recordId,
|
|
23
|
-
basePath = '/lite',
|
|
24
|
-
label,
|
|
25
|
-
}: Props = $props();
|
|
26
|
-
|
|
27
|
-
const href = $derived(
|
|
28
|
-
mode === 'create'
|
|
29
|
-
? `${basePath}/${resource.name}/create`
|
|
30
|
-
: `${basePath}/${resource.name}/edit/${recordId}`
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const buttonLabel = $derived(
|
|
34
|
-
label ??
|
|
35
|
-
(mode === 'create'
|
|
36
|
-
? `${t('common.create') || 'Create'} ${resource.label || resource.name}`
|
|
37
|
-
: `${t('common.edit') || 'Edit'} ${resource.label || resource.name}`)
|
|
38
|
-
);
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<!-- In lite mode, modals degrade to a full-page navigation -->
|
|
42
|
-
<a href={href} class="lite-btn lite-btn-primary">
|
|
43
|
-
{buttonLabel}
|
|
44
|
-
</a>
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR Toast — Degraded to a static alert banner.
|
|
4
|
-
* In SSR-only mode, toast notifications are rendered as inline banners
|
|
5
|
-
* by the server and included in the page HTML. They persist until the
|
|
6
|
-
* next full-page navigation (no auto-dismiss without JS).
|
|
7
|
-
*/
|
|
8
|
-
interface Props {
|
|
9
|
-
message: string;
|
|
10
|
-
type?: 'success' | 'error' | 'warning' | 'info';
|
|
11
|
-
dismissUrl?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let { message, type = 'info', dismissUrl }: Props = $props();
|
|
15
|
-
|
|
16
|
-
const colors: Record<string, { bg: string; border: string; text: string }> = {
|
|
17
|
-
success: { bg: '#ecfdf5', border: '#a7f3d0', text: '#065f46' },
|
|
18
|
-
error: { bg: '#fef2f2', border: '#fecaca', text: '#991b1b' },
|
|
19
|
-
warning: { bg: '#fffbeb', border: '#fed7aa', text: '#92400e' },
|
|
20
|
-
info: { bg: '#eff6ff', border: '#bfdbfe', text: '#1e40af' },
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const style = $derived(colors[type] ?? colors.info);
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<div
|
|
27
|
-
role="alert"
|
|
28
|
-
style="padding: 12px 16px; background: {style.bg}; border: 1px solid {style.border}; color: {style.text}; border-radius: 6px; margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; font-size: 14px;"
|
|
29
|
-
>
|
|
30
|
-
<span>{message}</span>
|
|
31
|
-
{#if dismissUrl}
|
|
32
|
-
<a href={dismissUrl} style="color: {style.text}; text-decoration: none; font-weight: 600; margin-left: 12px;">✕</a>
|
|
33
|
-
{/if}
|
|
34
|
-
</div>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR UndoableNotification — Degraded to a static banner with an undo link.
|
|
4
|
-
* Without JS there's no countdown timer. Instead, the server provides an
|
|
5
|
-
* undo URL that performs the reversal action. If the user navigates away,
|
|
6
|
-
* the action is considered confirmed.
|
|
7
|
-
*/
|
|
8
|
-
interface Props {
|
|
9
|
-
message: string;
|
|
10
|
-
undoUrl: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let { message, undoUrl }: Props = $props();
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<div
|
|
17
|
-
role="alert"
|
|
18
|
-
style="padding: 12px 16px; background: #fefce8; border: 1px solid #fde68a; color: #713f12; border-radius: 6px; margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; font-size: 14px;"
|
|
19
|
-
>
|
|
20
|
-
<span>{message}</span>
|
|
21
|
-
<a
|
|
22
|
-
href={undoUrl}
|
|
23
|
-
style="color: #b45309; font-weight: 600; text-decoration: underline; margin-left: 16px; white-space: nowrap;"
|
|
24
|
-
>Undo</a>
|
|
25
|
-
</div>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR VirtualTable — Identical to LiteTable.
|
|
4
|
-
* Virtual scrolling (rendering only visible rows) requires JS.
|
|
5
|
-
* In lite mode, all rows are rendered server-side. For large datasets,
|
|
6
|
-
* rely on server-side pagination instead.
|
|
7
|
-
*/
|
|
8
|
-
import type { ResourceDefinition } from '@svadmin/core';
|
|
9
|
-
import LiteTable from '../LiteTable.svelte';
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
records: Record<string, unknown>[];
|
|
13
|
-
resource: ResourceDefinition;
|
|
14
|
-
currentSort?: string;
|
|
15
|
-
currentOrder?: 'asc' | 'desc';
|
|
16
|
-
currentSearch?: string;
|
|
17
|
-
basePath?: string;
|
|
18
|
-
canEdit?: boolean;
|
|
19
|
-
canDelete?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let {
|
|
23
|
-
records,
|
|
24
|
-
resource,
|
|
25
|
-
currentSort,
|
|
26
|
-
currentOrder = 'asc',
|
|
27
|
-
currentSearch,
|
|
28
|
-
basePath = '/lite',
|
|
29
|
-
canEdit = true,
|
|
30
|
-
canDelete = true,
|
|
31
|
-
}: Props = $props();
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<!-- Virtual scrolling is not available without JS. All rows are rendered. -->
|
|
35
|
-
<LiteTable
|
|
36
|
-
{records}
|
|
37
|
-
{resource}
|
|
38
|
-
{currentSort}
|
|
39
|
-
{currentOrder}
|
|
40
|
-
{currentSearch}
|
|
41
|
-
{basePath}
|
|
42
|
-
{canEdit}
|
|
43
|
-
{canDelete}
|
|
44
|
-
/>
|
|
@@ -1,33 +0,0 @@
|
|
|
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}/clone/${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>
|
|
@@ -1,31 +0,0 @@
|
|
|
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>
|
|
@@ -1,57 +0,0 @@
|
|
|
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="?/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={(e) => (e.currentTarget as HTMLElement).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>
|
|
@@ -1,33 +0,0 @@
|
|
|
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>
|
|
@@ -1,31 +0,0 @@
|
|
|
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>
|