@svadmin/ui 0.29.0 → 0.29.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/package.json +20 -9
- package/src/README.md +0 -18
- package/src/actions.ts +0 -110
- package/src/app.css +0 -519
- 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,71 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getResource } from '@svadmin/core';
|
|
3
|
-
import { navigate } from '@svadmin/core/router';
|
|
4
|
-
import { t } from '@svadmin/core/i18n';
|
|
5
|
-
import type { Snippet } from 'svelte';
|
|
6
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
7
|
-
import PageHeader from './PageHeader.svelte';
|
|
8
|
-
import AutoTable from './AutoTable.svelte';
|
|
9
|
-
import { Button } from './ui/button/index.js';
|
|
10
|
-
import { Plus } from '@lucide/svelte';
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
resourceName: string;
|
|
14
|
-
title?: string;
|
|
15
|
-
canCreate?: boolean;
|
|
16
|
-
/** Passthrough: enable row selection checkboxes */
|
|
17
|
-
selectable?: boolean;
|
|
18
|
-
/** Passthrough: custom header actions */
|
|
19
|
-
headerActions?: Snippet;
|
|
20
|
-
/** Passthrough: custom cell renderer per field */
|
|
21
|
-
cellRenderer?: Snippet<[{ field: FieldDefinition; value: unknown; record: Record<string, unknown> }]>;
|
|
22
|
-
/** Passthrough: custom row actions */
|
|
23
|
-
rowActions?: Snippet<[{ record: Record<string, unknown>; id: string | number }]>;
|
|
24
|
-
/** Passthrough: custom empty state */
|
|
25
|
-
emptyState?: Snippet;
|
|
26
|
-
/** Passthrough: expandable row content */
|
|
27
|
-
expandedRowRender?: Snippet<[{ record: Record<string, unknown> }]>;
|
|
28
|
-
class?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let {
|
|
32
|
-
resourceName,
|
|
33
|
-
title,
|
|
34
|
-
canCreate,
|
|
35
|
-
selectable,
|
|
36
|
-
headerActions,
|
|
37
|
-
cellRenderer,
|
|
38
|
-
rowActions,
|
|
39
|
-
emptyState,
|
|
40
|
-
expandedRowRender,
|
|
41
|
-
class: className = '',
|
|
42
|
-
}: Props = $props();
|
|
43
|
-
|
|
44
|
-
const resource = $derived(getResource(resourceName));
|
|
45
|
-
const pageTitle = $derived(title ?? resource.label);
|
|
46
|
-
const showCreate = $derived(canCreate ?? resource.canCreate !== false);
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
<div class="space-y-6 {className}">
|
|
50
|
-
<PageHeader title={pageTitle}>
|
|
51
|
-
{#snippet actions()}
|
|
52
|
-
{#if showCreate}
|
|
53
|
-
<Button onclick={() => navigate(`/${resourceName}/create`)}>
|
|
54
|
-
<Plus class="h-4 w-4" data-icon="inline-start" /> {t('common.create')}
|
|
55
|
-
</Button>
|
|
56
|
-
{/if}
|
|
57
|
-
{#if headerActions}
|
|
58
|
-
{@render headerActions()}
|
|
59
|
-
{/if}
|
|
60
|
-
{/snippet}
|
|
61
|
-
</PageHeader>
|
|
62
|
-
|
|
63
|
-
<AutoTable
|
|
64
|
-
{resourceName}
|
|
65
|
-
{selectable}
|
|
66
|
-
defaultCellRenderer={cellRenderer}
|
|
67
|
-
{rowActions}
|
|
68
|
-
{emptyState}
|
|
69
|
-
{expandedRowRender}
|
|
70
|
-
/>
|
|
71
|
-
</div>
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { fade } from 'svelte/transition';
|
|
3
|
-
import { Wifi, WifiOff, Loader2 } from '@lucide/svelte';
|
|
4
|
-
import { Badge } from './ui/badge/index.js';
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
status = 'disconnected',
|
|
8
|
-
resource = '',
|
|
9
|
-
lastEvent = null,
|
|
10
|
-
showDetails = false,
|
|
11
|
-
} = $props<{
|
|
12
|
-
status: 'connecting' | 'connected' | 'disconnected';
|
|
13
|
-
resource?: string;
|
|
14
|
-
lastEvent?: { type: string; timestamp?: number } | null;
|
|
15
|
-
showDetails?: boolean;
|
|
16
|
-
}>();
|
|
17
|
-
|
|
18
|
-
const statusConfigs = {
|
|
19
|
-
connected: { variant: 'default' as const, dotClass: 'bg-success', label: 'Live', icon: Wifi },
|
|
20
|
-
connecting: { variant: 'secondary' as const, dotClass: 'bg-warning', label: 'Connecting...', icon: Loader2 as typeof Wifi },
|
|
21
|
-
disconnected: { variant: 'destructive' as const, dotClass: 'bg-destructive', label: 'Offline', icon: WifiOff },
|
|
22
|
-
} as const;
|
|
23
|
-
type StatusKey = 'connected' | 'connecting' | 'disconnected';
|
|
24
|
-
const cfg = $derived(statusConfigs[status as StatusKey]);
|
|
25
|
-
|
|
26
|
-
const timeSinceEvent = $derived(
|
|
27
|
-
lastEvent?.timestamp
|
|
28
|
-
? Math.floor((Date.now() - lastEvent.timestamp) / 1000)
|
|
29
|
-
: null
|
|
30
|
-
);
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
{#key status}
|
|
34
|
-
<div transition:fade={{ duration: 200 }}>
|
|
35
|
-
<Badge variant={cfg.variant} class="gap-1.5 select-none" title={cfg.label}>
|
|
36
|
-
<span class="relative flex h-2 w-2">
|
|
37
|
-
<span class="{cfg.dotClass} rounded-full h-2 w-2"></span>
|
|
38
|
-
{#if status === 'connected'}
|
|
39
|
-
<span class="absolute inset-0 rounded-full {cfg.dotClass} opacity-40 animate-ping"></span>
|
|
40
|
-
{/if}
|
|
41
|
-
</span>
|
|
42
|
-
<span class="text-[0.6875rem] font-semibold uppercase tracking-wide">{cfg.label}</span>
|
|
43
|
-
|
|
44
|
-
{#if showDetails && resource}
|
|
45
|
-
<span class="text-[0.625rem] font-mono opacity-70">{resource}</span>
|
|
46
|
-
{/if}
|
|
47
|
-
|
|
48
|
-
{#if showDetails && lastEvent}
|
|
49
|
-
<span class="text-[0.625rem] opacity-70">
|
|
50
|
-
{lastEvent.type}
|
|
51
|
-
{#if timeSinceEvent !== null && timeSinceEvent < 60}
|
|
52
|
-
<span class="opacity-60">{timeSinceEvent}s ago</span>
|
|
53
|
-
{/if}
|
|
54
|
-
</span>
|
|
55
|
-
{/if}
|
|
56
|
-
</Badge>
|
|
57
|
-
</div>
|
|
58
|
-
{/key}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useLogin, getAuthProvider } 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 PasswordInput from './PasswordInput.svelte';
|
|
11
|
-
import { Separator } from './ui/separator/index.js';
|
|
12
|
-
import { LogIn, User, Loader2, AlertCircle } from '@lucide/svelte';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Social provider config for the login page.
|
|
16
|
-
* Each entry renders a button that calls `onClick()` when clicked.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* const socialProviders = [
|
|
21
|
-
* { name: 'Google', icon: '🔵', onClick: () => googleAuth.login({}) },
|
|
22
|
-
* { name: 'GitHub', icon: '🐙', onClick: () => githubAuth.login({}) },
|
|
23
|
-
* ];
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
interface SocialProvider {
|
|
27
|
-
/** Display name (e.g., 'Google') */
|
|
28
|
-
name: string;
|
|
29
|
-
/** Icon character, emoji, or SVG string */
|
|
30
|
-
icon?: string;
|
|
31
|
-
/** Handler to initiate social login */
|
|
32
|
-
onClick: () => void | Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let { title = 'Admin', onSuccess, socialProviders = [] } = $props<{
|
|
36
|
-
title?: string;
|
|
37
|
-
onSuccess?: () => void;
|
|
38
|
-
socialProviders?: SocialProvider[];
|
|
39
|
-
}>();
|
|
40
|
-
|
|
41
|
-
const login = useLogin();
|
|
42
|
-
const authProvider = getAuthProvider();
|
|
43
|
-
|
|
44
|
-
let identifier = $state('');
|
|
45
|
-
let password = $state('');
|
|
46
|
-
let error = $state('');
|
|
47
|
-
|
|
48
|
-
async function handleSubmit(e: Event) {
|
|
49
|
-
e.preventDefault();
|
|
50
|
-
error = '';
|
|
51
|
-
|
|
52
|
-
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
53
|
-
if (!password) { error = t('auth.passwordRequired'); return; }
|
|
54
|
-
|
|
55
|
-
const result = await login.mutate({ email: identifier, username: identifier, password });
|
|
56
|
-
if (result.success) {
|
|
57
|
-
onSuccess?.();
|
|
58
|
-
} else {
|
|
59
|
-
error = result.error?.message ?? t('common.loginFailed');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
</script>
|
|
63
|
-
|
|
64
|
-
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-100 via-blue-50/30 to-indigo-50/40 dark:from-slate-950 dark:via-slate-900 dark:to-indigo-950/20 p-4">
|
|
65
|
-
<div class="w-full max-w-[380px]">
|
|
66
|
-
<Card.Card>
|
|
67
|
-
<Card.CardHeader class="text-center pb-2">
|
|
68
|
-
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
69
|
-
<LogIn class="h-5 w-5" />
|
|
70
|
-
</div>
|
|
71
|
-
<Card.CardTitle class="text-xl">{t('auth.welcomeBack')}</Card.CardTitle>
|
|
72
|
-
<p class="text-sm text-muted-foreground">{t('auth.welcomeMessage')}</p>
|
|
73
|
-
</Card.CardHeader>
|
|
74
|
-
<Card.CardContent>
|
|
75
|
-
<form onsubmit={handleSubmit} class="space-y-4">
|
|
76
|
-
{#if error}
|
|
77
|
-
<Alert.Root variant="destructive">
|
|
78
|
-
<AlertCircle class="h-4 w-4" />
|
|
79
|
-
<Alert.Description>{error}</Alert.Description>
|
|
80
|
-
</Alert.Root>
|
|
81
|
-
{/if}
|
|
82
|
-
|
|
83
|
-
<div class="space-y-2">
|
|
84
|
-
<Label for="login-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
85
|
-
<div class="relative">
|
|
86
|
-
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
87
|
-
<Input
|
|
88
|
-
id="login-identifier"
|
|
89
|
-
type="text"
|
|
90
|
-
placeholder={t('auth.identifierPlaceholder')}
|
|
91
|
-
bind:value={identifier}
|
|
92
|
-
class="pl-9"
|
|
93
|
-
autocomplete="username"
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div>
|
|
99
|
-
<div class="flex items-center justify-between mb-2">
|
|
100
|
-
<Label for="login-password">{t('auth.password')}</Label>
|
|
101
|
-
{#if authProvider.forgotPassword}
|
|
102
|
-
<Button variant="link" class="text-xs h-auto p-0" onclick={() => navigate('/forgot-password')}>
|
|
103
|
-
{t('auth.forgotPasswordLink')}
|
|
104
|
-
</Button>
|
|
105
|
-
{/if}
|
|
106
|
-
</div>
|
|
107
|
-
<PasswordInput
|
|
108
|
-
id="login-password"
|
|
109
|
-
label=""
|
|
110
|
-
bind:value={password}
|
|
111
|
-
autocomplete="current-password"
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
<Button type="submit" class="w-full" disabled={login.isLoading}>
|
|
116
|
-
{#if login.isLoading}
|
|
117
|
-
<Loader2 class="h-4 w-4 animate-spin mr-2" />
|
|
118
|
-
{/if}
|
|
119
|
-
{t('auth.loginButton')}
|
|
120
|
-
</Button>
|
|
121
|
-
</form>
|
|
122
|
-
|
|
123
|
-
{#if socialProviders.length > 0}
|
|
124
|
-
<div class="relative my-5">
|
|
125
|
-
<Separator />
|
|
126
|
-
<span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-3 text-xs text-muted-foreground">
|
|
127
|
-
{t('auth.orContinueWith')}
|
|
128
|
-
</span>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<div class="grid gap-2" class:grid-cols-2={socialProviders.length >= 2}>
|
|
132
|
-
{#each socialProviders as provider (provider.name)}
|
|
133
|
-
<Button
|
|
134
|
-
variant="outline"
|
|
135
|
-
class="w-full"
|
|
136
|
-
onclick={provider.onClick}
|
|
137
|
-
>
|
|
138
|
-
{#if provider.icon}
|
|
139
|
-
<span class="mr-2">{provider.icon}</span>
|
|
140
|
-
{/if}
|
|
141
|
-
{provider.name}
|
|
142
|
-
</Button>
|
|
143
|
-
{/each}
|
|
144
|
-
</div>
|
|
145
|
-
{/if}
|
|
146
|
-
|
|
147
|
-
{#if authProvider.register}
|
|
148
|
-
<div class="flex items-center justify-center gap-1 mt-5 pt-5 border-t">
|
|
149
|
-
<span class="text-sm text-muted-foreground">{t('auth.noAccount')}</span>
|
|
150
|
-
<Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/register')}>
|
|
151
|
-
{t('auth.register')}
|
|
152
|
-
</Button>
|
|
153
|
-
</div>
|
|
154
|
-
{/if}
|
|
155
|
-
</Card.CardContent>
|
|
156
|
-
</Card.Card>
|
|
157
|
-
|
|
158
|
-
<p class="text-xs text-muted-foreground mt-4 text-center">
|
|
159
|
-
Powered by {title}
|
|
160
|
-
</p>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as markedPkg from "marked";
|
|
3
|
-
import * as markedHighlightPkg from "marked-highlight";
|
|
4
|
-
import * as hljsPkg from "highlight.js";
|
|
5
|
-
import * as DOMPurifyPkg from "isomorphic-dompurify";
|
|
6
|
-
import { Check, Copy } from '@lucide/svelte';
|
|
7
|
-
import { Button } from "./ui/button/index.js";
|
|
8
|
-
|
|
9
|
-
const Marked = markedPkg.Marked;
|
|
10
|
-
const markedHighlight = markedHighlightPkg.markedHighlight;
|
|
11
|
-
const hljsModule = hljsPkg.default || hljsPkg;
|
|
12
|
-
const DOMPurify = 'default' in DOMPurifyPkg ? DOMPurifyPkg.default : DOMPurifyPkg;
|
|
13
|
-
|
|
14
|
-
// Only safely import css if hljs exists
|
|
15
|
-
if (hljsModule && Object.keys(hljsModule).length > 0) {
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
import("highlight.js/styles/github-dark.css").catch(() => {});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface Props {
|
|
21
|
-
/** The raw markdown string to render */
|
|
22
|
-
content: string;
|
|
23
|
-
/** Whether to enable streaming/typing effect styling */
|
|
24
|
-
streaming?: boolean;
|
|
25
|
-
/** Custom class for the container */
|
|
26
|
-
class?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let { content, streaming = false, class: className = "" }: Props = $props();
|
|
30
|
-
|
|
31
|
-
const hasMarkdownDeps = !!(Marked && Object.keys(DOMPurify || {}).length > 0);
|
|
32
|
-
|
|
33
|
-
// Configure marked with syntax highlighting if available
|
|
34
|
-
const markedObj = hasMarkdownDeps ? new Marked(
|
|
35
|
-
markedHighlight({
|
|
36
|
-
langPrefix: "hljs language-",
|
|
37
|
-
highlight(code: string, lang: string) {
|
|
38
|
-
const language = hljsModule.getLanguage && hljsModule.getLanguage(lang) ? lang : "plaintext";
|
|
39
|
-
return hljsModule.highlight ? hljsModule.highlight(code, { language }).value : code;
|
|
40
|
-
},
|
|
41
|
-
}),
|
|
42
|
-
) : null;
|
|
43
|
-
|
|
44
|
-
// Render HTML safely
|
|
45
|
-
const html = $derived(
|
|
46
|
-
hasMarkdownDeps
|
|
47
|
-
? DOMPurify.sanitize(markedObj!.parse(content || "") as string)
|
|
48
|
-
: `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "<").replace(/>/g, ">")}</div>`
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
// Handle copy code blocks
|
|
52
|
-
let copiedBlock = $state<string | null>(null);
|
|
53
|
-
|
|
54
|
-
function handleCopy(event: MouseEvent) {
|
|
55
|
-
const target = event.target as HTMLElement;
|
|
56
|
-
const button = target.closest(".copy-btn");
|
|
57
|
-
if (!button) return;
|
|
58
|
-
|
|
59
|
-
const pre = button.closest(".code-block-wrapper")?.querySelector("pre");
|
|
60
|
-
if (!pre) return;
|
|
61
|
-
|
|
62
|
-
const code = pre.textContent || "";
|
|
63
|
-
navigator.clipboard.writeText(code);
|
|
64
|
-
|
|
65
|
-
const id = Math.random().toString(36);
|
|
66
|
-
copiedBlock = id;
|
|
67
|
-
button.setAttribute("data-copied-id", id);
|
|
68
|
-
|
|
69
|
-
setTimeout(() => {
|
|
70
|
-
if (copiedBlock === id) copiedBlock = null;
|
|
71
|
-
}, 2000);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Inject copy buttons into the HTML after rendering
|
|
75
|
-
// We do this via action instead of raw string manipulation for safety
|
|
76
|
-
function enhanceCodeBlocks(node: HTMLElement, _: string) {
|
|
77
|
-
return {
|
|
78
|
-
update(newHtml: string) {
|
|
79
|
-
// Find all pre > code blocks that don't have wrappers yet
|
|
80
|
-
const pres = node.querySelectorAll("pre:not(.enhanced)");
|
|
81
|
-
pres.forEach((pre) => {
|
|
82
|
-
pre.classList.add("enhanced");
|
|
83
|
-
const wrapper = document.createElement("div");
|
|
84
|
-
wrapper.className =
|
|
85
|
-
"code-block-wrapper group relative my-4 rounded-md bg-foreground/95";
|
|
86
|
-
|
|
87
|
-
const header = document.createElement("div");
|
|
88
|
-
header.className =
|
|
89
|
-
"flex items-center justify-between px-4 py-2 text-xs text-zinc-400 border-b border-zinc-800";
|
|
90
|
-
|
|
91
|
-
const lang =
|
|
92
|
-
Array.from(pre.querySelector("code")?.classList || [])
|
|
93
|
-
.find((c) => c.startsWith("language-"))
|
|
94
|
-
?.replace("language-", "") || "Code";
|
|
95
|
-
header.innerHTML = `
|
|
96
|
-
<span>${lang}</span>
|
|
97
|
-
<button class="copy-btn hover:text-white transition-colors flex items-center gap-1">
|
|
98
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
99
|
-
</button>
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
pre.replaceWith(wrapper);
|
|
103
|
-
wrapper.appendChild(header);
|
|
104
|
-
|
|
105
|
-
const scrollContainer = document.createElement("div");
|
|
106
|
-
scrollContainer.className = "overflow-x-auto p-4";
|
|
107
|
-
scrollContainer.appendChild(pre);
|
|
108
|
-
wrapper.appendChild(scrollContainer);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Add event listener for copy buttons
|
|
112
|
-
node.addEventListener("click", handleCopy);
|
|
113
|
-
},
|
|
114
|
-
destroy() {
|
|
115
|
-
node.removeEventListener("click", handleCopy);
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
</script>
|
|
120
|
-
|
|
121
|
-
<div
|
|
122
|
-
class="prose prose-sm dark:prose-invert max-w-none wrap-break-word {className}"
|
|
123
|
-
class:streaming={streaming}
|
|
124
|
-
use:enhanceCodeBlocks={html}
|
|
125
|
-
>
|
|
126
|
-
{@html html}
|
|
127
|
-
</div>
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
|
-
import { getResource, useForm } from '@svadmin/core';
|
|
5
|
-
import { t } from '@svadmin/core/i18n';
|
|
6
|
-
import * as Dialog from './ui/dialog/index.js';
|
|
7
|
-
import AutoForm from './AutoForm.svelte';
|
|
8
|
-
|
|
9
|
-
let { resourceName, mode = 'create', id, open = $bindable(false), onSuccess } = $props<{
|
|
10
|
-
resourceName: string;
|
|
11
|
-
mode?: 'create' | 'edit';
|
|
12
|
-
id?: string | number;
|
|
13
|
-
open: boolean;
|
|
14
|
-
onSuccess?: () => void;
|
|
15
|
-
}>();
|
|
16
|
-
|
|
17
|
-
const resource = $derived(getResource(resourceName));
|
|
18
|
-
const fieldCount = $derived(resource.fields.filter(f => f.showInForm !== false).length);
|
|
19
|
-
const dialogWidth = $derived(fieldCount > 8 ? 'sm:max-w-3xl' : fieldCount > 5 ? 'sm:max-w-2xl' : 'sm:max-w-lg');
|
|
20
|
-
|
|
21
|
-
function handleClose() {
|
|
22
|
-
open = false;
|
|
23
|
-
onSuccess?.();
|
|
24
|
-
}
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
{#if open}
|
|
28
|
-
<Dialog.Dialog bind:open>
|
|
29
|
-
<Dialog.DialogContent class="{dialogWidth} max-h-[85vh] overflow-y-auto">
|
|
30
|
-
<Dialog.DialogHeader>
|
|
31
|
-
<Dialog.DialogTitle>
|
|
32
|
-
{mode === 'create' ? `${t('common.create')}${resource.label}` : `${t('common.edit')}${resource.label}`}
|
|
33
|
-
</Dialog.DialogTitle>
|
|
34
|
-
</Dialog.DialogHeader>
|
|
35
|
-
<AutoForm {resourceName} {mode} {id} />
|
|
36
|
-
</Dialog.DialogContent>
|
|
37
|
-
</Dialog.Dialog>
|
|
38
|
-
{/if}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useMenu } from '@svadmin/core';
|
|
3
|
-
import { navigate } from '@svadmin/core/router';
|
|
4
|
-
|
|
5
|
-
const { menuItems } = useMenu();
|
|
6
|
-
|
|
7
|
-
$effect(() => {
|
|
8
|
-
// Automatically navigate to the first defined resource
|
|
9
|
-
if (menuItems.length > 0) {
|
|
10
|
-
navigate(menuItems[0].route);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<div class="flex h-screen w-screen items-center justify-center p-4 text-muted-foreground">
|
|
16
|
-
<div class="text-center">
|
|
17
|
-
<div class="mb-4 inline-block h-8 w-8 animate-spin rounded-full border-4 border-primary border-r-transparent"></div>
|
|
18
|
-
<p>Navigating to default resource...</p>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import Breadcrumbs from './Breadcrumbs.svelte';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
title: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
actions?: Snippet;
|
|
9
|
-
showBreadcrumbs?: boolean;
|
|
10
|
-
class?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let {
|
|
14
|
-
title,
|
|
15
|
-
description,
|
|
16
|
-
actions,
|
|
17
|
-
showBreadcrumbs = true,
|
|
18
|
-
class: className = '',
|
|
19
|
-
}: Props = $props();
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<div class="space-y-4 {className}">
|
|
23
|
-
{#if showBreadcrumbs}
|
|
24
|
-
<Breadcrumbs />
|
|
25
|
-
{/if}
|
|
26
|
-
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
27
|
-
<div>
|
|
28
|
-
<h1 class="text-xl font-semibold tracking-tight text-foreground">{title}</h1>
|
|
29
|
-
{#if description}
|
|
30
|
-
<p class="text-sm text-muted-foreground">{description}</p>
|
|
31
|
-
{/if}
|
|
32
|
-
</div>
|
|
33
|
-
{#if actions}
|
|
34
|
-
<div class="flex items-center gap-2">
|
|
35
|
-
{@render actions()}
|
|
36
|
-
</div>
|
|
37
|
-
{/if}
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { fade } from 'svelte/transition';
|
|
3
|
-
import { Skeleton } from './ui/skeleton';
|
|
4
|
-
|
|
5
|
-
let {
|
|
6
|
-
type = 'list',
|
|
7
|
-
rows = 5,
|
|
8
|
-
} = $props<{
|
|
9
|
-
type?: 'list' | 'show' | 'form';
|
|
10
|
-
rows?: number;
|
|
11
|
-
}>();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<div class="w-full space-y-6" out:fade={{ duration: 200 }}>
|
|
15
|
-
<!-- Header Skeleton -->
|
|
16
|
-
<div class="flex items-center justify-between">
|
|
17
|
-
<div class="space-y-2">
|
|
18
|
-
<Skeleton class="h-8 w-[200px]" />
|
|
19
|
-
<Skeleton class="h-4 w-[300px]" />
|
|
20
|
-
</div>
|
|
21
|
-
{#if type === 'list'}
|
|
22
|
-
<Skeleton class="h-10 w-[120px]" />
|
|
23
|
-
{/if}
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div class="rounded-md border p-4 space-y-4">
|
|
27
|
-
{#if type === 'list'}
|
|
28
|
-
<!-- Table Header -->
|
|
29
|
-
<div class="flex gap-4 border-b pb-4">
|
|
30
|
-
{#each Array(4) as _}
|
|
31
|
-
<Skeleton class="h-6 flex-1" />
|
|
32
|
-
{/each}
|
|
33
|
-
</div>
|
|
34
|
-
<!-- Table Rows -->
|
|
35
|
-
<div class="space-y-4">
|
|
36
|
-
{#each Array(rows) as _}
|
|
37
|
-
<div class="flex gap-4">
|
|
38
|
-
<Skeleton class="h-6 flex-1" />
|
|
39
|
-
<Skeleton class="h-6 flex-1" />
|
|
40
|
-
<Skeleton class="h-6 flex-1" />
|
|
41
|
-
<Skeleton class="h-6 flex-1" />
|
|
42
|
-
</div>
|
|
43
|
-
{/each}
|
|
44
|
-
</div>
|
|
45
|
-
{:else if type === 'show'}
|
|
46
|
-
<!-- Key-Value pairs -->
|
|
47
|
-
<div class="space-y-6">
|
|
48
|
-
{#each Array(rows) as _}
|
|
49
|
-
<div class="space-y-2">
|
|
50
|
-
<Skeleton class="h-4 w-[100px]" />
|
|
51
|
-
<Skeleton class="h-6 w-[250px]" />
|
|
52
|
-
</div>
|
|
53
|
-
{/each}
|
|
54
|
-
</div>
|
|
55
|
-
{:else if type === 'form'}
|
|
56
|
-
<!-- Form Inputs -->
|
|
57
|
-
<div class="space-y-6">
|
|
58
|
-
{#each Array(rows) as _}
|
|
59
|
-
<div class="space-y-2">
|
|
60
|
-
<Skeleton class="h-4 w-[100px]" />
|
|
61
|
-
<Skeleton class="h-10 w-full max-w-md" />
|
|
62
|
-
</div>
|
|
63
|
-
{/each}
|
|
64
|
-
<div class="pt-4 flex gap-2">
|
|
65
|
-
<Skeleton class="h-10 w-[100px]" />
|
|
66
|
-
<Skeleton class="h-10 w-[100px]" />
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
{/if}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Input } from './ui/input/index.js';
|
|
3
|
-
import { Button } from './ui/button/index.js';
|
|
4
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
5
|
-
import { Label } from './ui/label/index.js';
|
|
6
|
-
import { Progress } from './ui/progress/index.js';
|
|
7
|
-
import { Lock, Eye, EyeOff } from '@lucide/svelte';
|
|
8
|
-
import { t } from '@svadmin/core/i18n';
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
id: string;
|
|
12
|
-
label: string;
|
|
13
|
-
value: string;
|
|
14
|
-
placeholder?: string;
|
|
15
|
-
autocomplete?: string;
|
|
16
|
-
showStrength?: boolean;
|
|
17
|
-
class?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let {
|
|
21
|
-
id,
|
|
22
|
-
label,
|
|
23
|
-
value = $bindable(''),
|
|
24
|
-
placeholder = '••••••••',
|
|
25
|
-
autocomplete = 'current-password',
|
|
26
|
-
showStrength = false,
|
|
27
|
-
class: className = '',
|
|
28
|
-
}: Props = $props();
|
|
29
|
-
|
|
30
|
-
let showPassword = $state(false);
|
|
31
|
-
|
|
32
|
-
// Password strength calculation
|
|
33
|
-
const strength = $derived.by(() => {
|
|
34
|
-
if (!showStrength || !value) return 0;
|
|
35
|
-
let score = 0;
|
|
36
|
-
if (value.length >= 8) score += 25;
|
|
37
|
-
if (value.length >= 12) score += 10;
|
|
38
|
-
if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score += 25;
|
|
39
|
-
if (/\d/.test(value)) score += 20;
|
|
40
|
-
if (/[^a-zA-Z0-9]/.test(value)) score += 20;
|
|
41
|
-
return Math.min(score, 100);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const strengthColor = $derived(
|
|
45
|
-
strength < 30 ? 'bg-destructive' :
|
|
46
|
-
strength < 60 ? 'bg-warning' :
|
|
47
|
-
strength < 80 ? 'bg-info' : 'bg-success'
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const strengthLabel = $derived(
|
|
51
|
-
strength < 30 ? 'Weak' :
|
|
52
|
-
strength < 60 ? 'Fair' :
|
|
53
|
-
strength < 80 ? 'Good' : 'Strong'
|
|
54
|
-
);
|
|
55
|
-
</script>
|
|
56
|
-
|
|
57
|
-
<div class="space-y-2 {className}">
|
|
58
|
-
<Label for={id}>{label}</Label>
|
|
59
|
-
<div class="relative">
|
|
60
|
-
<Lock class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
61
|
-
<Input
|
|
62
|
-
{id}
|
|
63
|
-
type={showPassword ? 'text' : 'password'}
|
|
64
|
-
{placeholder}
|
|
65
|
-
bind:value
|
|
66
|
-
class="pl-9 pr-9"
|
|
67
|
-
autocomplete={autocomplete as any}
|
|
68
|
-
/>
|
|
69
|
-
<TooltipButton
|
|
70
|
-
tooltip={showPassword ? t('common.hidePassword') : t('common.showPassword')}
|
|
71
|
-
variant="ghost"
|
|
72
|
-
size="icon"
|
|
73
|
-
type="button"
|
|
74
|
-
class="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 z-[1]"
|
|
75
|
-
onclick={(e) => { e.preventDefault(); showPassword = !showPassword; }}
|
|
76
|
-
tabindex={-1}
|
|
77
|
-
>
|
|
78
|
-
{#if showPassword}
|
|
79
|
-
<EyeOff class="h-4 w-4" />
|
|
80
|
-
{:else}
|
|
81
|
-
<Eye class="h-4 w-4" />
|
|
82
|
-
{/if}
|
|
83
|
-
</TooltipButton>
|
|
84
|
-
</div>
|
|
85
|
-
{#if showStrength && value}
|
|
86
|
-
<div class="flex items-center gap-2">
|
|
87
|
-
<div class="flex-1 h-1.5 rounded-full bg-muted overflow-hidden">
|
|
88
|
-
<div
|
|
89
|
-
class="h-full rounded-full transition-all duration-300 {strengthColor}"
|
|
90
|
-
style="width: {strength}%"
|
|
91
|
-
></div>
|
|
92
|
-
</div>
|
|
93
|
-
<span class="text-xs text-muted-foreground w-12 text-right">{strengthLabel}</span>
|
|
94
|
-
</div>
|
|
95
|
-
{/if}
|
|
96
|
-
</div>
|