@softwareone/spi-sv5-library 1.5.9 → 1.6.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/dist/Form/AttachFile/AttachFileForm.svelte +294 -0
- package/dist/Form/AttachFile/AttachFileForm.svelte.d.ts +17 -0
- package/dist/Form/AttachFile/FileManager.svelte +115 -0
- package/dist/Form/AttachFile/FileManager.svelte.d.ts +7 -0
- package/dist/Form/AttachFile/Warnings.svelte +64 -0
- package/dist/Form/AttachFile/Warnings.svelte.d.ts +6 -0
- package/dist/Form/AttachFile/attachFile.svelte.d.ts +7 -0
- package/dist/Form/AttachFile/fileContext.d.ts +3 -0
- package/dist/Form/AttachFile/fileContext.js +8 -0
- package/dist/Form/AttachFile/helper.d.ts +7 -0
- package/dist/Form/AttachFile/helper.js +7 -0
- package/dist/Home/Home.svelte +1 -1
- package/dist/Menu/Menu.svelte +1 -1
- package/dist/Menu/Menu.svelte.d.ts +1 -1
- package/dist/Menu/MenuItem.svelte +5 -3
- package/dist/Menu/MenuItem.svelte.d.ts +1 -1
- package/dist/Menu/MenuState.svelte.d.ts +22 -0
- package/dist/Menu/MenuState.svelte.js +7 -0
- package/dist/Menu/Sidebar.svelte +46 -36
- package/dist/Menu/Sidebar.svelte.d.ts +4 -8
- package/dist/Tabs/Tabs.svelte +16 -9
- package/dist/Tabs/Tabs.svelte.d.ts +2 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -2
- package/package.json +1 -1
- package/dist/Home/homeState.svelte.d.ts +0 -4
- package/dist/Home/homeState.svelte.js +0 -1
- package/dist/Menu/SidebarState.svelte.d.ts +0 -6
- /package/dist/{Menu/SidebarState.svelte.js → Form/AttachFile/attachFile.svelte.js} +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { Form, Notification } from '../../index.js';
|
|
5
|
+
import { getFormContext } from '../FormController/context.js';
|
|
6
|
+
import type { FileValidationCallback } from './attachFile.svelte.js';
|
|
7
|
+
import { getFileValidationContext } from './fileContext.js';
|
|
8
|
+
import FileManager from './FileManager.svelte';
|
|
9
|
+
import Warnings from './Warnings.svelte';
|
|
10
|
+
|
|
11
|
+
type Schema = z.infer<typeof schema>;
|
|
12
|
+
|
|
13
|
+
interface AttachFileFormProps {
|
|
14
|
+
extraData?: Record<string, string | number>;
|
|
15
|
+
action: string;
|
|
16
|
+
schema: z.ZodObject;
|
|
17
|
+
accept?: string;
|
|
18
|
+
multiple?: boolean;
|
|
19
|
+
externalFileValidationMessages?: string[];
|
|
20
|
+
incomingFileNames?: string[];
|
|
21
|
+
files?: FileList;
|
|
22
|
+
onsuccess: (data?: Record<string, unknown>) => void;
|
|
23
|
+
onfailure?: (status: number | undefined, error: unknown) => void;
|
|
24
|
+
ongetattachments?: (files: FileList) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
extraData,
|
|
29
|
+
action,
|
|
30
|
+
accept,
|
|
31
|
+
multiple,
|
|
32
|
+
externalFileValidationMessages = [],
|
|
33
|
+
incomingFileNames = [],
|
|
34
|
+
files,
|
|
35
|
+
schema,
|
|
36
|
+
onsuccess,
|
|
37
|
+
onfailure,
|
|
38
|
+
ongetattachments
|
|
39
|
+
}: AttachFileFormProps = $props();
|
|
40
|
+
|
|
41
|
+
const { form, errors } = getFormContext<Schema>();
|
|
42
|
+
|
|
43
|
+
const validateFile: FileValidationCallback | undefined = getFileValidationContext();
|
|
44
|
+
|
|
45
|
+
let htmlInputFiles = $state<HTMLInputElement>();
|
|
46
|
+
let validationMessages = $state<string[]>([]);
|
|
47
|
+
|
|
48
|
+
const extensionFilesMessage = accept
|
|
49
|
+
? `Files of the following type are allowed: ${accept.replaceAll(',', ' & ')}.`
|
|
50
|
+
: 'All file types are allowed.';
|
|
51
|
+
|
|
52
|
+
const reviewMessages = [
|
|
53
|
+
extensionFilesMessage,
|
|
54
|
+
'Duplicate file names are not allowed.',
|
|
55
|
+
...externalFileValidationMessages
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const mappedFiles = $derived(
|
|
59
|
+
files ? Array.from(files).map((file) => ({ name: file.name, size: file.size })) : []
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
$effect(() => {
|
|
63
|
+
if (files && htmlInputFiles) {
|
|
64
|
+
htmlInputFiles.files = files;
|
|
65
|
+
$form.files = mappedFiles;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const onChangeInputFile = async (selectedFiles: FileList | null) => {
|
|
70
|
+
if (selectedFiles?.length) {
|
|
71
|
+
validationMessages = [];
|
|
72
|
+
let newFileList = new DataTransfer();
|
|
73
|
+
|
|
74
|
+
const list: File[] = Array.from(selectedFiles);
|
|
75
|
+
|
|
76
|
+
if (files?.length && multiple) {
|
|
77
|
+
const oldFileList: File[] = Array.from(files);
|
|
78
|
+
for (const file of oldFileList) {
|
|
79
|
+
newFileList.items.add(file);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const file of list) {
|
|
84
|
+
const isEnabled = await isEnabledToUpload(file);
|
|
85
|
+
if (isEnabled) {
|
|
86
|
+
newFileList.items.add(file);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
files = newFileList.files;
|
|
91
|
+
ongetattachments?.(files);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isEnabledToUpload = async (file: File): Promise<boolean> => {
|
|
96
|
+
const fileName = file.name;
|
|
97
|
+
|
|
98
|
+
if (!isFileExtensionAllowed(fileName)) {
|
|
99
|
+
validationMessages.push(`${fileName} - File extension is not allowed.`);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isSelectedFileAlreadyExists(fileName)) {
|
|
104
|
+
validationMessages.push(`${fileName} - File has already been preselected.`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isSelectedFileAlreadyExistsOnIncomingFiles(fileName)) {
|
|
109
|
+
validationMessages.push(`${fileName} - File has already been uploaded.`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (validateFile) {
|
|
114
|
+
const message = await validateFile(file);
|
|
115
|
+
if (message) {
|
|
116
|
+
validationMessages.push(`${fileName} - ${message}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const isFileExtensionAllowed = (file: string): boolean => {
|
|
125
|
+
if (!accept) return true;
|
|
126
|
+
|
|
127
|
+
const acceptedExts = accept
|
|
128
|
+
.split(',')
|
|
129
|
+
.map((x) => x.trim().toLowerCase())
|
|
130
|
+
.filter((x) => x.startsWith('.'));
|
|
131
|
+
|
|
132
|
+
const fileExts = file
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.split('.')
|
|
135
|
+
.slice(1)
|
|
136
|
+
.map((extension) => `.${extension}`);
|
|
137
|
+
|
|
138
|
+
return fileExts.some((extension) => acceptedExts.includes(extension));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const isSelectedFileAlreadyExists = (fileName: string): boolean => {
|
|
142
|
+
if (!files) return false;
|
|
143
|
+
return Array.from(files).some((file) => file.name === fileName);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const isSelectedFileAlreadyExistsOnIncomingFiles = (fileName: string): boolean => {
|
|
147
|
+
if (incomingFileNames.length === 0) return false;
|
|
148
|
+
return incomingFileNames.includes(fileName);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const updateFiles = (list: File[]) => {
|
|
152
|
+
let newFileList = new DataTransfer();
|
|
153
|
+
list.forEach((file) => {
|
|
154
|
+
newFileList.items.add(file);
|
|
155
|
+
});
|
|
156
|
+
files = newFileList.files;
|
|
157
|
+
ongetattachments?.(files);
|
|
158
|
+
};
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
<aside class="container">
|
|
162
|
+
<section class="notification">
|
|
163
|
+
<Notification title="" type="info" disableBorder>
|
|
164
|
+
{#snippet content()}
|
|
165
|
+
<ul class="message">
|
|
166
|
+
{#each reviewMessages as message}
|
|
167
|
+
<li>{message}</li>
|
|
168
|
+
{/each}
|
|
169
|
+
</ul>
|
|
170
|
+
{/snippet}
|
|
171
|
+
</Notification>
|
|
172
|
+
</section>
|
|
173
|
+
<Form {action} initialData={{ files }} {schema} {extraData} {onsuccess} {onfailure}>
|
|
174
|
+
{#snippet children()}
|
|
175
|
+
<h2 class="title">Upload file</h2>
|
|
176
|
+
<section class="drop-area">
|
|
177
|
+
<p class="caption">Drag and drop files here</p>
|
|
178
|
+
<p class="text-small">or</p>
|
|
179
|
+
|
|
180
|
+
<input
|
|
181
|
+
name="files"
|
|
182
|
+
type="file"
|
|
183
|
+
aria-label="Select files to upload"
|
|
184
|
+
{accept}
|
|
185
|
+
{multiple}
|
|
186
|
+
bind:this={htmlInputFiles}
|
|
187
|
+
onchange={({ currentTarget }) => onChangeInputFile(currentTarget.files)}
|
|
188
|
+
/>
|
|
189
|
+
<div class="file-select">
|
|
190
|
+
<span>Select file</span>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
{#if $errors.files}
|
|
194
|
+
<p class="text-error">{$errors.files[0]}</p>
|
|
195
|
+
{/if}
|
|
196
|
+
{#if validationMessages?.length}
|
|
197
|
+
<Warnings {validationMessages} />
|
|
198
|
+
{/if}
|
|
199
|
+
{#if files?.length}
|
|
200
|
+
<FileManager {files} {updateFiles} />
|
|
201
|
+
{/if}
|
|
202
|
+
{/snippet}
|
|
203
|
+
</Form>
|
|
204
|
+
</aside>
|
|
205
|
+
|
|
206
|
+
<style>
|
|
207
|
+
.container {
|
|
208
|
+
--color-red: #b30825;
|
|
209
|
+
--color-gray-outer: #434952;
|
|
210
|
+
--color-gray-auro: #6b7180;
|
|
211
|
+
--color-gray: #e0e5e8;
|
|
212
|
+
--color-magentablue: #472aff;
|
|
213
|
+
--color-border: #cccccc;
|
|
214
|
+
--color-bg-select: #e0e7ff;
|
|
215
|
+
--color-text-select: var(--color-magentablue);
|
|
216
|
+
--spacing-md: 20px;
|
|
217
|
+
--spacing-sm: 10px;
|
|
218
|
+
--border-radius-md: 5px;
|
|
219
|
+
width: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.notification {
|
|
223
|
+
display: flex;
|
|
224
|
+
border-width: 1px;
|
|
225
|
+
border-radius: 8px;
|
|
226
|
+
border-style: solid;
|
|
227
|
+
border-color: var(--color-gray);
|
|
228
|
+
padding: 8px;
|
|
229
|
+
margin-bottom: 16px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.message {
|
|
233
|
+
font-size: 14px;
|
|
234
|
+
line-height: 20px;
|
|
235
|
+
}
|
|
236
|
+
.title {
|
|
237
|
+
font-size: 16px;
|
|
238
|
+
line-height: 24px;
|
|
239
|
+
font-weight: 500;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.caption {
|
|
243
|
+
font-size: 16px;
|
|
244
|
+
line-height: 24px;
|
|
245
|
+
color: var(--color-gray-outer);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.text-small {
|
|
249
|
+
font-size: 14px;
|
|
250
|
+
line-height: 20px;
|
|
251
|
+
color: var(--color-gray-auro);
|
|
252
|
+
margin-top: 4px;
|
|
253
|
+
margin-bottom: 8px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.text-error {
|
|
257
|
+
color: var(--color-red);
|
|
258
|
+
margin: 4px;
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
line-height: 16px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.drop-area {
|
|
264
|
+
position: relative;
|
|
265
|
+
padding: var(--spacing-md);
|
|
266
|
+
border: 2px dashed var(--color-border);
|
|
267
|
+
border-radius: var(--border-radius-md);
|
|
268
|
+
text-align: center;
|
|
269
|
+
margin-top: 8px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.drop-area input[type='file'] {
|
|
273
|
+
position: absolute;
|
|
274
|
+
opacity: 0;
|
|
275
|
+
inset: 0;
|
|
276
|
+
width: 100%;
|
|
277
|
+
height: 100%;
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.file-select {
|
|
282
|
+
display: inline-block;
|
|
283
|
+
background: var(--color-bg-select);
|
|
284
|
+
color: var(--color-text-select);
|
|
285
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
286
|
+
border-radius: var(--border-radius-md);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.drop-area input[type='file']::before {
|
|
290
|
+
position: absolute;
|
|
291
|
+
content: '';
|
|
292
|
+
inset: 0;
|
|
293
|
+
}
|
|
294
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
interface AttachFileFormProps {
|
|
3
|
+
extraData?: Record<string, string | number>;
|
|
4
|
+
action: string;
|
|
5
|
+
schema: z.ZodObject;
|
|
6
|
+
accept?: string;
|
|
7
|
+
multiple?: boolean;
|
|
8
|
+
externalFileValidationMessages?: string[];
|
|
9
|
+
incomingFileNames?: string[];
|
|
10
|
+
files?: FileList;
|
|
11
|
+
onsuccess: (data?: Record<string, unknown>) => void;
|
|
12
|
+
onfailure?: (status: number | undefined, error: unknown) => void;
|
|
13
|
+
ongetattachments?: (files: FileList) => void;
|
|
14
|
+
}
|
|
15
|
+
declare const AttachFileForm: import("svelte").Component<AttachFileFormProps, {}, "">;
|
|
16
|
+
type AttachFileForm = ReturnType<typeof AttachFileForm>;
|
|
17
|
+
export default AttachFileForm;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { limitInMegabytes, mbInBytes } from './helper.js';
|
|
3
|
+
|
|
4
|
+
interface FileProps {
|
|
5
|
+
files: FileList;
|
|
6
|
+
updateFiles: (list: File[]) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { files, updateFiles }: FileProps = $props();
|
|
10
|
+
|
|
11
|
+
let totalSize = $derived(files ? Array.from(files).reduce((sum, file) => sum + file.size, 0) : 0);
|
|
12
|
+
|
|
13
|
+
const removeFile = (index: number) => {
|
|
14
|
+
let fileList: File[] = Array.from(files);
|
|
15
|
+
fileList.splice(index, 1);
|
|
16
|
+
updateFiles(fileList);
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<section>
|
|
21
|
+
<p class="header-file">
|
|
22
|
+
{(totalSize / mbInBytes).toFixed(2)} from {limitInMegabytes} MB
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<ul class="container">
|
|
26
|
+
{#if files?.length}
|
|
27
|
+
{#each Array.from(files) as file, index}
|
|
28
|
+
<li class="list-detail">
|
|
29
|
+
<span class="material-icons-outlined">description</span>
|
|
30
|
+
|
|
31
|
+
<span class="item-detail">
|
|
32
|
+
<h3 class="file-name">{file.name}</h3>
|
|
33
|
+
<p class="file-size">
|
|
34
|
+
{(file.size / mbInBytes).toFixed(2)} MB
|
|
35
|
+
</p>
|
|
36
|
+
</span>
|
|
37
|
+
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onclick={() => removeFile(index)}
|
|
41
|
+
class="button-icon"
|
|
42
|
+
aria-label="Remove file"
|
|
43
|
+
>
|
|
44
|
+
<span class="material-icons-outlined icon-size">close</span>
|
|
45
|
+
</button>
|
|
46
|
+
</li>
|
|
47
|
+
{/each}
|
|
48
|
+
{/if}
|
|
49
|
+
</ul>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.header-file {
|
|
54
|
+
margin-top: 4px;
|
|
55
|
+
color: var(--color-gray-auro);
|
|
56
|
+
text-align: right;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.container {
|
|
60
|
+
margin-top: 8px;
|
|
61
|
+
height: auto;
|
|
62
|
+
max-height: 208px;
|
|
63
|
+
overflow-x: hidden;
|
|
64
|
+
overflow-y: visible;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.list-detail {
|
|
68
|
+
--color-white-anti: #f5f7f9;
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
background-color: var(--color-gray-light, var(--color-white-anti));
|
|
72
|
+
border-radius: 8px;
|
|
73
|
+
gap: 8px;
|
|
74
|
+
padding: 8px;
|
|
75
|
+
margin-bottom: 8px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.item-detail {
|
|
79
|
+
width: 100%;
|
|
80
|
+
padding-left: 8px;
|
|
81
|
+
padding-right: 8px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.file-name {
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
line-height: 16px;
|
|
87
|
+
font-weight: 500;
|
|
88
|
+
word-break: break-all;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.file-size {
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
line-height: 16px;
|
|
94
|
+
color: var(--color-gray-auro);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.button-icon {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
align-items: center;
|
|
101
|
+
font-size: 14px;
|
|
102
|
+
line-height: 20px;
|
|
103
|
+
border: none;
|
|
104
|
+
background-color: transparent;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.material-icons-outlined {
|
|
109
|
+
font-size: 40px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.icon-size {
|
|
113
|
+
font-size: 15px;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface WarningProps {
|
|
3
|
+
validationMessages: string[];
|
|
4
|
+
}
|
|
5
|
+
let { validationMessages }: WarningProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<details class="container">
|
|
9
|
+
<summary class="header-summary" aria-label="Warnings">
|
|
10
|
+
<span class="material-icons-outlined">warning</span>
|
|
11
|
+
<span>Warnings</span>
|
|
12
|
+
</summary>
|
|
13
|
+
|
|
14
|
+
<section class="detail-section">
|
|
15
|
+
<ul class="list-inside">
|
|
16
|
+
{#each validationMessages as message}
|
|
17
|
+
<li class="item">
|
|
18
|
+
{message}
|
|
19
|
+
</li>
|
|
20
|
+
{/each}
|
|
21
|
+
</ul>
|
|
22
|
+
</section>
|
|
23
|
+
</details>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
.container {
|
|
27
|
+
margin-top: 16px;
|
|
28
|
+
font-size: 12px;
|
|
29
|
+
line-height: 16px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.header-summary {
|
|
33
|
+
display: flex;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
gap: 4px;
|
|
36
|
+
--color: #e67e00;
|
|
37
|
+
color: var(--color);
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
padding: 4px;
|
|
40
|
+
border-width: 1px;
|
|
41
|
+
border-radius: 4px;
|
|
42
|
+
border-style: solid;
|
|
43
|
+
width: max-content;
|
|
44
|
+
}
|
|
45
|
+
.detail-section {
|
|
46
|
+
margin-top: 4px;
|
|
47
|
+
margin-left: 8px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.list-inside {
|
|
51
|
+
list-style-position: inside;
|
|
52
|
+
padding-left: 16px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.item {
|
|
56
|
+
white-space: pre-line;
|
|
57
|
+
overflow-wrap: break-word;
|
|
58
|
+
list-style-type: disc;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.material-icons-outlined {
|
|
62
|
+
font-size: 15px;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const fileContextKey = Symbol('fileContext');
|
|
3
|
+
export const setFileValidationContext = (validateFile) => {
|
|
4
|
+
setContext(fileContextKey, validateFile);
|
|
5
|
+
};
|
|
6
|
+
export const getFileValidationContext = () => {
|
|
7
|
+
return getContext(fileContextKey);
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const limitInMegabytes = 100;
|
|
2
|
+
export const mbInBytes = 1048576;
|
|
3
|
+
export const limitInBytes = limitInMegabytes * mbInBytes;
|
|
4
|
+
export const AttachFileMessages = {
|
|
5
|
+
FilesRequired: 'You must select at least one file',
|
|
6
|
+
SizeLimit: `The selected files should not exceed ${limitInMegabytes} Megabytes`
|
|
7
|
+
};
|
package/dist/Home/Home.svelte
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<section class="home-container grid">
|
|
17
17
|
{#each homeItems as homeItem}
|
|
18
18
|
<a href={homeItem.url} class="home-item">
|
|
19
|
-
<img src={homeItem.
|
|
19
|
+
<img src={homeItem.homeIcon} alt={homeItem.text} />
|
|
20
20
|
<div>
|
|
21
21
|
<h2>{homeItem.text}</h2>
|
|
22
22
|
<p>{homeItem.detail}</p>
|
package/dist/Menu/Menu.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Tooltip } from '../index.js';
|
|
3
|
-
import type { MenuItem } from './
|
|
3
|
+
import type { MenuItem } from './MenuState.svelte.js';
|
|
4
4
|
|
|
5
5
|
interface MenuItemProps {
|
|
6
6
|
item: MenuItem;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
]}
|
|
24
24
|
onclick={() => onClick?.(item)}
|
|
25
25
|
>
|
|
26
|
+
|
|
26
27
|
<span class="material-icons-outlined icon-span">{item.icon}</span>
|
|
27
28
|
<h2 class="item-name-span" class:hidden={isCollapsed}>{item.text}</h2>
|
|
28
29
|
</a>
|
|
@@ -88,12 +89,13 @@
|
|
|
88
89
|
background-color: #f4f6f8;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
.active-collapsed
|
|
92
|
+
.active-collapsed {
|
|
92
93
|
background-color: #eaecff;
|
|
94
|
+
border-radius: 50%;
|
|
93
95
|
color: #472aff;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
.active-collapsed
|
|
98
|
+
.active-collapsed:hover {
|
|
97
99
|
background-color: #eaecff;
|
|
98
100
|
cursor: default;
|
|
99
101
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type BaseMenu = {
|
|
2
|
+
text: string;
|
|
3
|
+
url: string;
|
|
4
|
+
};
|
|
5
|
+
export type MenuItem = BaseMenu & {
|
|
6
|
+
icon?: string;
|
|
7
|
+
};
|
|
8
|
+
export type HomeItem = BaseMenu & {
|
|
9
|
+
homeIcon?: string;
|
|
10
|
+
detail?: string;
|
|
11
|
+
};
|
|
12
|
+
export type MainMenu = HomeItem & {
|
|
13
|
+
icon?: string;
|
|
14
|
+
roles?: string[];
|
|
15
|
+
children?: SidebarItem[];
|
|
16
|
+
};
|
|
17
|
+
export interface SidebarItem {
|
|
18
|
+
category: string;
|
|
19
|
+
menuItems: MenuItem[];
|
|
20
|
+
}
|
|
21
|
+
export declare const getSidebarItemsFromMenu: (items: MainMenu[], url: string, excludedRoutes?: string[]) => SidebarItem[];
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const getSidebarItemsFromMenu = (items, url, excludedRoutes) => {
|
|
2
|
+
if (excludedRoutes?.includes(url))
|
|
3
|
+
return [];
|
|
4
|
+
const matchedPath = /^\/[^/]+/.exec(url);
|
|
5
|
+
const urlValue = matchedPath?.[0] ?? '';
|
|
6
|
+
return items.find((menu) => menu.url.startsWith(urlValue))?.children ?? [];
|
|
7
|
+
};
|
package/dist/Menu/Sidebar.svelte
CHANGED
|
@@ -1,52 +1,62 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { page } from '$app/state';
|
|
2
3
|
import { fade } from 'svelte/transition';
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import MenuItemSidebar from './MenuItem.svelte';
|
|
6
|
+
import type { MenuItem, SidebarItem } from './MenuState.svelte.js';
|
|
5
7
|
|
|
6
|
-
interface
|
|
7
|
-
|
|
8
|
-
menuItems: MenuItem[];
|
|
8
|
+
interface Props {
|
|
9
|
+
sidebarItems: SidebarItem[];
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
let { sidebarItems
|
|
12
|
-
|
|
13
|
-
let
|
|
12
|
+
let { sidebarItems = [] }: Props = $props();
|
|
13
|
+
|
|
14
|
+
let hasItems: boolean = $derived(!!sidebarItems.length);
|
|
15
|
+
let isCollapsed: boolean = $state(true);
|
|
16
|
+
let selectedItem: MenuItem | undefined = $derived(
|
|
17
|
+
sidebarItems
|
|
18
|
+
.flatMap((item) => item.menuItems)
|
|
19
|
+
.find(
|
|
20
|
+
(menuItem) =>
|
|
21
|
+
page.url.pathname === menuItem.url || page.url.pathname.startsWith(menuItem.url + '/')
|
|
22
|
+
)
|
|
23
|
+
);
|
|
14
24
|
|
|
15
25
|
const toggleSidebar = () => {
|
|
16
26
|
isCollapsed = !isCollapsed;
|
|
17
27
|
};
|
|
18
28
|
</script>
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
<div class="menu">
|
|
29
|
-
<
|
|
30
|
+
{#if hasItems}
|
|
31
|
+
<aside
|
|
32
|
+
class={['side-menu', isCollapsed ? 'collapsed' : 'expanded']}
|
|
33
|
+
transition:fade|global={{ delay: 50, duration: 200 }}
|
|
34
|
+
>
|
|
35
|
+
<section class="menu-content">
|
|
36
|
+
<header class="header">
|
|
37
|
+
<section class="menu-header">
|
|
38
|
+
<div class="menu-title" class:hidden={isCollapsed}>
|
|
39
|
+
<div class="menu">
|
|
40
|
+
<h2 class="menu-span">Menu</h2>
|
|
41
|
+
</div>
|
|
30
42
|
</div>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</
|
|
37
|
-
</
|
|
38
|
-
</
|
|
39
|
-
</
|
|
40
|
-
</header>
|
|
43
|
+
<div class="category-items">
|
|
44
|
+
<button class="button" onclick={toggleSidebar}>
|
|
45
|
+
<span class="material-icons-outlined icon-span">
|
|
46
|
+
{isCollapsed ? 'menu' : 'menu_open'}
|
|
47
|
+
</span>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</section>
|
|
51
|
+
</header>
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{#each sidebarItems as element}
|
|
53
|
+
<section class="content">
|
|
54
|
+
{#each sidebarItems as item}
|
|
45
55
|
<section class="category-content">
|
|
46
|
-
<h2 class="category-span" class:hidden={isCollapsed}>{
|
|
56
|
+
<h2 class="category-span" class:hidden={isCollapsed}>{item.category}</h2>
|
|
47
57
|
<menu class="category-items-details">
|
|
48
|
-
{#if
|
|
49
|
-
{#each
|
|
58
|
+
{#if item.menuItems}
|
|
59
|
+
{#each item.menuItems as menuItem}
|
|
50
60
|
<MenuItemSidebar
|
|
51
61
|
item={menuItem}
|
|
52
62
|
{isCollapsed}
|
|
@@ -58,10 +68,10 @@
|
|
|
58
68
|
</menu>
|
|
59
69
|
</section>
|
|
60
70
|
{/each}
|
|
61
|
-
|
|
71
|
+
</section>
|
|
62
72
|
</section>
|
|
63
|
-
</
|
|
64
|
-
|
|
73
|
+
</aside>
|
|
74
|
+
{/if}
|
|
65
75
|
|
|
66
76
|
<style>
|
|
67
77
|
.side-menu {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
interface
|
|
3
|
-
|
|
4
|
-
menuItems: MenuItem[];
|
|
1
|
+
import type { SidebarItem } from './MenuState.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
sidebarItems: SidebarItem[];
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
sidebarItems: sidebarProps[];
|
|
8
|
-
};
|
|
9
|
-
declare const Sidebar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
5
|
+
declare const Sidebar: import("svelte").Component<Props, {}, "">;
|
|
10
6
|
type Sidebar = ReturnType<typeof Sidebar>;
|
|
11
7
|
export default Sidebar;
|
package/dist/Tabs/Tabs.svelte
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
2
4
|
import type { Tab } from '../index.js';
|
|
3
5
|
|
|
4
6
|
interface TabsProps {
|
|
5
7
|
tabs: Tab[];
|
|
6
8
|
activeTab?: number;
|
|
9
|
+
children?: Snippet;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
let { tabs = [], activeTab = 0 }: TabsProps = $props();
|
|
12
|
+
let { tabs = [], activeTab = 0, children }: TabsProps = $props();
|
|
10
13
|
|
|
11
14
|
const handleClick = (index: number) => (): void => {
|
|
12
15
|
activeTab = index;
|
|
@@ -36,14 +39,18 @@
|
|
|
36
39
|
|
|
37
40
|
{#each tabs as tab}
|
|
38
41
|
{#if activeTab === tab.index}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
{#if tab.component}
|
|
43
|
+
<div
|
|
44
|
+
class="tabs-content"
|
|
45
|
+
id="panel-{tab.index}"
|
|
46
|
+
role="tabpanel"
|
|
47
|
+
aria-labelledby="tab-{tab.index}"
|
|
48
|
+
>
|
|
49
|
+
{@render tab.component()}
|
|
50
|
+
</div>
|
|
51
|
+
{:else}
|
|
52
|
+
{@render children?.()}
|
|
53
|
+
{/if}
|
|
47
54
|
{/if}
|
|
48
55
|
{/each}
|
|
49
56
|
</div>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { Tab } from '../index.js';
|
|
2
3
|
interface TabsProps {
|
|
3
4
|
tabs: Tab[];
|
|
4
5
|
activeTab?: number;
|
|
6
|
+
children?: Snippet;
|
|
5
7
|
}
|
|
6
8
|
declare const Tabs: import("svelte").Component<TabsProps, {}, "">;
|
|
7
9
|
type Tabs = ReturnType<typeof Tabs>;
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ import Toaster from './Toast/Toast.svelte';
|
|
|
32
32
|
import Toggle from './Form/Toggle/Toggle.svelte';
|
|
33
33
|
import Tooltip from './Tooltip/Tooltip.svelte';
|
|
34
34
|
import Waffle from './Waffle/Waffle.svelte';
|
|
35
|
+
import AttachFile from './Form/AttachFile/AttachFileForm.svelte';
|
|
35
36
|
import { addBreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
|
|
36
37
|
import { ChipType } from './Chips/chipsState.svelte.js';
|
|
37
38
|
import { ColumnType, ImageType } from './HighlightPanel/highlightPanelState.svelte.js';
|
|
@@ -41,11 +42,12 @@ import { addToast } from './Toast/toastState.svelte';
|
|
|
41
42
|
import { setFormContext, getFormContext } from './Form/FormController/context.js';
|
|
42
43
|
import { validateSchema } from './Form/FormController/helper.js';
|
|
43
44
|
import { createZodString } from './Form/FormController/zod-validations.js';
|
|
45
|
+
import { getSidebarItemsFromMenu } from './Menu/MenuState.svelte';
|
|
44
46
|
import type { BreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
|
|
45
47
|
import type { FormError, FormContext } from './Form/FormController/types.js';
|
|
46
48
|
import type { HighlightPanelColumn } from './HighlightPanel/highlightPanelState.svelte.js';
|
|
47
|
-
import type { HomeItem } from './
|
|
48
|
-
import type { MenuItem } from './Menu/
|
|
49
|
+
import type { HomeItem } from './Menu/MenuState.svelte.js';
|
|
50
|
+
import type { MainMenu, MenuItem, SidebarItem } from './Menu/MenuState.svelte.js';
|
|
49
51
|
import type { ModalProps } from './Modal/modalState.svelte.js';
|
|
50
52
|
import type { ProgressWizardStep } from './ProgressWizard/progressWizardState.svelte.js';
|
|
51
53
|
import type { SwitcherOption } from './Switcher/switcherState.svelte.js';
|
|
@@ -53,4 +55,6 @@ import type { SelectOption } from './Form/Select/selectState.svelte.js';
|
|
|
53
55
|
import type { Tab } from './Tabs/tabsState.svelte.js';
|
|
54
56
|
import type { Toast } from './Toast/toastState.svelte';
|
|
55
57
|
import type { WaffleItem } from './Waffle/waffleState.svelte.js';
|
|
56
|
-
|
|
58
|
+
import type { AttachFileFormConfig, FileValidationCallback } from './Form/AttachFile/attachFile.svelte.js';
|
|
59
|
+
import type { NotificationProps } from './Notification/notificationState.svelte.js';
|
|
60
|
+
export { Accordion, Avatar, Breadcrumbs, Button, Card, Chips, ErrorPage, Footer, Form, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, AttachFile, addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, setFormContext, getFormContext, validateSchema, createZodString, getSidebarItemsFromMenu, ChipType, ColumnType, ImageType, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MainMenu, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type SwitcherOption, type Tab, type Toast, type WaffleItem, type FormError, type FormContext, type SidebarItem, type AttachFileFormConfig, type FileValidationCallback, type NotificationProps };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import Toaster from './Toast/Toast.svelte';
|
|
|
33
33
|
import Toggle from './Form/Toggle/Toggle.svelte';
|
|
34
34
|
import Tooltip from './Tooltip/Tooltip.svelte';
|
|
35
35
|
import Waffle from './Waffle/Waffle.svelte';
|
|
36
|
+
import AttachFile from './Form/AttachFile/AttachFileForm.svelte';
|
|
36
37
|
// State, enums, and helpers
|
|
37
38
|
import { addBreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
|
|
38
39
|
import { ChipType } from './Chips/chipsState.svelte.js';
|
|
@@ -43,10 +44,11 @@ import { addToast } from './Toast/toastState.svelte';
|
|
|
43
44
|
import { setFormContext, getFormContext } from './Form/FormController/context.js';
|
|
44
45
|
import { validateSchema } from './Form/FormController/helper.js';
|
|
45
46
|
import { createZodString } from './Form/FormController/zod-validations.js';
|
|
47
|
+
import { getSidebarItemsFromMenu } from './Menu/MenuState.svelte';
|
|
46
48
|
export {
|
|
47
49
|
// Components
|
|
48
|
-
Accordion, Avatar, Breadcrumbs, Button, Card, Chips, ErrorPage, Footer, Form, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle,
|
|
50
|
+
Accordion, Avatar, Breadcrumbs, Button, Card, Chips, ErrorPage, Footer, Form, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, AttachFile,
|
|
49
51
|
// Functions and helpers
|
|
50
|
-
addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, setFormContext, getFormContext, validateSchema, createZodString,
|
|
52
|
+
addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, setFormContext, getFormContext, validateSchema, createZodString, getSidebarItemsFromMenu,
|
|
51
53
|
// Enums
|
|
52
54
|
ChipType, ColumnType, ImageType };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import {} from '../index.js';
|
|
File without changes
|