@svadmin/ui 0.18.0 → 0.20.0
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 +24 -2
- package/src/components/AuditLogViewer.svelte +226 -0
- package/src/components/AutoTable.svelte +11 -5
- package/src/components/CanAccess.svelte +1 -1
- package/src/components/ChatDialog.svelte +104 -20
- package/src/components/CommandPalette.svelte +47 -5
- package/src/components/DraggableGrid.svelte +90 -0
- package/src/components/FieldRenderer.svelte +36 -2
- package/src/components/MarkdownRenderer.svelte +0 -4
- package/src/components/PermissionMatrix.svelte +190 -0
- package/src/components/RolesSettings.svelte +144 -0
- package/src/components/SettingsPage.svelte +12 -2
- package/src/components/TaskQueueDrawer.svelte +170 -0
- package/src/components/TenantSwitcher.svelte +90 -0
- package/src/components/buttons/CloneButton.svelte +3 -3
- package/src/components/buttons/CreateButton.svelte +3 -3
- package/src/components/buttons/DeleteButton.svelte +3 -3
- package/src/components/buttons/EditButton.svelte +3 -3
- package/src/components/buttons/ExportButton.svelte +3 -3
- package/src/components/buttons/ImportButton.svelte +3 -3
- package/src/components/buttons/ShowButton.svelte +3 -3
- package/src/components/fields/ArrayField.svelte +86 -0
- package/src/index.ts +14 -0
- package/src/markdown-deps.d.ts +21 -0
- package/src/components/stories/BarChart.stories.ts +0 -43
- package/src/components/stories/LineChart.stories.ts +0 -48
- package/src/components/stories/PieChart.stories.ts +0 -41
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { ChevronsUpDown, Building2, Check } from 'lucide-svelte';
|
|
4
|
+
import { Button } from './ui/button/index.js';
|
|
5
|
+
import * as DropdownMenu from './ui/dropdown-menu/index.js';
|
|
6
|
+
|
|
7
|
+
export interface Tenant {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
logo?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
tenants = [],
|
|
16
|
+
currentTenantId = $bindable(''),
|
|
17
|
+
collapsed = false,
|
|
18
|
+
onSwitch,
|
|
19
|
+
headerSnippet,
|
|
20
|
+
}: {
|
|
21
|
+
tenants: Tenant[];
|
|
22
|
+
currentTenantId?: string;
|
|
23
|
+
collapsed?: boolean;
|
|
24
|
+
onSwitch?: (tenantId: string) => void;
|
|
25
|
+
headerSnippet?: Snippet;
|
|
26
|
+
} = $props();
|
|
27
|
+
|
|
28
|
+
let currentTenant = $derived(tenants.find(t => t.id === currentTenantId) ?? tenants[0]);
|
|
29
|
+
|
|
30
|
+
function selectTenant(id: string) {
|
|
31
|
+
currentTenantId = id;
|
|
32
|
+
onSwitch?.(id);
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
{#if tenants.length > 0}
|
|
37
|
+
<DropdownMenu.Root>
|
|
38
|
+
<DropdownMenu.Trigger>
|
|
39
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
40
|
+
<button
|
|
41
|
+
{...props}
|
|
42
|
+
class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-sidebar-accent/50
|
|
43
|
+
{collapsed ? 'justify-center' : ''}"
|
|
44
|
+
>
|
|
45
|
+
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary text-sm font-bold">
|
|
46
|
+
{#if currentTenant?.logo}
|
|
47
|
+
<img src={currentTenant.logo} alt={currentTenant.name} class="h-full w-full rounded-lg object-cover" />
|
|
48
|
+
{:else}
|
|
49
|
+
<Building2 class="h-4 w-4" />
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
{#if !collapsed}
|
|
53
|
+
<div class="flex-1 min-w-0">
|
|
54
|
+
<p class="truncate text-sm font-semibold text-sidebar-foreground">{currentTenant?.name ?? 'Select'}</p>
|
|
55
|
+
<p class="truncate text-[10px] text-sidebar-foreground/50">Workspace</p>
|
|
56
|
+
</div>
|
|
57
|
+
<ChevronsUpDown class="h-4 w-4 shrink-0 text-sidebar-foreground/40" />
|
|
58
|
+
{/if}
|
|
59
|
+
</button>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</DropdownMenu.Trigger>
|
|
62
|
+
<DropdownMenu.Content class="w-56" align="start" side="right" sideOffset={8}>
|
|
63
|
+
{#if headerSnippet}
|
|
64
|
+
<div class="px-2 py-1.5">
|
|
65
|
+
{@render headerSnippet()}
|
|
66
|
+
</div>
|
|
67
|
+
<DropdownMenu.Separator />
|
|
68
|
+
{/if}
|
|
69
|
+
<DropdownMenu.Label class="text-xs text-muted-foreground">Workspaces</DropdownMenu.Label>
|
|
70
|
+
{#each tenants as tenant}
|
|
71
|
+
<DropdownMenu.Item
|
|
72
|
+
class="flex items-center gap-3 cursor-pointer"
|
|
73
|
+
onclick={() => selectTenant(tenant.id)}
|
|
74
|
+
>
|
|
75
|
+
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-primary/10 text-primary text-xs font-bold">
|
|
76
|
+
{#if tenant.logo}
|
|
77
|
+
<img src={tenant.logo} alt={tenant.name} class="h-full w-full rounded object-cover" />
|
|
78
|
+
{:else}
|
|
79
|
+
{tenant.name.charAt(0).toUpperCase()}
|
|
80
|
+
{/if}
|
|
81
|
+
</div>
|
|
82
|
+
<span class="flex-1 truncate">{tenant.name}</span>
|
|
83
|
+
{#if tenant.id === currentTenantId}
|
|
84
|
+
<Check class="h-4 w-4 text-primary" />
|
|
85
|
+
{/if}
|
|
86
|
+
</DropdownMenu.Item>
|
|
87
|
+
{/each}
|
|
88
|
+
</DropdownMenu.Content>
|
|
89
|
+
</DropdownMenu.Root>
|
|
90
|
+
{/if}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
}>();
|
|
13
13
|
|
|
14
14
|
const nav = useNavigation();
|
|
15
|
-
const can =
|
|
16
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
15
|
+
const can = useCan(() => ({ resource, action: 'create', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
16
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
{#if !hidden}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
variant="outline"
|
|
22
22
|
size={hideText ? 'icon' : 'sm'}
|
|
23
23
|
class={className}
|
|
24
|
-
disabled={
|
|
24
|
+
disabled={!can.allowed}
|
|
25
25
|
onclick={() => nav.clone(resource, recordItemId)}
|
|
26
26
|
>
|
|
27
27
|
<Copy class="h-4 w-4" />
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
}>();
|
|
12
12
|
|
|
13
13
|
const nav = useNavigation();
|
|
14
|
-
const can =
|
|
15
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
14
|
+
const can = useCan(() => ({ resource, action: 'create', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
15
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
{#if !hidden}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
variant="default"
|
|
21
21
|
size={hideText ? 'icon' : 'default'}
|
|
22
22
|
class={className}
|
|
23
|
-
disabled={
|
|
23
|
+
disabled={!can.allowed}
|
|
24
24
|
onclick={() => nav.create(resource)}
|
|
25
25
|
>
|
|
26
26
|
<Plus class="h-4 w-4" />
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
}>();
|
|
18
18
|
|
|
19
19
|
const deleteMut = useDelete({ get resource() { return resource; }, get mutationMode() { return undoable ? 'undoable' as const : 'pessimistic' as const; } });
|
|
20
|
-
const can =
|
|
21
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
20
|
+
const can = useCan(() => ({ resource, action: 'delete', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
21
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
22
22
|
let confirming = $state(false);
|
|
23
23
|
|
|
24
24
|
async function handleDelete() {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
variant="ghost"
|
|
50
50
|
size={hideText ? 'icon' : 'sm'}
|
|
51
51
|
class="text-destructive hover:text-destructive {className}"
|
|
52
|
-
disabled={
|
|
52
|
+
disabled={!can.allowed}
|
|
53
53
|
onclick={handleDelete}
|
|
54
54
|
>
|
|
55
55
|
<Trash2 class="h-4 w-4" />
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
}>();
|
|
13
13
|
|
|
14
14
|
const nav = useNavigation();
|
|
15
|
-
const can =
|
|
16
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
15
|
+
const can = useCan(() => ({ resource, action: 'edit', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
16
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
{#if !hidden}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
variant="outline"
|
|
22
22
|
size={hideText ? 'icon' : 'sm'}
|
|
23
23
|
class={className}
|
|
24
|
-
disabled={
|
|
24
|
+
disabled={!can.allowed}
|
|
25
25
|
onclick={() => nav.edit(resource, recordItemId)}
|
|
26
26
|
>
|
|
27
27
|
<Pencil class="h-4 w-4" />
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
}>();
|
|
12
12
|
|
|
13
13
|
const { triggerExport, isLoading } = useExport({ get resource() { return resource; } });
|
|
14
|
-
const can =
|
|
15
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
14
|
+
const can = useCan(() => ({ resource, action: 'export', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
15
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
{#if !hidden}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
variant="outline"
|
|
22
22
|
size={hideText ? 'icon' : 'sm'}
|
|
23
23
|
class={className}
|
|
24
|
-
disabled={isLoading ||
|
|
24
|
+
disabled={isLoading || !can.allowed}
|
|
25
25
|
onclick={triggerExport}
|
|
26
26
|
>
|
|
27
27
|
<Download class="h-4 w-4" />
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const can =
|
|
35
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
34
|
+
const can = useCan(() => ({ resource, action: 'import', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
35
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
38
|
{#if !hidden}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
variant="outline"
|
|
48
48
|
size={hideText ? 'icon' : 'sm'}
|
|
49
49
|
class={className}
|
|
50
|
-
disabled={importHook.isLoading ||
|
|
50
|
+
disabled={importHook.isLoading || !can.allowed}
|
|
51
51
|
onclick={triggerImport}
|
|
52
52
|
>
|
|
53
53
|
<Upload class="h-4 w-4" />
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
}>();
|
|
13
13
|
|
|
14
14
|
const nav = useNavigation();
|
|
15
|
-
const can =
|
|
16
|
-
const hidden = $derived(accessControl?.hideIfUnauthorized &&
|
|
15
|
+
const can = useCan(() => ({ resource, action: 'show', queryOptions: { enabled: accessControl?.enabled ?? true } }));
|
|
16
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
{#if !hidden}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
variant="ghost"
|
|
23
23
|
size={hideText ? 'icon' : 'sm'}
|
|
24
24
|
class={className}
|
|
25
|
-
disabled={
|
|
25
|
+
disabled={!can.allowed}
|
|
26
26
|
onclick={() => nav.show(resource, recordItemId)}
|
|
27
27
|
>
|
|
28
28
|
<Eye class="h-4 w-4" />
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
import { t } from '@svadmin/core/i18n';
|
|
4
|
+
import { Button } from '../ui/button/index.js';
|
|
5
|
+
import { Plus, Trash2 } from 'lucide-svelte';
|
|
6
|
+
import TooltipButton from '../TooltipButton.svelte';
|
|
7
|
+
import FieldRenderer from '../FieldRenderer.svelte';
|
|
8
|
+
|
|
9
|
+
let { field, value, onchange } = $props<{
|
|
10
|
+
field: FieldDefinition;
|
|
11
|
+
value: unknown;
|
|
12
|
+
onchange: (val: unknown) => void;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const arrayVal = $derived(Array.isArray(value) ? value : []);
|
|
16
|
+
const subFields = $derived(field.subFields || []);
|
|
17
|
+
|
|
18
|
+
function getDefaultForType(type: string): unknown {
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'text': case 'textarea': case 'richtext': case 'image': return '';
|
|
21
|
+
case 'number': return 0;
|
|
22
|
+
case 'boolean': return false;
|
|
23
|
+
case 'tags': case 'images': case 'multiselect': case 'array': return [];
|
|
24
|
+
case 'json': return {};
|
|
25
|
+
default: return '';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleAdd() {
|
|
30
|
+
const newItem: Record<string, unknown> = {};
|
|
31
|
+
for (const f of subFields) {
|
|
32
|
+
newItem[f.key] = f.defaultValue ?? getDefaultForType(f.type);
|
|
33
|
+
}
|
|
34
|
+
onchange([...arrayVal, newItem]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function handleRemove(index: number) {
|
|
38
|
+
onchange(arrayVal.filter((_, i) => i !== index));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleChange(index: number, key: string, val: unknown) {
|
|
42
|
+
const next = [...arrayVal];
|
|
43
|
+
next[index] = { ...next[index], [key]: val };
|
|
44
|
+
onchange(next);
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<div class="space-y-4 rounded-lg border border-dashed p-4">
|
|
49
|
+
<div class="flex items-center justify-between">
|
|
50
|
+
<div class="text-sm font-medium text-foreground">
|
|
51
|
+
{field.label}
|
|
52
|
+
{#if field.required}
|
|
53
|
+
<span class="text-destructive">*</span>
|
|
54
|
+
{/if}
|
|
55
|
+
</div>
|
|
56
|
+
<Button variant="outline" size="sm" type="button" onclick={handleAdd}>
|
|
57
|
+
<Plus class="h-3.5 w-3.5 mr-1" /> {t('common.add') || 'Add'}
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="space-y-4 pt-2">
|
|
62
|
+
{#each arrayVal as item, i}
|
|
63
|
+
<div class="relative rounded-md border bg-card p-4 pt-6 shadow-sm">
|
|
64
|
+
<div class="absolute right-2 top-2">
|
|
65
|
+
<TooltipButton tooltip={t('common.remove') || 'Remove'} variant="ghost" size="icon" class="h-8 w-8 text-muted-foreground hover:text-destructive transition-colors" onclick={() => handleRemove(i)}>
|
|
66
|
+
<Trash2 class="h-4 w-4" />
|
|
67
|
+
</TooltipButton>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="grid gap-5">
|
|
70
|
+
{#each subFields as subField (subField.key)}
|
|
71
|
+
<FieldRenderer
|
|
72
|
+
field={subField}
|
|
73
|
+
value={item[subField.key]}
|
|
74
|
+
onchange={(val) => handleChange(i, subField.key, val)}
|
|
75
|
+
/>
|
|
76
|
+
{/each}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
{/each}
|
|
80
|
+
{#if arrayVal.length === 0}
|
|
81
|
+
<div class="flex flex-col items-center justify-center py-6 text-sm text-muted-foreground bg-muted/10 rounded-md">
|
|
82
|
+
<span>{t('common.noData') || 'No items added yet.'}</span>
|
|
83
|
+
</div>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { default as LoginPage } from './components/LoginPage.svelte';
|
|
|
22
22
|
export { default as RegisterPage } from './components/RegisterPage.svelte';
|
|
23
23
|
export { default as ForgotPasswordPage } from './components/ForgotPasswordPage.svelte';
|
|
24
24
|
export { default as CanAccess } from './components/CanAccess.svelte';
|
|
25
|
+
export { default as Can } from './components/CanAccess.svelte';
|
|
25
26
|
export { default as UndoableNotification } from './components/UndoableNotification.svelte';
|
|
26
27
|
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
27
28
|
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
@@ -62,6 +63,16 @@ export { default as DraggableHeader } from "./components/DraggableHeader.svelte"
|
|
|
62
63
|
export { default as SettingsPage } from "./components/SettingsPage.svelte";
|
|
63
64
|
export { default as AppearanceSettings } from "./components/AppearanceSettings.svelte";
|
|
64
65
|
export { default as AboutSettings } from "./components/AboutSettings.svelte";
|
|
66
|
+
export { default as PermissionMatrix } from "./components/PermissionMatrix.svelte";
|
|
67
|
+
export type { RoleInfo, ResourceInfo, ActionInfo } from "./components/PermissionMatrix.svelte";
|
|
68
|
+
export { default as RolesSettings } from "./components/RolesSettings.svelte";
|
|
69
|
+
export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
|
|
70
|
+
export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
|
|
71
|
+
export type { Tenant } from "./components/TenantSwitcher.svelte";
|
|
72
|
+
export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
|
|
73
|
+
export type { BackgroundTask, TaskStatus } from "./components/TaskQueueDrawer.svelte";
|
|
74
|
+
export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
|
|
75
|
+
export type { GridModule } from "./components/DraggableGrid.svelte";
|
|
65
76
|
|
|
66
77
|
// Dashboard Charts (zero-dependency SVG)
|
|
67
78
|
export { BarChart, LineChart, PieChart } from './components/charts/index.js';
|
|
@@ -82,6 +93,9 @@ export { default as RelationField } from './components/fields/RelationField.svel
|
|
|
82
93
|
export { default as JsonField } from './components/fields/JsonField.svelte';
|
|
83
94
|
export { default as RichTextField } from './components/fields/RichTextField.svelte';
|
|
84
95
|
|
|
96
|
+
// Rich text editor (requires @svadmin/editor)
|
|
97
|
+
export { Editor as RichTextEditor } from '@svadmin/editor';
|
|
98
|
+
|
|
85
99
|
// CRUD Buttons
|
|
86
100
|
export {
|
|
87
101
|
CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Type declarations for MarkdownRenderer dependencies
|
|
2
|
+
declare module 'marked' {
|
|
3
|
+
export class Marked {
|
|
4
|
+
constructor(...extensions: unknown[]);
|
|
5
|
+
parse(src: string): string;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module 'marked-highlight' {
|
|
10
|
+
export function markedHighlight(options: {
|
|
11
|
+
langPrefix?: string;
|
|
12
|
+
highlight: (code: string, lang: string) => string;
|
|
13
|
+
}): unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module 'isomorphic-dompurify' {
|
|
17
|
+
const DOMPurify: {
|
|
18
|
+
sanitize(dirty: string, config?: Record<string, unknown>): string;
|
|
19
|
+
};
|
|
20
|
+
export default DOMPurify;
|
|
21
|
+
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/svelte';
|
|
2
|
-
import BarChart from '../charts/BarChart.svelte';
|
|
3
|
-
|
|
4
|
-
const meta = {
|
|
5
|
-
title: 'Charts/BarChart',
|
|
6
|
-
component: BarChart,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
argTypes: {
|
|
9
|
-
height: { control: { type: 'number', min: 100, max: 500 } },
|
|
10
|
-
showValues: { control: 'boolean' },
|
|
11
|
-
},
|
|
12
|
-
} satisfies Meta<BarChart>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Default: Story = {
|
|
18
|
-
args: {
|
|
19
|
-
data: [
|
|
20
|
-
{ label: 'Jan', value: 120 },
|
|
21
|
-
{ label: 'Feb', value: 180 },
|
|
22
|
-
{ label: 'Mar', value: 95 },
|
|
23
|
-
{ label: 'Apr', value: 210 },
|
|
24
|
-
{ label: 'May', value: 165 },
|
|
25
|
-
{ label: 'Jun', value: 245 },
|
|
26
|
-
],
|
|
27
|
-
height: 200,
|
|
28
|
-
showValues: true,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const CustomColors: Story = {
|
|
33
|
-
args: {
|
|
34
|
-
data: [
|
|
35
|
-
{ label: 'Users', value: 1200, color: '#6366f1' },
|
|
36
|
-
{ label: 'Orders', value: 850, color: '#f59e0b' },
|
|
37
|
-
{ label: 'Revenue', value: 2100, color: '#10b981' },
|
|
38
|
-
{ label: 'Returns', value: 120, color: '#ef4444' },
|
|
39
|
-
],
|
|
40
|
-
height: 250,
|
|
41
|
-
showValues: true,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/svelte';
|
|
2
|
-
import LineChart from '../charts/LineChart.svelte';
|
|
3
|
-
|
|
4
|
-
const meta = {
|
|
5
|
-
title: 'Charts/LineChart',
|
|
6
|
-
component: LineChart,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
argTypes: {
|
|
9
|
-
height: { control: { type: 'number', min: 100, max: 500 } },
|
|
10
|
-
showDots: { control: 'boolean' },
|
|
11
|
-
fill: { control: 'boolean' },
|
|
12
|
-
},
|
|
13
|
-
} satisfies Meta<LineChart>;
|
|
14
|
-
|
|
15
|
-
export default meta;
|
|
16
|
-
type Story = StoryObj<typeof meta>;
|
|
17
|
-
|
|
18
|
-
export const Default: Story = {
|
|
19
|
-
args: {
|
|
20
|
-
data: [
|
|
21
|
-
{ label: 'Mon', value: 120 },
|
|
22
|
-
{ label: 'Tue', value: 180 },
|
|
23
|
-
{ label: 'Wed', value: 150 },
|
|
24
|
-
{ label: 'Thu', value: 210 },
|
|
25
|
-
{ label: 'Fri', value: 190 },
|
|
26
|
-
{ label: 'Sat', value: 250 },
|
|
27
|
-
{ label: 'Sun', value: 220 },
|
|
28
|
-
],
|
|
29
|
-
height: 200,
|
|
30
|
-
showDots: true,
|
|
31
|
-
fill: true,
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const NoFill: Story = {
|
|
36
|
-
args: {
|
|
37
|
-
data: [
|
|
38
|
-
{ label: 'Q1', value: 3200 },
|
|
39
|
-
{ label: 'Q2', value: 4100 },
|
|
40
|
-
{ label: 'Q3', value: 3800 },
|
|
41
|
-
{ label: 'Q4', value: 5200 },
|
|
42
|
-
],
|
|
43
|
-
height: 250,
|
|
44
|
-
showDots: true,
|
|
45
|
-
fill: false,
|
|
46
|
-
color: '#10b981',
|
|
47
|
-
},
|
|
48
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/svelte';
|
|
2
|
-
import PieChart from '../charts/PieChart.svelte';
|
|
3
|
-
|
|
4
|
-
const meta = {
|
|
5
|
-
title: 'Charts/PieChart',
|
|
6
|
-
component: PieChart,
|
|
7
|
-
tags: ['autodocs'],
|
|
8
|
-
argTypes: {
|
|
9
|
-
size: { control: { type: 'number', min: 100, max: 400 } },
|
|
10
|
-
donut: { control: { type: 'number', min: 0, max: 0.8, step: 0.1 } },
|
|
11
|
-
},
|
|
12
|
-
} satisfies Meta<PieChart>;
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof meta>;
|
|
16
|
-
|
|
17
|
-
export const Donut: Story = {
|
|
18
|
-
args: {
|
|
19
|
-
data: [
|
|
20
|
-
{ label: 'Desktop', value: 580, color: '#6366f1' },
|
|
21
|
-
{ label: 'Mobile', value: 320, color: '#f59e0b' },
|
|
22
|
-
{ label: 'Tablet', value: 100, color: '#10b981' },
|
|
23
|
-
],
|
|
24
|
-
size: 200,
|
|
25
|
-
donut: 0.6,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const Pie: Story = {
|
|
30
|
-
args: {
|
|
31
|
-
data: [
|
|
32
|
-
{ label: 'Chrome', value: 64, color: '#4285f4' },
|
|
33
|
-
{ label: 'Safari', value: 19, color: '#5ac8fa' },
|
|
34
|
-
{ label: 'Firefox', value: 10, color: '#ff7139' },
|
|
35
|
-
{ label: 'Edge', value: 4, color: '#0078d7' },
|
|
36
|
-
{ label: 'Other', value: 3, color: '#a0a0a0' },
|
|
37
|
-
],
|
|
38
|
-
size: 220,
|
|
39
|
-
donut: 0,
|
|
40
|
-
},
|
|
41
|
-
};
|