@svadmin/ui 0.29.1 → 0.29.3
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 +20 -9
- package/src/README.md +0 -18
- package/src/actions.ts +0 -110
- package/src/app.css +0 -673
- package/src/component-registry.svelte.ts +0 -60
- package/src/components/AICommandBar.svelte +0 -180
- package/src/components/AboutSettings.svelte +0 -106
- package/src/components/AdminApp.svelte +0 -199
- package/src/components/AnimatedCounter.svelte +0 -46
- package/src/components/AnomalyBadge.svelte +0 -65
- package/src/components/AppearanceSettings.svelte +0 -183
- package/src/components/AuditLogDrawer.svelte +0 -93
- package/src/components/AuditLogViewer.svelte +0 -226
- package/src/components/Authenticated.svelte +0 -51
- package/src/components/AutoForm.svelte +0 -242
- package/src/components/AutoSaveIndicator.svelte +0 -35
- package/src/components/AutoTable.svelte +0 -839
- package/src/components/Breadcrumbs.svelte +0 -58
- package/src/components/CanAccess.svelte +0 -23
- package/src/components/CatchAllNavigate.svelte +0 -21
- package/src/components/ChatDialog.svelte +0 -535
- package/src/components/ComboboxField.svelte +0 -129
- package/src/components/CommandPalette.svelte +0 -135
- package/src/components/ConfigErrorScreen.svelte +0 -126
- package/src/components/ConfirmDialog.svelte +0 -53
- package/src/components/CopilotPanel.svelte +0 -163
- package/src/components/CreatePage.svelte +0 -39
- package/src/components/DevTools.svelte +0 -149
- package/src/components/DraggableGrid.svelte +0 -86
- package/src/components/DraggableHeader.svelte +0 -112
- package/src/components/DrawerForm.svelte +0 -35
- package/src/components/EditPage.svelte +0 -52
- package/src/components/EmptyState.svelte +0 -31
- package/src/components/ErrorBoundary.svelte +0 -49
- package/src/components/ErrorComponent.svelte +0 -36
- package/src/components/FieldRenderer.svelte +0 -357
- package/src/components/ForgotPasswordPage.svelte +0 -112
- package/src/components/Header.svelte +0 -94
- package/src/components/InferencerPanel.svelte +0 -192
- package/src/components/InfiniteList.svelte +0 -93
- package/src/components/InlineEdit.svelte +0 -88
- package/src/components/InsightCard.svelte +0 -98
- package/src/components/KeyboardShortcuts.svelte +0 -61
- package/src/components/Layout.svelte +0 -167
- package/src/components/ListPage.svelte +0 -71
- package/src/components/LiveIndicator.svelte +0 -58
- package/src/components/LoginPage.svelte +0 -163
- package/src/components/MarkdownRenderer.svelte +0 -127
- package/src/components/ModalForm.svelte +0 -38
- package/src/components/NavigateToResource.svelte +0 -20
- package/src/components/PageHeader.svelte +0 -39
- package/src/components/PageSkeleton.svelte +0 -71
- package/src/components/PasswordInput.svelte +0 -96
- package/src/components/PermissionMatrix.svelte +0 -185
- package/src/components/ProfilePage.svelte +0 -284
- package/src/components/RegisterPage.svelte +0 -112
- package/src/components/RolesSettings.svelte +0 -146
- package/src/components/SettingsPage.svelte +0 -113
- package/src/components/ShowPage.svelte +0 -73
- package/src/components/Sidebar.svelte +0 -417
- package/src/components/SidebarItem.svelte +0 -129
- package/src/components/SmartSuggest.svelte +0 -158
- package/src/components/StatsCard.svelte +0 -79
- package/src/components/StepsForm.svelte +0 -192
- package/src/components/TaskQueueDrawer.svelte +0 -158
- package/src/components/TenantSwitcher.svelte +0 -84
- package/src/components/ThemedTitle.svelte +0 -32
- package/src/components/Toast.svelte +0 -46
- package/src/components/TooltipButton.svelte +0 -42
- package/src/components/UndoableNotification.svelte +0 -69
- package/src/components/UpdatePasswordPage.svelte +0 -89
- package/src/components/VirtualTable.svelte +0 -76
- package/src/components/VoiceInput.svelte +0 -120
- package/src/components/buttons/CloneButton.svelte +0 -30
- package/src/components/buttons/CreateButton.svelte +0 -29
- package/src/components/buttons/DeleteButton.svelte +0 -59
- package/src/components/buttons/EditButton.svelte +0 -30
- package/src/components/buttons/ExportButton.svelte +0 -30
- package/src/components/buttons/ImportButton.svelte +0 -56
- package/src/components/buttons/ListButton.svelte +0 -23
- package/src/components/buttons/RefreshButton.svelte +0 -36
- package/src/components/buttons/SaveButton.svelte +0 -27
- package/src/components/buttons/ShowButton.svelte +0 -31
- package/src/components/buttons/index.ts +0 -10
- package/src/components/charts/BarChart.svelte +0 -69
- package/src/components/charts/LineChart.svelte +0 -115
- package/src/components/charts/PieChart.svelte +0 -95
- package/src/components/charts/index.ts +0 -3
- package/src/components/fieldComponentMap.ts +0 -79
- package/src/components/fields/ArrayField.svelte +0 -86
- package/src/components/fields/BooleanField.svelte +0 -13
- package/src/components/fields/DateField.svelte +0 -6
- package/src/components/fields/EmailField.svelte +0 -9
- package/src/components/fields/FileField.svelte +0 -14
- package/src/components/fields/ImageField.svelte +0 -9
- package/src/components/fields/JsonField.svelte +0 -72
- package/src/components/fields/MarkdownField.svelte +0 -9
- package/src/components/fields/MultiSelectField.svelte +0 -21
- package/src/components/fields/NumberField.svelte +0 -6
- package/src/components/fields/RelationField.svelte +0 -24
- package/src/components/fields/RichTextField.svelte +0 -35
- package/src/components/fields/SelectField.svelte +0 -14
- package/src/components/fields/TagField.svelte +0 -12
- package/src/components/fields/TextField.svelte +0 -11
- package/src/components/fields/UrlField.svelte +0 -14
- package/src/components/ui/alert/alert-description.svelte +0 -17
- package/src/components/ui/alert/alert-title.svelte +0 -17
- package/src/components/ui/alert/alert.svelte +0 -44
- package/src/components/ui/alert/index.ts +0 -15
- package/src/components/ui/alert-dialog/alert-dialog-content.svelte +0 -29
- package/src/components/ui/alert-dialog/alert-dialog-description.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-header.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +0 -6
- package/src/components/ui/alert-dialog/alert-dialog-title.svelte +0 -16
- package/src/components/ui/alert-dialog/index.ts +0 -20
- package/src/components/ui/avatar/avatar.svelte +0 -63
- package/src/components/ui/avatar/index.ts +0 -2
- package/src/components/ui/badge/badge.svelte +0 -49
- package/src/components/ui/badge/index.ts +0 -3
- package/src/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-item.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-link.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-list.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-page.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-separator.svelte +0 -22
- package/src/components/ui/breadcrumb/breadcrumb.svelte +0 -11
- package/src/components/ui/breadcrumb/index.ts +0 -7
- package/src/components/ui/button/button.svelte +0 -90
- package/src/components/ui/button/index.ts +0 -18
- package/src/components/ui/card/card-action.svelte +0 -23
- package/src/components/ui/card/card-content.svelte +0 -20
- package/src/components/ui/card/card-description.svelte +0 -20
- package/src/components/ui/card/card-footer.svelte +0 -20
- package/src/components/ui/card/card-header.svelte +0 -23
- package/src/components/ui/card/card-title.svelte +0 -15
- package/src/components/ui/card/card.svelte +0 -22
- package/src/components/ui/card/index.ts +0 -25
- package/src/components/ui/checkbox/checkbox.svelte +0 -39
- package/src/components/ui/checkbox/index.ts +0 -6
- package/src/components/ui/collapsible/index.ts +0 -7
- package/src/components/ui/command/command-dialog.svelte +0 -31
- package/src/components/ui/command/index.ts +0 -2
- package/src/components/ui/context-menu/index.ts +0 -11
- package/src/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/components/ui/dialog/dialog-content.svelte +0 -48
- package/src/components/ui/dialog/dialog-description.svelte +0 -17
- package/src/components/ui/dialog/dialog-footer.svelte +0 -32
- package/src/components/ui/dialog/dialog-header.svelte +0 -20
- package/src/components/ui/dialog/dialog-overlay.svelte +0 -17
- package/src/components/ui/dialog/dialog-portal.svelte +0 -7
- package/src/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/components/ui/dialog/dialog.svelte +0 -7
- package/src/components/ui/dialog/index.ts +0 -34
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -43
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -35
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -36
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -10
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -27
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +0 -45
- package/src/components/ui/dropdown-menu/index.ts +0 -21
- package/src/components/ui/input/index.ts +0 -7
- package/src/components/ui/input/input.svelte +0 -48
- package/src/components/ui/label/index.ts +0 -1
- package/src/components/ui/label/label.svelte +0 -18
- package/src/components/ui/pagination/index.ts +0 -7
- package/src/components/ui/pagination/pagination-content.svelte +0 -11
- package/src/components/ui/pagination/pagination-ellipsis.svelte +0 -12
- package/src/components/ui/pagination/pagination-item.svelte +0 -11
- package/src/components/ui/pagination/pagination-link.svelte +0 -26
- package/src/components/ui/pagination/pagination-next.svelte +0 -22
- package/src/components/ui/pagination/pagination-previous.svelte +0 -22
- package/src/components/ui/pagination/pagination.svelte +0 -11
- package/src/components/ui/popover/index.ts +0 -12
- package/src/components/ui/popover/popover-content.svelte +0 -25
- package/src/components/ui/progress/index.ts +0 -1
- package/src/components/ui/progress/progress.svelte +0 -22
- package/src/components/ui/scroll-area/index.ts +0 -1
- package/src/components/ui/scroll-area/scroll-area.svelte +0 -20
- package/src/components/ui/select/index.ts +0 -2
- package/src/components/ui/select/select.svelte +0 -36
- package/src/components/ui/separator/index.ts +0 -7
- package/src/components/ui/separator/separator.svelte +0 -23
- package/src/components/ui/sheet/index.ts +0 -16
- package/src/components/ui/sheet/sheet-content.svelte +0 -28
- package/src/components/ui/sheet/sheet-description.svelte +0 -22
- package/src/components/ui/sheet/sheet-footer.svelte +0 -22
- package/src/components/ui/sheet/sheet-header.svelte +0 -22
- package/src/components/ui/sheet/sheet-title.svelte +0 -22
- package/src/components/ui/sheet/sheet.svelte +0 -78
- package/src/components/ui/skeleton/index.ts +0 -2
- package/src/components/ui/skeleton/skeleton.svelte +0 -21
- package/src/components/ui/switch/index.ts +0 -2
- package/src/components/ui/switch/switch.svelte +0 -49
- package/src/components/ui/table/index.ts +0 -28
- package/src/components/ui/table/table-body.svelte +0 -15
- package/src/components/ui/table/table-caption.svelte +0 -20
- package/src/components/ui/table/table-cell.svelte +0 -15
- package/src/components/ui/table/table-footer.svelte +0 -20
- package/src/components/ui/table/table-head.svelte +0 -15
- package/src/components/ui/table/table-header.svelte +0 -20
- package/src/components/ui/table/table-row.svelte +0 -15
- package/src/components/ui/table/table.svelte +0 -17
- package/src/components/ui/tabs/index.ts +0 -15
- package/src/components/ui/tabs/tabs-content.svelte +0 -30
- package/src/components/ui/tabs/tabs-list.svelte +0 -26
- package/src/components/ui/tabs/tabs-trigger.svelte +0 -37
- package/src/components/ui/tabs/tabs.svelte +0 -27
- package/src/components/ui/textarea/index.ts +0 -2
- package/src/components/ui/textarea/textarea.svelte +0 -24
- package/src/components/ui/tooltip/index.ts +0 -19
- package/src/components/ui/tooltip/tooltip-content.svelte +0 -52
- package/src/components/ui/tooltip/tooltip-portal.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
- package/src/components/ui/tooltip/tooltip.svelte +0 -10
- package/src/editor-config.svelte.ts +0 -23
- package/src/index.ts +0 -155
- package/src/markdown-deps.d.ts +0 -21
- package/src/router-state.svelte.ts +0 -65
- package/src/types.ts +0 -33
- package/src/utils.ts +0 -28
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
export type { FieldDefinition } from '@svadmin/core';
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
7
|
-
import { t } from '@svadmin/core/i18n';
|
|
8
|
-
import { Input } from './ui/input/index.js';
|
|
9
|
-
import { Textarea } from './ui/textarea/index.js';
|
|
10
|
-
import { Switch } from './ui/switch/index.js';
|
|
11
|
-
import { Badge } from './ui/badge/index.js';
|
|
12
|
-
import { Label } from './ui/label/index.js';
|
|
13
|
-
import { Select } from './ui/select/index.js';
|
|
14
|
-
import { Checkbox } from './ui/checkbox/index.js';
|
|
15
|
-
import { Button } from './ui/button/index.js';
|
|
16
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
17
|
-
import ComboboxField from './ComboboxField.svelte';
|
|
18
|
-
import ArrayField from './fields/ArrayField.svelte';
|
|
19
|
-
import { Plus, X } from '@lucide/svelte';
|
|
20
|
-
import type { Snippet } from 'svelte';
|
|
21
|
-
import { getRichTextEditor } from '../editor-config.svelte.js';
|
|
22
|
-
|
|
23
|
-
let { field, value, onchange, children } = $props<{
|
|
24
|
-
field: FieldDefinition;
|
|
25
|
-
value: unknown;
|
|
26
|
-
onchange: (val: unknown) => void;
|
|
27
|
-
/** Optional custom renderer snippet */
|
|
28
|
-
children?: Snippet;
|
|
29
|
-
}>();
|
|
30
|
-
|
|
31
|
-
// Typed accessors
|
|
32
|
-
const strVal = $derived((value as string) ?? '');
|
|
33
|
-
const numVal = $derived((value as number) ?? 0);
|
|
34
|
-
const boolVal = $derived((value as boolean) ?? false);
|
|
35
|
-
const tagsVal = $derived((value as string[]) ?? []);
|
|
36
|
-
const multiVal = $derived((value as (string | number)[]) ?? []);
|
|
37
|
-
const imagesVal = $derived((value as string[]) ?? []);
|
|
38
|
-
|
|
39
|
-
function handleTagKeydown(e: KeyboardEvent) {
|
|
40
|
-
if (e.key === 'Enter') {
|
|
41
|
-
e.preventDefault();
|
|
42
|
-
const input = e.target as HTMLInputElement;
|
|
43
|
-
const tag = input.value.trim();
|
|
44
|
-
if (tag) {
|
|
45
|
-
onchange([...tagsVal, tag]);
|
|
46
|
-
input.value = '';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function removeTag(index: number) {
|
|
52
|
-
onchange(tagsVal.filter((_: string, i: number) => i !== index));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Multiselect toggle
|
|
56
|
-
function toggleMulti(optValue: string | number) {
|
|
57
|
-
if (multiVal.includes(optValue)) {
|
|
58
|
-
onchange(multiVal.filter((v: string | number) => v !== optValue));
|
|
59
|
-
} else {
|
|
60
|
-
onchange([...multiVal, optValue]);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Images management
|
|
65
|
-
function addImage() {
|
|
66
|
-
onchange([...imagesVal, '']);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function updateImage(index: number, url: string) {
|
|
70
|
-
const next = [...imagesVal];
|
|
71
|
-
next[index] = url;
|
|
72
|
-
onchange(next);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function removeImage(index: number) {
|
|
76
|
-
onchange(imagesVal.filter((_: string, i: number) => i !== index));
|
|
77
|
-
}
|
|
78
|
-
</script>
|
|
79
|
-
|
|
80
|
-
<div class="space-y-1.5">
|
|
81
|
-
<Label for={field.key} id="label-{field.key}">
|
|
82
|
-
{field.label}
|
|
83
|
-
{#if field.required}
|
|
84
|
-
<span class="text-destructive">*</span>
|
|
85
|
-
{/if}
|
|
86
|
-
</Label>
|
|
87
|
-
|
|
88
|
-
{#if children}
|
|
89
|
-
{@render children()}
|
|
90
|
-
|
|
91
|
-
{:else if field.type === 'array'}
|
|
92
|
-
<ArrayField {field} {value} {onchange} />
|
|
93
|
-
|
|
94
|
-
{:else if field.type === 'text' || field.type === 'image'}
|
|
95
|
-
<Input
|
|
96
|
-
id={field.key}
|
|
97
|
-
type="text"
|
|
98
|
-
value={strVal}
|
|
99
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
100
|
-
required={field.required}
|
|
101
|
-
placeholder={t('field.enterValue', { label: field.label })}
|
|
102
|
-
/>
|
|
103
|
-
|
|
104
|
-
{:else if field.type === 'email'}
|
|
105
|
-
<Input
|
|
106
|
-
id={field.key}
|
|
107
|
-
type="email"
|
|
108
|
-
value={strVal}
|
|
109
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
110
|
-
required={field.required}
|
|
111
|
-
placeholder="name@example.com"
|
|
112
|
-
/>
|
|
113
|
-
|
|
114
|
-
{:else if field.type === 'url'}
|
|
115
|
-
<Input
|
|
116
|
-
id={field.key}
|
|
117
|
-
type="url"
|
|
118
|
-
value={strVal}
|
|
119
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
120
|
-
required={field.required}
|
|
121
|
-
placeholder="https://"
|
|
122
|
-
/>
|
|
123
|
-
|
|
124
|
-
{:else if field.type === 'phone'}
|
|
125
|
-
<Input
|
|
126
|
-
id={field.key}
|
|
127
|
-
type="tel"
|
|
128
|
-
value={strVal}
|
|
129
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
130
|
-
required={field.required}
|
|
131
|
-
placeholder="+1 (555) 000-0000"
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
{:else if field.type === 'color'}
|
|
135
|
-
<div class="flex items-center gap-3">
|
|
136
|
-
<input
|
|
137
|
-
id={field.key}
|
|
138
|
-
type="color"
|
|
139
|
-
value={strVal || '#000000'}
|
|
140
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
141
|
-
class="h-10 w-14 cursor-pointer rounded-md border border-input bg-background p-1"
|
|
142
|
-
/>
|
|
143
|
-
<Input
|
|
144
|
-
type="text"
|
|
145
|
-
value={strVal}
|
|
146
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
147
|
-
placeholder="#000000"
|
|
148
|
-
class="max-w-32 font-mono text-sm"
|
|
149
|
-
/>
|
|
150
|
-
{#if strVal}
|
|
151
|
-
<span
|
|
152
|
-
class="h-8 w-8 rounded-full border border-border shadow-sm"
|
|
153
|
-
style="background-color: {strVal}"
|
|
154
|
-
></span>
|
|
155
|
-
{/if}
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
{:else if field.type === 'number'}
|
|
159
|
-
<Input
|
|
160
|
-
id={field.key}
|
|
161
|
-
type="number"
|
|
162
|
-
value={String(numVal)}
|
|
163
|
-
oninput={(e) => onchange(parseFloat((e.target as HTMLInputElement).value) || 0)}
|
|
164
|
-
required={field.required}
|
|
165
|
-
/>
|
|
166
|
-
|
|
167
|
-
{:else if field.type === 'richtext'}
|
|
168
|
-
{@const EditorComp = getRichTextEditor()}
|
|
169
|
-
{#if EditorComp}
|
|
170
|
-
<EditorComp
|
|
171
|
-
id={field.key}
|
|
172
|
-
value={strVal}
|
|
173
|
-
placeholder={t('field.enterValue', { label: field.label })}
|
|
174
|
-
preset="full"
|
|
175
|
-
onchange={(html: string) => onchange(html)}
|
|
176
|
-
/>
|
|
177
|
-
{:else}
|
|
178
|
-
<Textarea
|
|
179
|
-
id={field.key}
|
|
180
|
-
value={strVal}
|
|
181
|
-
oninput={(e) => onchange((e.target as HTMLTextAreaElement).value)}
|
|
182
|
-
required={field.required}
|
|
183
|
-
rows={10}
|
|
184
|
-
placeholder={t('field.enterValue', { label: field.label })}
|
|
185
|
-
class="resize-y"
|
|
186
|
-
/>
|
|
187
|
-
{/if}
|
|
188
|
-
|
|
189
|
-
{:else if field.type === 'textarea'}
|
|
190
|
-
<Textarea
|
|
191
|
-
id={field.key}
|
|
192
|
-
value={strVal}
|
|
193
|
-
oninput={(e) => onchange((e.target as HTMLTextAreaElement).value)}
|
|
194
|
-
required={field.required}
|
|
195
|
-
rows={4}
|
|
196
|
-
placeholder={t('field.enterValue', { label: field.label })}
|
|
197
|
-
class="resize-y"
|
|
198
|
-
/>
|
|
199
|
-
|
|
200
|
-
{:else if field.type === 'relation' && field.resource}
|
|
201
|
-
<ComboboxField
|
|
202
|
-
id={field.key}
|
|
203
|
-
resource={field.resource}
|
|
204
|
-
value={value as string | number | null}
|
|
205
|
-
onchange={(v) => onchange(v)}
|
|
206
|
-
optionLabel={field.optionLabel ?? 'title'}
|
|
207
|
-
optionValue={field.optionValue ?? 'id'}
|
|
208
|
-
placeholder={t('field.selectPlaceholder')}
|
|
209
|
-
/>
|
|
210
|
-
|
|
211
|
-
{:else if field.type === 'select'}
|
|
212
|
-
{#if (field.options?.length ?? 0) > 8}
|
|
213
|
-
<!-- Many options → use ComboboxField-style search -->
|
|
214
|
-
<ComboboxField
|
|
215
|
-
id={field.key}
|
|
216
|
-
resource=""
|
|
217
|
-
value={value as string | number | null}
|
|
218
|
-
onchange={(v) => onchange(v)}
|
|
219
|
-
searchable={false}
|
|
220
|
-
/>
|
|
221
|
-
{:else}
|
|
222
|
-
<Select
|
|
223
|
-
id={field.key}
|
|
224
|
-
value={strVal}
|
|
225
|
-
onchange={(e) => onchange((e.target as HTMLSelectElement).value)}
|
|
226
|
-
required={field.required}
|
|
227
|
-
placeholder={t('field.selectPlaceholder')}
|
|
228
|
-
>
|
|
229
|
-
{#each field.options ?? [] as opt}
|
|
230
|
-
<option value={opt.value}>{opt.label}</option>
|
|
231
|
-
{/each}
|
|
232
|
-
</Select>
|
|
233
|
-
{/if}
|
|
234
|
-
|
|
235
|
-
{:else if field.type === 'multiselect'}
|
|
236
|
-
<div
|
|
237
|
-
class="space-y-2 rounded-lg border border-input p-3 max-h-48 overflow-y-auto"
|
|
238
|
-
role="group"
|
|
239
|
-
aria-labelledby="label-{field.key}"
|
|
240
|
-
>
|
|
241
|
-
{#each field.options ?? [] as opt}
|
|
242
|
-
<label class="flex items-center gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-1 py-0.5 transition-colors">
|
|
243
|
-
<Checkbox
|
|
244
|
-
id={`${field.key}-${opt.value}`}
|
|
245
|
-
checked={multiVal.includes(opt.value)}
|
|
246
|
-
onCheckedChange={() => toggleMulti(opt.value)}
|
|
247
|
-
/>
|
|
248
|
-
{opt.label}
|
|
249
|
-
</label>
|
|
250
|
-
{/each}
|
|
251
|
-
{#if !(field.options?.length)}
|
|
252
|
-
<p class="text-xs text-muted-foreground">{t('field.noOptions')}</p>
|
|
253
|
-
{/if}
|
|
254
|
-
</div>
|
|
255
|
-
{#if multiVal.length > 0}
|
|
256
|
-
<div class="flex flex-wrap gap-1 mt-1">
|
|
257
|
-
{#each multiVal as v}
|
|
258
|
-
{@const label = field.options?.find((o: { label: string; value: string | number }) => o.value === v)?.label ?? String(v)}
|
|
259
|
-
<Badge variant="secondary" class="gap-1">
|
|
260
|
-
{label}
|
|
261
|
-
<button type="button" onclick={() => toggleMulti(v)} class="ml-0.5 rounded-sm hover:text-destructive hover:bg-destructive/10 transition-colors" aria-label={t('common.clear')}>×</button>
|
|
262
|
-
</Badge>
|
|
263
|
-
{/each}
|
|
264
|
-
</div>
|
|
265
|
-
{/if}
|
|
266
|
-
|
|
267
|
-
{:else if field.type === 'boolean'}
|
|
268
|
-
<div class="flex items-center gap-2 pt-1">
|
|
269
|
-
<Switch
|
|
270
|
-
id={field.key}
|
|
271
|
-
checked={boolVal}
|
|
272
|
-
onCheckedChange={(v) => onchange(v)}
|
|
273
|
-
/>
|
|
274
|
-
<span class="text-sm text-muted-foreground">{boolVal ? t('common.yes') : t('common.no')}</span>
|
|
275
|
-
</div>
|
|
276
|
-
|
|
277
|
-
{:else if field.type === 'tags'}
|
|
278
|
-
<div class="space-y-2">
|
|
279
|
-
<div class="flex flex-wrap gap-1.5">
|
|
280
|
-
{#each tagsVal as tag, i}
|
|
281
|
-
<Badge variant="secondary" class="gap-1">
|
|
282
|
-
{tag}
|
|
283
|
-
<button
|
|
284
|
-
type="button"
|
|
285
|
-
onclick={() => removeTag(i)}
|
|
286
|
-
class="ml-0.5 rounded-sm hover:text-destructive hover:bg-destructive/10 transition-colors"
|
|
287
|
-
aria-label={t('common.clear')}
|
|
288
|
-
>×</button>
|
|
289
|
-
</Badge>
|
|
290
|
-
{/each}
|
|
291
|
-
</div>
|
|
292
|
-
<Input
|
|
293
|
-
id={field.key}
|
|
294
|
-
type="text"
|
|
295
|
-
placeholder={t('field.tagsPlaceholder')}
|
|
296
|
-
onkeydown={handleTagKeydown}
|
|
297
|
-
/>
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
{:else if field.type === 'date'}
|
|
301
|
-
<Input
|
|
302
|
-
id={field.key}
|
|
303
|
-
type="date"
|
|
304
|
-
value={strVal}
|
|
305
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
306
|
-
required={field.required}
|
|
307
|
-
/>
|
|
308
|
-
|
|
309
|
-
{:else if field.type === 'images'}
|
|
310
|
-
<div class="space-y-2">
|
|
311
|
-
{#each imagesVal as url, i}
|
|
312
|
-
<div class="flex items-center gap-2">
|
|
313
|
-
<Input
|
|
314
|
-
id={i === 0 ? field.key : undefined}
|
|
315
|
-
type="text"
|
|
316
|
-
value={url}
|
|
317
|
-
oninput={(e) => updateImage(i, (e.target as HTMLInputElement).value)}
|
|
318
|
-
placeholder="https://example.com/image.jpg"
|
|
319
|
-
class="flex-1"
|
|
320
|
-
/>
|
|
321
|
-
{#if url}
|
|
322
|
-
<img src={url} alt="preview" class="h-9 w-9 rounded object-cover border" />
|
|
323
|
-
{/if}
|
|
324
|
-
<TooltipButton tooltip={t('common.removeImage')} variant="ghost" size="icon" class="h-8 w-8 shrink-0" onclick={() => removeImage(i)}>
|
|
325
|
-
<X class="h-3.5 w-3.5" />
|
|
326
|
-
</TooltipButton>
|
|
327
|
-
</div>
|
|
328
|
-
{/each}
|
|
329
|
-
<Button variant="outline" size="sm" type="button" onclick={addImage}>
|
|
330
|
-
<Plus class="h-3.5 w-3.5 mr-1" /> {t('field.addImage')}
|
|
331
|
-
</Button>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
{:else if field.type === 'json'}
|
|
335
|
-
<Textarea
|
|
336
|
-
id={field.key}
|
|
337
|
-
value={typeof value === 'string' ? strVal : JSON.stringify(value, null, 2)}
|
|
338
|
-
oninput={(e) => {
|
|
339
|
-
try {
|
|
340
|
-
onchange(JSON.parse((e.target as HTMLTextAreaElement).value));
|
|
341
|
-
} catch {
|
|
342
|
-
// keep raw text until valid JSON
|
|
343
|
-
}
|
|
344
|
-
}}
|
|
345
|
-
rows={6}
|
|
346
|
-
class="resize-y font-mono text-xs"
|
|
347
|
-
/>
|
|
348
|
-
|
|
349
|
-
{:else}
|
|
350
|
-
<Input
|
|
351
|
-
id={field.key}
|
|
352
|
-
type="text"
|
|
353
|
-
value={strVal}
|
|
354
|
-
oninput={(e) => onchange((e.target as HTMLInputElement).value)}
|
|
355
|
-
/>
|
|
356
|
-
{/if}
|
|
357
|
-
</div>
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useForgotPassword } from '@svadmin/core';
|
|
3
|
-
import { t } from '@svadmin/core/i18n';
|
|
4
|
-
import { navigate } from '@svadmin/core/router';
|
|
5
|
-
import { Button } from './ui/button/index.js';
|
|
6
|
-
import { Input } from './ui/input/index.js';
|
|
7
|
-
import { Label } from './ui/label/index.js';
|
|
8
|
-
import * as Card from './ui/card/index.js';
|
|
9
|
-
import * as Alert from './ui/alert/index.js';
|
|
10
|
-
import { KeyRound, User, ArrowLeft, CheckCircle, Loader2, AlertCircle } from '@lucide/svelte';
|
|
11
|
-
|
|
12
|
-
let { title = 'Admin' } = $props<{
|
|
13
|
-
title?: string;
|
|
14
|
-
}>();
|
|
15
|
-
|
|
16
|
-
const forgot = useForgotPassword();
|
|
17
|
-
|
|
18
|
-
let identifier = $state('');
|
|
19
|
-
let error = $state('');
|
|
20
|
-
let sent = $state(false);
|
|
21
|
-
|
|
22
|
-
async function handleSubmit(e: Event) {
|
|
23
|
-
e.preventDefault();
|
|
24
|
-
error = '';
|
|
25
|
-
|
|
26
|
-
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
27
|
-
|
|
28
|
-
const result = await forgot.mutate({ email: identifier, username: identifier });
|
|
29
|
-
if (result.success) {
|
|
30
|
-
sent = true;
|
|
31
|
-
} else {
|
|
32
|
-
error = result.error?.message ?? t('common.operationFailed');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<div class="min-h-screen flex items-center justify-center bg-background p-4">
|
|
38
|
-
<div class="w-full max-w-[380px]">
|
|
39
|
-
<Card.Card>
|
|
40
|
-
<Card.CardHeader class="text-center pb-2">
|
|
41
|
-
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
42
|
-
{#if sent}
|
|
43
|
-
<CheckCircle class="h-5 w-5" />
|
|
44
|
-
{:else}
|
|
45
|
-
<KeyRound class="h-5 w-5" />
|
|
46
|
-
{/if}
|
|
47
|
-
</div>
|
|
48
|
-
<Card.CardTitle class="text-xl">
|
|
49
|
-
{sent ? t('auth.resetLinkSent') : t('auth.forgotPassword')}
|
|
50
|
-
</Card.CardTitle>
|
|
51
|
-
{#if !sent}
|
|
52
|
-
<p class="text-sm text-muted-foreground">{t('auth.forgotPasswordDescription')}</p>
|
|
53
|
-
{/if}
|
|
54
|
-
</Card.CardHeader>
|
|
55
|
-
<Card.CardContent>
|
|
56
|
-
{#if sent}
|
|
57
|
-
<div class="py-2">
|
|
58
|
-
<p class="text-sm text-muted-foreground text-center mb-4">
|
|
59
|
-
{t('auth.resetLinkSent')}
|
|
60
|
-
</p>
|
|
61
|
-
<Button variant="outline" class="w-full" onclick={() => navigate('/login')}>
|
|
62
|
-
<ArrowLeft class="h-4 w-4 mr-2" />
|
|
63
|
-
{t('auth.backToLogin')}
|
|
64
|
-
</Button>
|
|
65
|
-
</div>
|
|
66
|
-
{:else}
|
|
67
|
-
<form onsubmit={handleSubmit} class="space-y-4">
|
|
68
|
-
{#if error}
|
|
69
|
-
<Alert.Root variant="destructive">
|
|
70
|
-
<AlertCircle class="h-4 w-4" />
|
|
71
|
-
<Alert.Description>{error}</Alert.Description>
|
|
72
|
-
</Alert.Root>
|
|
73
|
-
{/if}
|
|
74
|
-
|
|
75
|
-
<div class="space-y-2">
|
|
76
|
-
<Label for="forgot-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
77
|
-
<div class="relative">
|
|
78
|
-
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
79
|
-
<Input
|
|
80
|
-
id="forgot-identifier"
|
|
81
|
-
type="text"
|
|
82
|
-
placeholder={t('auth.identifierPlaceholder')}
|
|
83
|
-
bind:value={identifier}
|
|
84
|
-
class="pl-9"
|
|
85
|
-
autocomplete="username"
|
|
86
|
-
/>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<Button type="submit" class="w-full" disabled={forgot.isLoading}>
|
|
91
|
-
{#if forgot.isLoading}
|
|
92
|
-
<Loader2 class="h-4 w-4 animate-spin mr-2" />
|
|
93
|
-
{/if}
|
|
94
|
-
{t('auth.sendResetLink')}
|
|
95
|
-
</Button>
|
|
96
|
-
</form>
|
|
97
|
-
|
|
98
|
-
<div class="flex items-center justify-center mt-5 pt-5 border-t">
|
|
99
|
-
<Button variant="link" class="text-sm h-auto p-0 font-medium inline-flex items-center gap-1" onclick={() => navigate('/login')}>
|
|
100
|
-
<ArrowLeft class="h-3 w-3" />
|
|
101
|
-
{t('auth.backToLogin')}
|
|
102
|
-
</Button>
|
|
103
|
-
</div>
|
|
104
|
-
{/if}
|
|
105
|
-
</Card.CardContent>
|
|
106
|
-
</Card.Card>
|
|
107
|
-
|
|
108
|
-
<p class="text-xs text-muted-foreground mt-4 text-center opacity-60">
|
|
109
|
-
Powered by {title}
|
|
110
|
-
</p>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Moon, Sun, Search, MonitorUp } from '@lucide/svelte';
|
|
3
|
-
import { Button } from './ui/button';
|
|
4
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
5
|
-
import { getResolvedTheme, toggleTheme } from '@svadmin/core';
|
|
6
|
-
import Breadcrumbs from './Breadcrumbs.svelte';
|
|
7
|
-
import { t } from '@svadmin/core/i18n';
|
|
8
|
-
import { getComponentRegistry } from '../component-registry.svelte.js';
|
|
9
|
-
|
|
10
|
-
let {
|
|
11
|
-
showThemeToggle = false,
|
|
12
|
-
showBreadcrumbs = true,
|
|
13
|
-
showSearch = true,
|
|
14
|
-
onSearchClick,
|
|
15
|
-
children,
|
|
16
|
-
/** Slot for additional actions on the right side of the header */
|
|
17
|
-
rightActions,
|
|
18
|
-
siteUrl,
|
|
19
|
-
} = $props<{
|
|
20
|
-
showThemeToggle?: boolean;
|
|
21
|
-
showBreadcrumbs?: boolean;
|
|
22
|
-
showSearch?: boolean;
|
|
23
|
-
onSearchClick?: () => void;
|
|
24
|
-
children?: import('svelte').Snippet;
|
|
25
|
-
rightActions?: import('svelte').Snippet;
|
|
26
|
-
siteUrl?: string;
|
|
27
|
-
}>();
|
|
28
|
-
|
|
29
|
-
// Retrieve optional component overrides from registry
|
|
30
|
-
const registry = getComponentRegistry();
|
|
31
|
-
const CustomBreadcrumbs = registry.Breadcrumbs;
|
|
32
|
-
const CustomThemeToggle = registry.ThemeToggle;
|
|
33
|
-
const CustomUserMenu = registry.UserMenu;
|
|
34
|
-
const CustomNotificationPanel = registry.NotificationPanel;
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<header class="sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between bg-card/60 backdrop-blur-md px-4 md:px-6">
|
|
38
|
-
<div class="flex items-center gap-4">
|
|
39
|
-
{#if children}
|
|
40
|
-
{@render children()}
|
|
41
|
-
{/if}
|
|
42
|
-
{#if showBreadcrumbs}
|
|
43
|
-
{#if CustomBreadcrumbs}
|
|
44
|
-
<CustomBreadcrumbs />
|
|
45
|
-
{:else}
|
|
46
|
-
<Breadcrumbs />
|
|
47
|
-
{/if}
|
|
48
|
-
{/if}
|
|
49
|
-
</div>
|
|
50
|
-
<div class="ml-auto flex items-center gap-2">
|
|
51
|
-
{#if rightActions}
|
|
52
|
-
{@render rightActions()}
|
|
53
|
-
{/if}
|
|
54
|
-
|
|
55
|
-
{#if siteUrl}
|
|
56
|
-
<TooltipButton tooltip={t('common.goToSite') || 'Site'} href={siteUrl} target="_blank" rel="noopener noreferrer" class="rounded-full flex items-center justify-center">
|
|
57
|
-
<MonitorUp class="h-4 w-4 text-muted-foreground transition-colors hover:text-foreground" />
|
|
58
|
-
</TooltipButton>
|
|
59
|
-
{/if}
|
|
60
|
-
|
|
61
|
-
{#if CustomNotificationPanel}
|
|
62
|
-
<CustomNotificationPanel />
|
|
63
|
-
{/if}
|
|
64
|
-
|
|
65
|
-
{#if showSearch && onSearchClick}
|
|
66
|
-
<Button variant="outline" size="sm" onclick={onSearchClick} class="gap-2 text-muted-foreground h-8 px-3">
|
|
67
|
-
<Search class="h-3.5 w-3.5" />
|
|
68
|
-
<span class="hidden sm:inline text-xs">Search...</span>
|
|
69
|
-
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium font-mono text-muted-foreground">
|
|
70
|
-
<span class="text-xs">⌘</span>K
|
|
71
|
-
</kbd>
|
|
72
|
-
</Button>
|
|
73
|
-
{/if}
|
|
74
|
-
|
|
75
|
-
{#if showThemeToggle}
|
|
76
|
-
{#if CustomThemeToggle}
|
|
77
|
-
<CustomThemeToggle />
|
|
78
|
-
{:else}
|
|
79
|
-
<TooltipButton tooltip={t('common.toggleTheme')} onclick={() => toggleTheme()} class="rounded-full">
|
|
80
|
-
{#if getResolvedTheme() === 'dark'}
|
|
81
|
-
<Moon class="h-4 w-4 transition-all" />
|
|
82
|
-
{:else}
|
|
83
|
-
<Sun class="h-4 w-4 transition-all" />
|
|
84
|
-
{/if}
|
|
85
|
-
</TooltipButton>
|
|
86
|
-
{/if}
|
|
87
|
-
{/if}
|
|
88
|
-
|
|
89
|
-
{#if CustomUserMenu}
|
|
90
|
-
<CustomUserMenu />
|
|
91
|
-
{/if}
|
|
92
|
-
</div>
|
|
93
|
-
</header>
|
|
94
|
-
|