@svadmin/lite 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/components/LiteActivityFeed.svelte +98 -0
- package/dist/components/LiteActivityFeed.svelte.d.ts +19 -0
- package/dist/components/LiteApprovalActionCard.svelte +75 -0
- package/dist/components/LiteApprovalActionCard.svelte.d.ts +10 -0
- package/dist/components/LiteCanvasAnnotation.svelte +136 -0
- package/dist/components/LiteCanvasAnnotation.svelte.d.ts +16 -0
- package/dist/components/LiteColumnHeaderFilter.svelte +38 -0
- package/dist/components/LiteColumnHeaderFilter.svelte.d.ts +8 -0
- package/dist/components/LiteColumnSettings.svelte +82 -0
- package/dist/components/LiteColumnSettings.svelte.d.ts +15 -0
- package/dist/components/LiteDecisionTable.svelte +178 -0
- package/dist/components/LiteDecisionTable.svelte.d.ts +21 -0
- package/dist/components/LiteDraggableRowTable.svelte +70 -0
- package/dist/components/LiteDraggableRowTable.svelte.d.ts +14 -0
- package/dist/components/LiteDrawerForm.svelte +85 -0
- package/dist/components/LiteDrawerForm.svelte.d.ts +13 -0
- package/dist/components/LiteEditableTable.svelte +84 -0
- package/dist/components/LiteEditableTable.svelte.d.ts +19 -0
- package/dist/components/LiteGanttChart.svelte +144 -0
- package/dist/components/LiteGanttChart.svelte.d.ts +17 -0
- package/dist/components/LiteImageCropper.svelte +81 -0
- package/dist/components/LiteImageCropper.svelte.d.ts +9 -0
- package/dist/components/LiteImportWizard.svelte +99 -0
- package/dist/components/LiteImportWizard.svelte.d.ts +11 -0
- package/dist/components/LiteJsonSchemaForm.svelte +102 -0
- package/dist/components/LiteJsonSchemaForm.svelte.d.ts +10 -0
- package/dist/components/LiteKanbanBoard.svelte +169 -0
- package/dist/components/LiteKanbanBoard.svelte.d.ts +21 -0
- package/dist/components/LiteMasterDetailView.svelte +113 -0
- package/dist/components/LiteMasterDetailView.svelte.d.ts +11 -0
- package/dist/components/LiteMediaLibraryModal.svelte +110 -0
- package/dist/components/LiteMediaLibraryModal.svelte.d.ts +17 -0
- package/dist/components/LiteMentionsInput.svelte +98 -0
- package/dist/components/LiteMentionsInput.svelte.d.ts +19 -0
- package/dist/components/LiteModalForm.svelte +84 -0
- package/dist/components/LiteModalForm.svelte.d.ts +13 -0
- package/dist/components/LiteMultiTabKeepAlive.svelte +79 -0
- package/dist/components/LiteMultiTabKeepAlive.svelte.d.ts +15 -0
- package/dist/components/LiteOfflineSyncBanner.svelte +75 -0
- package/dist/components/LiteOfflineSyncBanner.svelte.d.ts +15 -0
- package/dist/components/LitePdfDocumentViewer.svelte +138 -0
- package/dist/components/LitePdfDocumentViewer.svelte.d.ts +17 -0
- package/dist/components/LitePivotTable.svelte +163 -0
- package/dist/components/LitePivotTable.svelte.d.ts +15 -0
- package/dist/components/LitePresenceAvatarGroup.svelte +52 -0
- package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +12 -0
- package/dist/components/LitePrintableBill.svelte +119 -0
- package/dist/components/LitePrintableBill.svelte.d.ts +19 -0
- package/dist/components/LiteSensitiveDataMask.svelte +35 -0
- package/dist/components/LiteSensitiveDataMask.svelte.d.ts +7 -0
- package/dist/components/LiteSignaturePad.svelte +99 -0
- package/dist/components/LiteSignaturePad.svelte.d.ts +11 -0
- package/dist/components/LiteSplitPaneLayout.svelte +66 -0
- package/dist/components/LiteSplitPaneLayout.svelte.d.ts +9 -0
- package/dist/components/LiteSpreadsheetView.svelte +148 -0
- package/dist/components/LiteSpreadsheetView.svelte.d.ts +16 -0
- package/dist/components/LiteStepForm.svelte +144 -0
- package/dist/components/LiteStepForm.svelte.d.ts +18 -0
- package/dist/components/LiteTableSummary.svelte +93 -0
- package/dist/components/LiteTableSummary.svelte.d.ts +17 -0
- package/dist/components/LiteTreeTable.svelte +85 -0
- package/dist/components/LiteTreeTable.svelte.d.ts +13 -0
- package/dist/components/LiteVersionDiffViewer.svelte +129 -0
- package/dist/components/LiteVersionDiffViewer.svelte.d.ts +10 -0
- package/dist/components/LiteVirtualTable.svelte +52 -0
- package/dist/components/LiteVirtualTable.svelte.d.ts +14 -0
- package/dist/components/LiteWatermark.svelte +35 -0
- package/dist/components/LiteWatermark.svelte.d.ts +11 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +34 -0
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
imageUrl: string;
|
|
4
|
+
aspectRatio?: number;
|
|
5
|
+
action?: string;
|
|
6
|
+
method?: 'get' | 'post' | 'dialog' | 'GET' | 'POST' | 'DIALOG' | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
imageUrl,
|
|
11
|
+
aspectRatio = 1,
|
|
12
|
+
action = '',
|
|
13
|
+
method = 'POST',
|
|
14
|
+
}: Props = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="lite-cropper-card">
|
|
18
|
+
<div class="lite-cropper-header">
|
|
19
|
+
<strong>Image Crop (Aspect Ratio: {aspectRatio}:1)</strong>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="lite-cropper-preview">
|
|
23
|
+
<img src={imageUrl} alt="Source Preview" class="lite-cropper-img" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<form {action} {method} class="lite-cropper-form">
|
|
27
|
+
<input type="hidden" name="imageUrl" value={imageUrl} />
|
|
28
|
+
<input type="hidden" name="aspectRatio" value={aspectRatio} />
|
|
29
|
+
|
|
30
|
+
<div class="lite-cropper-row">
|
|
31
|
+
<label for="lite_crop_scale_input" class="lite-label">Scale Factor:</label>
|
|
32
|
+
<select id="lite_crop_scale_input" name="zoom" class="lite-select" style="width: 100px;">
|
|
33
|
+
<option value="1">100%</option>
|
|
34
|
+
<option value="1.2">120%</option>
|
|
35
|
+
<option value="1.5">150%</option>
|
|
36
|
+
<option value="2">200%</option>
|
|
37
|
+
</select>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="lite-cropper-footer">
|
|
41
|
+
<button type="submit" class="lite-btn lite-btn-primary lite-btn-sm">
|
|
42
|
+
Save Image
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</form>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.lite-cropper-card {
|
|
50
|
+
background: #ffffff;
|
|
51
|
+
border: 1px solid #cbd5e1;
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
padding: 16px;
|
|
54
|
+
margin-bottom: 16px;
|
|
55
|
+
max-width: 480px;
|
|
56
|
+
}
|
|
57
|
+
.lite-cropper-header {
|
|
58
|
+
padding-bottom: 8px;
|
|
59
|
+
border-bottom: 1px solid #e2e8f0;
|
|
60
|
+
margin-bottom: 12px;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
}
|
|
63
|
+
.lite-cropper-preview {
|
|
64
|
+
text-align: center;
|
|
65
|
+
background: #f1f5f9;
|
|
66
|
+
padding: 12px;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
margin-bottom: 12px;
|
|
69
|
+
}
|
|
70
|
+
.lite-cropper-img {
|
|
71
|
+
max-width: 100%;
|
|
72
|
+
max-height: 200px;
|
|
73
|
+
}
|
|
74
|
+
.lite-cropper-row {
|
|
75
|
+
margin-bottom: 12px;
|
|
76
|
+
}
|
|
77
|
+
.lite-cropper-footer {
|
|
78
|
+
display: flex;
|
|
79
|
+
justify-content: flex-end;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
imageUrl: string;
|
|
3
|
+
aspectRatio?: number;
|
|
4
|
+
action?: string;
|
|
5
|
+
method?: 'get' | 'post' | 'dialog' | 'GET' | 'POST' | 'DIALOG' | null;
|
|
6
|
+
}
|
|
7
|
+
declare const LiteImageCropper: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type LiteImageCropper = ReturnType<typeof LiteImageCropper>;
|
|
9
|
+
export default LiteImageCropper;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
resourceName: string;
|
|
6
|
+
fields?: FieldDefinition[];
|
|
7
|
+
action?: string;
|
|
8
|
+
method?: 'POST' | 'post';
|
|
9
|
+
submitText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
resourceName,
|
|
14
|
+
fields = [],
|
|
15
|
+
action = '',
|
|
16
|
+
method = 'POST',
|
|
17
|
+
submitText = 'Upload & Import CSV',
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div class="lite-import-wizard lite-form-group">
|
|
22
|
+
<div class="lite-import-title">
|
|
23
|
+
<strong>📥 Import {resourceName}</strong>
|
|
24
|
+
</div>
|
|
25
|
+
<form {action} {method} enctype="multipart/form-data" class="lite-import-form">
|
|
26
|
+
<div class="lite-file-row">
|
|
27
|
+
<label for="import_file" class="lite-label">Select CSV / JSON file:</label>
|
|
28
|
+
<input type="file" id="import_file" name="file" accept=".csv,.json" class="lite-input" required />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{#if fields.length > 0}
|
|
32
|
+
<div class="lite-field-mapping-hints">
|
|
33
|
+
<span class="lite-hint-title">Target Schema Fields:</span>
|
|
34
|
+
<div class="lite-field-tags">
|
|
35
|
+
{#each fields as field (field.key)}
|
|
36
|
+
<span class="lite-badge">{field.label} ({field.key})</span>
|
|
37
|
+
{/each}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<div class="lite-import-actions">
|
|
43
|
+
<button type="submit" class="lite-btn lite-btn-primary">
|
|
44
|
+
{submitText}
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
.lite-import-wizard {
|
|
52
|
+
padding: 12px;
|
|
53
|
+
background: #f8fafc;
|
|
54
|
+
border: 1px solid #cbd5e1;
|
|
55
|
+
border-radius: 6px;
|
|
56
|
+
margin-bottom: 16px;
|
|
57
|
+
}
|
|
58
|
+
.lite-import-title {
|
|
59
|
+
font-size: 13px;
|
|
60
|
+
color: #334155;
|
|
61
|
+
margin-bottom: 8px;
|
|
62
|
+
}
|
|
63
|
+
.lite-file-row {
|
|
64
|
+
margin-bottom: 10px;
|
|
65
|
+
}
|
|
66
|
+
.lite-label {
|
|
67
|
+
display: block;
|
|
68
|
+
font-size: 12px;
|
|
69
|
+
color: #475569;
|
|
70
|
+
margin-bottom: 4px;
|
|
71
|
+
}
|
|
72
|
+
.lite-field-mapping-hints {
|
|
73
|
+
margin-bottom: 12px;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
color: #64748b;
|
|
76
|
+
}
|
|
77
|
+
.lite-hint-title {
|
|
78
|
+
display: block;
|
|
79
|
+
margin-bottom: 4px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
}
|
|
82
|
+
.lite-field-tags {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-wrap: wrap;
|
|
85
|
+
}
|
|
86
|
+
.lite-badge {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
background: #e2e8f0;
|
|
89
|
+
color: #334155;
|
|
90
|
+
padding: 2px 6px;
|
|
91
|
+
border-radius: 3px;
|
|
92
|
+
margin-right: 6px;
|
|
93
|
+
margin-bottom: 4px;
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
}
|
|
96
|
+
.lite-import-actions {
|
|
97
|
+
margin-top: 8px;
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FieldDefinition } from '@svadmin/core';
|
|
2
|
+
interface Props {
|
|
3
|
+
resourceName: string;
|
|
4
|
+
fields?: FieldDefinition[];
|
|
5
|
+
action?: string;
|
|
6
|
+
method?: 'POST' | 'post';
|
|
7
|
+
submitText?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const LiteImportWizard: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type LiteImportWizard = ReturnType<typeof LiteImportWizard>;
|
|
11
|
+
export default LiteImportWizard;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
schema: Record<string, unknown>;
|
|
4
|
+
value?: Record<string, unknown>;
|
|
5
|
+
action?: string;
|
|
6
|
+
method?: 'get' | 'post' | 'dialog' | 'GET' | 'POST' | 'DIALOG' | null;
|
|
7
|
+
submitText?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
schema,
|
|
12
|
+
value = {},
|
|
13
|
+
action = '',
|
|
14
|
+
method = 'POST',
|
|
15
|
+
submitText = 'Submit Form',
|
|
16
|
+
}: Props = $props();
|
|
17
|
+
|
|
18
|
+
interface FieldMeta {
|
|
19
|
+
key: string;
|
|
20
|
+
title: string;
|
|
21
|
+
type: string;
|
|
22
|
+
enum?: Array<string | number>;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const properties = $derived.by<FieldMeta[]>(() => {
|
|
27
|
+
const propsObj = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
28
|
+
const requiredKeys = Array.isArray(schema.required) ? (schema.required as string[]) : [];
|
|
29
|
+
|
|
30
|
+
return Object.entries(propsObj).map(([key, propDef]) => ({
|
|
31
|
+
key,
|
|
32
|
+
title: String(propDef.title ?? key),
|
|
33
|
+
type: String(propDef.type ?? 'string'),
|
|
34
|
+
enum: Array.isArray(propDef.enum) ? (propDef.enum as Array<string | number>) : undefined,
|
|
35
|
+
required: requiredKeys.includes(key),
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<div class="lite-schema-form-card">
|
|
41
|
+
{#if schema.title}
|
|
42
|
+
<div class="lite-schema-form-header">
|
|
43
|
+
<strong>{schema.title}</strong>
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
|
|
47
|
+
<form {action} {method} class="lite-schema-form">
|
|
48
|
+
{#each properties as field (field.key)}
|
|
49
|
+
<div class="lite-schema-field">
|
|
50
|
+
<label for="lite_json_{field.key}" class="lite-label">
|
|
51
|
+
{field.title} {#if field.required}<span style="color: #ef4444;">*</span>{/if}
|
|
52
|
+
</label>
|
|
53
|
+
{#if field.enum}
|
|
54
|
+
<select id="lite_json_{field.key}" name={field.key} class="lite-select" required={field.required}>
|
|
55
|
+
{#each field.enum as option (option)}
|
|
56
|
+
<option value={String(option)} selected={String(value[field.key]) === String(option)}>
|
|
57
|
+
{option}
|
|
58
|
+
</option>
|
|
59
|
+
{/each}
|
|
60
|
+
</select>
|
|
61
|
+
{:else if field.type === 'boolean'}
|
|
62
|
+
<input id="lite_json_{field.key}" type="checkbox" name={field.key} checked={Boolean(value[field.key])} />
|
|
63
|
+
{:else if field.type === 'number' || field.type === 'integer'}
|
|
64
|
+
<input id="lite_json_{field.key}" type="number" name={field.key} value={Number(value[field.key] ?? 0)} class="lite-input" required={field.required} />
|
|
65
|
+
{:else}
|
|
66
|
+
<input id="lite_json_{field.key}" type="text" name={field.key} value={String(value[field.key] ?? '')} class="lite-input" required={field.required} />
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
69
|
+
{/each}
|
|
70
|
+
|
|
71
|
+
<div class="lite-schema-footer">
|
|
72
|
+
<button type="submit" class="lite-btn lite-btn-primary lite-btn-sm">
|
|
73
|
+
{submitText}
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
.lite-schema-form-card {
|
|
81
|
+
background: #ffffff;
|
|
82
|
+
border: 1px solid #cbd5e1;
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
padding: 16px;
|
|
85
|
+
margin-bottom: 16px;
|
|
86
|
+
}
|
|
87
|
+
.lite-schema-form-header {
|
|
88
|
+
padding-bottom: 8px;
|
|
89
|
+
border-bottom: 1px solid #e2e8f0;
|
|
90
|
+
margin-bottom: 12px;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
}
|
|
93
|
+
.lite-schema-field {
|
|
94
|
+
margin-bottom: 12px;
|
|
95
|
+
}
|
|
96
|
+
.lite-schema-footer {
|
|
97
|
+
display: flex;
|
|
98
|
+
justify-content: flex-end;
|
|
99
|
+
padding-top: 12px;
|
|
100
|
+
border-top: 1px solid #e2e8f0;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
schema: Record<string, unknown>;
|
|
3
|
+
value?: Record<string, unknown>;
|
|
4
|
+
action?: string;
|
|
5
|
+
method?: 'get' | 'post' | 'dialog' | 'GET' | 'POST' | 'DIALOG' | null;
|
|
6
|
+
submitText?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const LiteJsonSchemaForm: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type LiteJsonSchemaForm = ReturnType<typeof LiteJsonSchemaForm>;
|
|
10
|
+
export default LiteJsonSchemaForm;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface KanbanCard {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
columnId: string;
|
|
7
|
+
priority?: 'low' | 'medium' | 'high' | 'urgent';
|
|
8
|
+
tags?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface KanbanColumn {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
columns: KanbanColumn[];
|
|
18
|
+
cards?: KanbanCard[];
|
|
19
|
+
formAction?: string;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
columns = [],
|
|
25
|
+
cards = [],
|
|
26
|
+
formAction = '',
|
|
27
|
+
class: className = '',
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div class="sv-lite-kanban-board {className}">
|
|
32
|
+
{#each columns as col (col.id)}
|
|
33
|
+
{@const colCards = cards.filter((c) => c.columnId === col.id)}
|
|
34
|
+
<div class="sv-lite-kanban-column">
|
|
35
|
+
<div class="sv-lite-column-header">
|
|
36
|
+
<span class="sv-lite-column-title">{col.title}</span>
|
|
37
|
+
<span class="sv-lite-column-count">({colCards.length})</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="sv-lite-column-cards">
|
|
41
|
+
{#each colCards as card (card.id)}
|
|
42
|
+
<div class="sv-lite-kanban-card">
|
|
43
|
+
<div class="sv-lite-card-title">{card.title}</div>
|
|
44
|
+
{#if card.description}
|
|
45
|
+
<div class="sv-lite-card-desc">{card.description}</div>
|
|
46
|
+
{/if}
|
|
47
|
+
<div class="sv-lite-card-meta">
|
|
48
|
+
{#if card.priority}
|
|
49
|
+
<span class="sv-lite-priority-badge sv-lite-{card.priority}">{card.priority}</span>
|
|
50
|
+
{/if}
|
|
51
|
+
{#each card.tags ?? [] as tag (tag)}
|
|
52
|
+
<span class="sv-lite-tag-badge">#{tag}</span>
|
|
53
|
+
{/each}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
{/each}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{#if formAction}
|
|
60
|
+
<form method="POST" action={formAction} class="sv-lite-card-form">
|
|
61
|
+
<input type="hidden" name="columnId" value={col.id} />
|
|
62
|
+
<input type="text" name="title" placeholder="+ Add card..." class="sv-lite-card-input" />
|
|
63
|
+
<button type="submit" class="sv-lite-card-btn">Add</button>
|
|
64
|
+
</form>
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
{/each}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
.sv-lite-kanban-board {
|
|
72
|
+
display: block;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
overflow-x: auto;
|
|
75
|
+
padding-bottom: 12px;
|
|
76
|
+
font-size: 12px;
|
|
77
|
+
}
|
|
78
|
+
.sv-lite-kanban-column {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
vertical-align: top;
|
|
81
|
+
width: 260px;
|
|
82
|
+
margin-right: 12px;
|
|
83
|
+
background-color: #f8fafc;
|
|
84
|
+
border: 1px solid #e2e8f0;
|
|
85
|
+
border-radius: 6px;
|
|
86
|
+
padding: 10px;
|
|
87
|
+
white-space: normal;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
}
|
|
90
|
+
.sv-lite-column-header {
|
|
91
|
+
font-weight: bold;
|
|
92
|
+
padding-bottom: 8px;
|
|
93
|
+
border-bottom: 1px solid #cbd5e1;
|
|
94
|
+
color: #1e293b;
|
|
95
|
+
margin-bottom: 8px;
|
|
96
|
+
}
|
|
97
|
+
.sv-lite-column-count {
|
|
98
|
+
color: #64748b;
|
|
99
|
+
font-weight: normal;
|
|
100
|
+
font-size: 11px;
|
|
101
|
+
margin-left: 4px;
|
|
102
|
+
}
|
|
103
|
+
.sv-lite-kanban-card {
|
|
104
|
+
background-color: #ffffff;
|
|
105
|
+
border: 1px solid #cbd5e1;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
padding: 8px;
|
|
108
|
+
margin-bottom: 8px;
|
|
109
|
+
}
|
|
110
|
+
.sv-lite-card-title {
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
color: #0f172a;
|
|
113
|
+
margin-bottom: 4px;
|
|
114
|
+
}
|
|
115
|
+
.sv-lite-card-desc {
|
|
116
|
+
color: #64748b;
|
|
117
|
+
font-size: 11px;
|
|
118
|
+
margin-bottom: 6px;
|
|
119
|
+
}
|
|
120
|
+
.sv-lite-card-meta {
|
|
121
|
+
padding-top: 4px;
|
|
122
|
+
border-top: 1px solid #f1f5f9;
|
|
123
|
+
}
|
|
124
|
+
.sv-lite-priority-badge {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
padding: 1px 4px;
|
|
127
|
+
font-size: 10px;
|
|
128
|
+
border-radius: 2px;
|
|
129
|
+
text-transform: uppercase;
|
|
130
|
+
margin-right: 4px;
|
|
131
|
+
}
|
|
132
|
+
.sv-lite-urgent, .sv-lite-high {
|
|
133
|
+
background-color: #fee2e2;
|
|
134
|
+
color: #991b1b;
|
|
135
|
+
}
|
|
136
|
+
.sv-lite-medium {
|
|
137
|
+
background-color: #e0e7ff;
|
|
138
|
+
color: #3730a3;
|
|
139
|
+
}
|
|
140
|
+
.sv-lite-low {
|
|
141
|
+
background-color: #f1f5f9;
|
|
142
|
+
color: #475569;
|
|
143
|
+
}
|
|
144
|
+
.sv-lite-tag-badge {
|
|
145
|
+
display: inline-block;
|
|
146
|
+
color: #64748b;
|
|
147
|
+
font-size: 10px;
|
|
148
|
+
margin-right: 4px;
|
|
149
|
+
}
|
|
150
|
+
.sv-lite-card-form {
|
|
151
|
+
margin-top: 6px;
|
|
152
|
+
}
|
|
153
|
+
.sv-lite-card-input {
|
|
154
|
+
width: 170px;
|
|
155
|
+
padding: 4px 6px;
|
|
156
|
+
font-size: 11px;
|
|
157
|
+
border: 1px solid #cbd5e1;
|
|
158
|
+
border-radius: 3px;
|
|
159
|
+
}
|
|
160
|
+
.sv-lite-card-btn {
|
|
161
|
+
padding: 4px 8px;
|
|
162
|
+
font-size: 11px;
|
|
163
|
+
background-color: #4f46e5;
|
|
164
|
+
color: #ffffff;
|
|
165
|
+
border: none;
|
|
166
|
+
border-radius: 3px;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface KanbanCard {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
columnId: string;
|
|
6
|
+
priority?: 'low' | 'medium' | 'high' | 'urgent';
|
|
7
|
+
tags?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface KanbanColumn {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}
|
|
13
|
+
interface Props {
|
|
14
|
+
columns: KanbanColumn[];
|
|
15
|
+
cards?: KanbanCard[];
|
|
16
|
+
formAction?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const LiteKanbanBoard: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LiteKanbanBoard = ReturnType<typeof LiteKanbanBoard>;
|
|
21
|
+
export default LiteKanbanBoard;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
items?: Record<string, unknown>[];
|
|
6
|
+
selectedId?: string | number;
|
|
7
|
+
idKey?: string;
|
|
8
|
+
titleKey?: string;
|
|
9
|
+
detailSnippet?: Snippet<[Record<string, unknown>]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
items = [],
|
|
14
|
+
selectedId,
|
|
15
|
+
idKey = 'id',
|
|
16
|
+
titleKey = 'title',
|
|
17
|
+
detailSnippet,
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
const activeItem = $derived(
|
|
21
|
+
items.find((i) => String(i[idKey]) === String(selectedId)) ?? items[0]
|
|
22
|
+
);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div class="lite-master-detail-card">
|
|
26
|
+
<!-- Left Master List -->
|
|
27
|
+
<div class="lite-master-list">
|
|
28
|
+
<div class="lite-master-header">
|
|
29
|
+
<strong>Item Directory</strong>
|
|
30
|
+
</div>
|
|
31
|
+
<ul class="lite-master-items">
|
|
32
|
+
{#each items as item (item[idKey])}
|
|
33
|
+
{@const isSelected = String(item[idKey]) === String(activeItem?.[idKey])}
|
|
34
|
+
<li class="lite-master-item {isSelected ? 'lite-master-item-active' : ''}">
|
|
35
|
+
<a href="?selectedId={item[idKey]}" class="lite-master-link">
|
|
36
|
+
{item[titleKey] ?? item.name ?? `Item #${item[idKey]}`}
|
|
37
|
+
</a>
|
|
38
|
+
</li>
|
|
39
|
+
{/each}
|
|
40
|
+
</ul>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Right Detail View -->
|
|
44
|
+
<div class="lite-detail-pane">
|
|
45
|
+
{#if activeItem}
|
|
46
|
+
{#if detailSnippet}
|
|
47
|
+
{@render detailSnippet(activeItem)}
|
|
48
|
+
{:else}
|
|
49
|
+
<div class="lite-detail-content">
|
|
50
|
+
<h4 style="margin: 0 0 12px 0;">{activeItem[titleKey] ?? activeItem.name ?? `Item #${activeItem[idKey]}`}</h4>
|
|
51
|
+
<table class="lite-table">
|
|
52
|
+
<tbody>
|
|
53
|
+
{#each Object.entries(activeItem) as [k, v] (k)}
|
|
54
|
+
<tr>
|
|
55
|
+
<th style="width: 30%; text-align: left; background: #f8fafc;">{k}</th>
|
|
56
|
+
<td>{typeof v === 'object' ? JSON.stringify(v) : String(v ?? '—')}</td>
|
|
57
|
+
</tr>
|
|
58
|
+
{/each}
|
|
59
|
+
</tbody>
|
|
60
|
+
</table>
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
{:else}
|
|
64
|
+
<div style="color: #94a3b8; padding: 24px; text-align: center;">
|
|
65
|
+
Select an item to view details
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
.lite-master-detail-card {
|
|
73
|
+
display: flex;
|
|
74
|
+
background: #ffffff;
|
|
75
|
+
border: 1px solid #cbd5e1;
|
|
76
|
+
border-radius: 6px;
|
|
77
|
+
margin-bottom: 16px;
|
|
78
|
+
min-height: 320px;
|
|
79
|
+
}
|
|
80
|
+
.lite-master-list {
|
|
81
|
+
flex: 0 0 240px;
|
|
82
|
+
border-right: 1px solid #e2e8f0;
|
|
83
|
+
background: #f8fafc;
|
|
84
|
+
}
|
|
85
|
+
.lite-master-header {
|
|
86
|
+
padding: 10px 12px;
|
|
87
|
+
border-bottom: 1px solid #e2e8f0;
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
}
|
|
90
|
+
.lite-master-items {
|
|
91
|
+
list-style: none;
|
|
92
|
+
padding: 0;
|
|
93
|
+
margin: 0;
|
|
94
|
+
}
|
|
95
|
+
.lite-master-item {
|
|
96
|
+
border-bottom: 1px solid #f1f5f9;
|
|
97
|
+
}
|
|
98
|
+
.lite-master-item-active {
|
|
99
|
+
background: #e0e7ff;
|
|
100
|
+
font-weight: bold;
|
|
101
|
+
}
|
|
102
|
+
.lite-master-link {
|
|
103
|
+
display: block;
|
|
104
|
+
padding: 8px 12px;
|
|
105
|
+
color: #1e293b;
|
|
106
|
+
text-decoration: none;
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
}
|
|
109
|
+
.lite-detail-pane {
|
|
110
|
+
flex: 1 1 auto;
|
|
111
|
+
padding: 16px;
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
items?: Record<string, unknown>[];
|
|
4
|
+
selectedId?: string | number;
|
|
5
|
+
idKey?: string;
|
|
6
|
+
titleKey?: string;
|
|
7
|
+
detailSnippet?: Snippet<[Record<string, unknown>]>;
|
|
8
|
+
}
|
|
9
|
+
declare const LiteMasterDetailView: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type LiteMasterDetailView = ReturnType<typeof LiteMasterDetailView>;
|
|
11
|
+
export default LiteMasterDetailView;
|