@functionalcms/svelte-components 5.4.0 → 5.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/css/functional.css +45 -45
- package/dist/components/Icon.svelte +16 -16
- package/dist/components/content/Markdown.svelte +10 -10
- package/dist/components/form/AntiBot.svelte +12 -12
- package/dist/components/form/Dropzone.svelte +180 -180
- package/dist/components/form/SmartForm.svelte +269 -271
- package/dist/components/form/SmartForm.svelte.d.ts +1 -1
- package/dist/components/head/Font.svelte +20 -20
- package/dist/components/head/Meta.svelte +28 -28
- package/dist/components/head/Tracker.svelte +18 -18
- package/dist/components/integrations/EasyTools.svelte +30 -30
- package/dist/components/layouts/DefaultLayout.svelte +117 -117
- package/dist/components/layouts/MenuLayout.svelte +79 -79
- package/dist/components/layouts/SimpleLayout.svelte +19 -19
- package/dist/components/presentation/FlexBox.svelte +73 -73
- package/dist/components/presentation/ImageCompare.svelte +180 -180
- package/dist/components/presentation/Logo.svelte +37 -37
- package/dist/components/presentation/StyleBox.svelte +17 -17
- package/dist/components/presentation/Well.svelte +17 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +75 -75
|
@@ -1,271 +1,269 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
type Field,
|
|
4
|
-
type SelectField,
|
|
5
|
-
type CollectionField,
|
|
6
|
-
type TextareaField,
|
|
7
|
-
type InputField,
|
|
8
|
-
type FilePickerField,
|
|
9
|
-
type PhoneInputField,
|
|
10
|
-
type TimePickerField,
|
|
11
|
-
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
>
|
|
104
|
-
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
/>
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
{
|
|
200
|
-
|
|
201
|
-
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
|
|
206
|
-
{
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
{@render
|
|
226
|
-
{:else if field.typeOfField === '
|
|
227
|
-
{@render
|
|
228
|
-
{:else if field.typeOfField === '
|
|
229
|
-
{@render
|
|
230
|
-
{:else if field.typeOfField === '
|
|
231
|
-
{@render
|
|
232
|
-
{:else if field.typeOfField === '
|
|
233
|
-
{@render
|
|
234
|
-
{:else if field.typeOfField === '
|
|
235
|
-
{@render
|
|
236
|
-
{:else if field.typeOfField === '
|
|
237
|
-
{@render
|
|
238
|
-
{:else if field.typeOfField === '
|
|
239
|
-
{@render
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{
|
|
254
|
-
>
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
</form>
|
|
271
|
-
{/if}
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
type Field,
|
|
4
|
+
type SelectField,
|
|
5
|
+
type CollectionField,
|
|
6
|
+
type TextareaField,
|
|
7
|
+
type InputField,
|
|
8
|
+
type FilePickerField,
|
|
9
|
+
type PhoneInputField,
|
|
10
|
+
type TimePickerField,
|
|
11
|
+
type ButtonConfig,
|
|
12
|
+
type HiddenField,
|
|
13
|
+
type DatePickerField,
|
|
14
|
+
SubmitResult,
|
|
15
|
+
} from './form.js';
|
|
16
|
+
import {
|
|
17
|
+
Button,
|
|
18
|
+
Label,
|
|
19
|
+
Fileupload,
|
|
20
|
+
Select,
|
|
21
|
+
MultiSelect,
|
|
22
|
+
Textarea,
|
|
23
|
+
Toggle,
|
|
24
|
+
Input,
|
|
25
|
+
FloatingLabelInput,
|
|
26
|
+
Timepicker,
|
|
27
|
+
PhoneInput,
|
|
28
|
+
Datepicker
|
|
29
|
+
} from 'flowbite-svelte';
|
|
30
|
+
import { fade } from 'svelte/transition';
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
action: string;
|
|
34
|
+
class?: string;
|
|
35
|
+
successMessage?: string;
|
|
36
|
+
fields?: Array<Field>;
|
|
37
|
+
onMessageSubmitted?: (result: SubmitResult) => void;
|
|
38
|
+
buttons: Array<ButtonConfig>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
action,
|
|
43
|
+
fields = [],
|
|
44
|
+
class: css = '',
|
|
45
|
+
successMessage,
|
|
46
|
+
onMessageSubmitted,
|
|
47
|
+
buttons = [
|
|
48
|
+
{
|
|
49
|
+
label: 'Submit',
|
|
50
|
+
type: 'submit',
|
|
51
|
+
action: undefined
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}: Props = $props();
|
|
55
|
+
|
|
56
|
+
let form: HTMLFormElement;
|
|
57
|
+
let showMessage = $state(false);
|
|
58
|
+
let isSendingMessage = $state(false);
|
|
59
|
+
let loading = $state(false);
|
|
60
|
+
|
|
61
|
+
const handleSubmit = async (event: Event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
loading = true;
|
|
64
|
+
try {
|
|
65
|
+
if (!isSendingMessage) {
|
|
66
|
+
isSendingMessage = true;
|
|
67
|
+
const formData = new FormData(form);
|
|
68
|
+
const response = await fetch(action, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: formData
|
|
71
|
+
});
|
|
72
|
+
showMessage = response.status === 200;
|
|
73
|
+
onMessageSubmitted?.(showMessage ? SubmitResult.Success : SubmitResult.Error);
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
showMessage = false;
|
|
76
|
+
}, 3000);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Error submitting form:', error);
|
|
80
|
+
} finally {
|
|
81
|
+
isSendingMessage = false;
|
|
82
|
+
}
|
|
83
|
+
loading = false;
|
|
84
|
+
};
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
{#snippet renderInput(field: InputField)}
|
|
88
|
+
{#if field.isFloat}
|
|
89
|
+
<FloatingLabelInput
|
|
90
|
+
id={field.id}
|
|
91
|
+
name={field.id}
|
|
92
|
+
required={field.required}
|
|
93
|
+
readonly={field.readonly}
|
|
94
|
+
disabled={field.disabled}
|
|
95
|
+
class={field.class}
|
|
96
|
+
size={field.size == 'sm' ? 'small' : 'default'}
|
|
97
|
+
type={field.type}
|
|
98
|
+
variant={field.floatVariant}
|
|
99
|
+
placeholder={field.placeholder}
|
|
100
|
+
bind:value={field.value}
|
|
101
|
+
>
|
|
102
|
+
{field.label}
|
|
103
|
+
</FloatingLabelInput>
|
|
104
|
+
{:else}
|
|
105
|
+
<Label for={field.id} class={field.labelClass}>{field.label}</Label>
|
|
106
|
+
<Input
|
|
107
|
+
id={field.id}
|
|
108
|
+
name={field.id}
|
|
109
|
+
required={field.required}
|
|
110
|
+
readonly={field.readonly}
|
|
111
|
+
disabled={field.disabled}
|
|
112
|
+
class={field.class}
|
|
113
|
+
size={field.size}
|
|
114
|
+
type={field.type}
|
|
115
|
+
placeholder={field.placeholder}
|
|
116
|
+
bind:value={field.value}
|
|
117
|
+
/>
|
|
118
|
+
{/if}
|
|
119
|
+
{/snippet}
|
|
120
|
+
|
|
121
|
+
{#snippet renderTextarea(field: TextareaField)}
|
|
122
|
+
<Label for={field.id} class={field.labelClass}>{field.label}</Label>
|
|
123
|
+
<Textarea
|
|
124
|
+
id={field.id}
|
|
125
|
+
name={field.id}
|
|
126
|
+
required={field.required}
|
|
127
|
+
readonly={field.readonly}
|
|
128
|
+
disabled={field.disabled}
|
|
129
|
+
class={field.class}
|
|
130
|
+
rows={field.rows}
|
|
131
|
+
cols={field.cols}
|
|
132
|
+
placeholder={field.placeholder}
|
|
133
|
+
bind:value={field.value}
|
|
134
|
+
/>
|
|
135
|
+
{/snippet}
|
|
136
|
+
|
|
137
|
+
{#snippet renderSelect(field: SelectField)}
|
|
138
|
+
<Label class={field.labelClass}
|
|
139
|
+
>{field.label}
|
|
140
|
+
{#if field.type === 'single'}
|
|
141
|
+
<Select
|
|
142
|
+
id={field.id}
|
|
143
|
+
name={field.id}
|
|
144
|
+
required={field.required}
|
|
145
|
+
readonly={field.readonly}
|
|
146
|
+
disabled={field.disabled}
|
|
147
|
+
class={field.class}
|
|
148
|
+
items={field.options}
|
|
149
|
+
bind:value={field.value}
|
|
150
|
+
/>
|
|
151
|
+
{:else}
|
|
152
|
+
<MultiSelect class={field.class} items={field.options} bind:value={field.value} />
|
|
153
|
+
{/if}
|
|
154
|
+
</Label>
|
|
155
|
+
{/snippet}
|
|
156
|
+
|
|
157
|
+
{#snippet renderCollection(field: CollectionField)}
|
|
158
|
+
{#if field.type === 'checkbox'}{:else if field.type === 'radio'}{:else if field.type === 'toggle'}
|
|
159
|
+
<Toggle
|
|
160
|
+
id={field.id}
|
|
161
|
+
name={field.id}
|
|
162
|
+
required={field.required}
|
|
163
|
+
readonly={field.readonly}
|
|
164
|
+
disabled={field.disabled}
|
|
165
|
+
class={field.class}
|
|
166
|
+
checked={field.checked}
|
|
167
|
+
>
|
|
168
|
+
{field.label}
|
|
169
|
+
</Toggle>
|
|
170
|
+
{/if}
|
|
171
|
+
{/snippet}
|
|
172
|
+
|
|
173
|
+
{#snippet renderFile(field: FilePickerField)}
|
|
174
|
+
<Label class={field.labelClass}>{field.label}</Label>
|
|
175
|
+
<Fileupload
|
|
176
|
+
id={field.id}
|
|
177
|
+
name={field.id}
|
|
178
|
+
required={field.required}
|
|
179
|
+
readonly={field.readonly}
|
|
180
|
+
disabled={field.disabled}
|
|
181
|
+
class={field.class}
|
|
182
|
+
multiple={field.multiple}
|
|
183
|
+
/>
|
|
184
|
+
{/snippet}
|
|
185
|
+
|
|
186
|
+
{#snippet renderPhoneInput(field: PhoneInputField)}
|
|
187
|
+
<Label class={field.labelClass}>{field.label}</Label>
|
|
188
|
+
<PhoneInput
|
|
189
|
+
id={field.id}
|
|
190
|
+
name={field.id}
|
|
191
|
+
required={field.required}
|
|
192
|
+
readonly={field.readonly}
|
|
193
|
+
disabled={field.disabled}
|
|
194
|
+
class={field.class}
|
|
195
|
+
placeholder={field.placeholder}
|
|
196
|
+
/>
|
|
197
|
+
{/snippet}
|
|
198
|
+
|
|
199
|
+
{#snippet renderTimePicker(field: TimePickerField)}
|
|
200
|
+
<Label for={field.id} class={field.labelClass}>{field.label}</Label>
|
|
201
|
+
<Timepicker id={field.id} required={field.required} disabled={field.disabled} />
|
|
202
|
+
{/snippet}
|
|
203
|
+
|
|
204
|
+
{#snippet renderDatePicker(field: DatePickerField)}
|
|
205
|
+
<Label for={field.id} class={field.labelClass}>{field.label}</Label>
|
|
206
|
+
<Datepicker required={field.required} bind:value={field.value} />
|
|
207
|
+
<input type="hidden" name={field.id} bind:value={field.value} />
|
|
208
|
+
{/snippet}
|
|
209
|
+
|
|
210
|
+
{#snippet renderHidden(field: HiddenField)}
|
|
211
|
+
<input type="hidden" id={field.id} name={field.id} bind:value={field.value} />
|
|
212
|
+
{/snippet}
|
|
213
|
+
|
|
214
|
+
{#if showMessage}
|
|
215
|
+
<div class="alert alert-success" transition:fade={{ duration: 2000 }}>
|
|
216
|
+
{successMessage}
|
|
217
|
+
</div>
|
|
218
|
+
{:else}
|
|
219
|
+
<form method="POST" {action} class={css} bind:this={form}>
|
|
220
|
+
<fieldset>
|
|
221
|
+
{#each fields as field}
|
|
222
|
+
{#if field.typeOfField === 'input'}
|
|
223
|
+
{@render renderInput(field)}
|
|
224
|
+
{:else if field.typeOfField === 'textarea'}
|
|
225
|
+
{@render renderTextarea(field)}
|
|
226
|
+
{:else if field.typeOfField === 'select'}
|
|
227
|
+
{@render renderSelect(field)}
|
|
228
|
+
{:else if field.typeOfField === 'collection'}
|
|
229
|
+
{@render renderCollection(field)}
|
|
230
|
+
{:else if field.typeOfField === 'file'}
|
|
231
|
+
{@render renderFile(field)}
|
|
232
|
+
{:else if field.typeOfField === 'phoneInput'}
|
|
233
|
+
{@render renderPhoneInput(field)}
|
|
234
|
+
{:else if field.typeOfField === 'timepicker'}
|
|
235
|
+
{@render renderTimePicker(field)}
|
|
236
|
+
{:else if field.typeOfField === 'hidden'}
|
|
237
|
+
{@render renderHidden(field)}
|
|
238
|
+
{:else if field.typeOfField === 'date'}
|
|
239
|
+
{@render renderDatePicker(field)}
|
|
240
|
+
{/if}
|
|
241
|
+
{/each}
|
|
242
|
+
|
|
243
|
+
<div class="buttons">
|
|
244
|
+
{#each buttons as button}
|
|
245
|
+
{#if button.type === 'submit'}
|
|
246
|
+
<Button
|
|
247
|
+
type="button"
|
|
248
|
+
class={button.class}
|
|
249
|
+
color={button.color}
|
|
250
|
+
onclick={handleSubmit}
|
|
251
|
+
{loading}
|
|
252
|
+
>
|
|
253
|
+
{button.label}
|
|
254
|
+
</Button>
|
|
255
|
+
{:else}
|
|
256
|
+
<Button
|
|
257
|
+
type={button.type}
|
|
258
|
+
class={button.class}
|
|
259
|
+
color={button.color}
|
|
260
|
+
onclick={button.action}
|
|
261
|
+
>
|
|
262
|
+
{button.label}
|
|
263
|
+
</Button>
|
|
264
|
+
{/if}
|
|
265
|
+
{/each}
|
|
266
|
+
</div>
|
|
267
|
+
</fieldset>
|
|
268
|
+
</form>
|
|
269
|
+
{/if}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
interface Props {
|
|
3
|
-
fonts: Array<{ name: string; values: string }>;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
let { fonts = [] }: Partial<Props> = $props();
|
|
7
|
-
|
|
8
|
-
const fontQuery = $derived(() =>
|
|
9
|
-
fonts.map((font) => `family=${font.name}:${font.values}`).join('&')
|
|
10
|
-
);
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<svelte:head>
|
|
14
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
15
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
|
16
|
-
<link
|
|
17
|
-
rel="stylesheet"
|
|
18
|
-
href={`https://fonts.googleapis.com/css2?${fontQuery}&display=swap`}
|
|
19
|
-
/>
|
|
20
|
-
</svelte:head>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
fonts: Array<{ name: string; values: string }>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let { fonts = [] }: Partial<Props> = $props();
|
|
7
|
+
|
|
8
|
+
const fontQuery = $derived(() =>
|
|
9
|
+
fonts.map((font) => `family=${font.name}:${font.values}`).join('&')
|
|
10
|
+
);
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<svelte:head>
|
|
14
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
15
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
|
|
16
|
+
<link
|
|
17
|
+
rel="stylesheet"
|
|
18
|
+
href={`https://fonts.googleapis.com/css2?${fontQuery}&display=swap`}
|
|
19
|
+
/>
|
|
20
|
+
</svelte:head>
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
interface MetaProps {
|
|
3
|
-
title: string;
|
|
4
|
-
description: string;
|
|
5
|
-
keywords: string;
|
|
6
|
-
robots: string;
|
|
7
|
-
charset: string;
|
|
8
|
-
viewport: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let {
|
|
12
|
-
title = '',
|
|
13
|
-
description = '',
|
|
14
|
-
keywords = '',
|
|
15
|
-
robots = 'index, follow',
|
|
16
|
-
charset = 'UTF-8',
|
|
17
|
-
viewport = 'width=device-width, initial-scale=1.0'
|
|
18
|
-
}: Partial<MetaProps> = $props();
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<svelte:head>
|
|
22
|
-
<title>{title}</title>
|
|
23
|
-
<meta name="description" content={description} />
|
|
24
|
-
<meta name="keywords" content={keywords} />
|
|
25
|
-
<meta name="robots" content={robots} />
|
|
26
|
-
<meta name="viewport" content={viewport} />
|
|
27
|
-
<meta {charset} />
|
|
28
|
-
</svelte:head>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface MetaProps {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
keywords: string;
|
|
6
|
+
robots: string;
|
|
7
|
+
charset: string;
|
|
8
|
+
viewport: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
title = '',
|
|
13
|
+
description = '',
|
|
14
|
+
keywords = '',
|
|
15
|
+
robots = 'index, follow',
|
|
16
|
+
charset = 'UTF-8',
|
|
17
|
+
viewport = 'width=device-width, initial-scale=1.0'
|
|
18
|
+
}: Partial<MetaProps> = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<svelte:head>
|
|
22
|
+
<title>{title}</title>
|
|
23
|
+
<meta name="description" content={description} />
|
|
24
|
+
<meta name="keywords" content={keywords} />
|
|
25
|
+
<meta name="robots" content={robots} />
|
|
26
|
+
<meta name="viewport" content={viewport} />
|
|
27
|
+
<meta {charset} />
|
|
28
|
+
</svelte:head>
|