@dargmuesli/nuxt-vio 2.0.1 → 3.0.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. package/app.config.ts +78 -34
  2. package/components/{VioApp.vue → _/VioApp.vue} +23 -5
  3. package/components/button/VioButtonColored.vue +52 -0
  4. package/components/card/VioCard.vue +19 -0
  5. package/components/card/state/VioCardState.vue +20 -0
  6. package/components/card/state/VioCardStateAlert.vue +14 -0
  7. package/components/form/VioForm.vue +84 -0
  8. package/components/form/VioFormCheckbox.vue +27 -0
  9. package/components/form/input/VioFormInput.vue +192 -0
  10. package/components/form/input/VioFormInputIconWrapper.vue +7 -0
  11. package/components/form/input/VioFormInputUrl.vue +54 -0
  12. package/components/form/input/state/VioFormInputState.vue +5 -0
  13. package/components/form/input/state/VioFormInputStateError.vue +32 -0
  14. package/components/form/input/state/VioFormInputStateInfo.vue +32 -0
  15. package/components/icon/IconArrowRight.vue +31 -0
  16. package/components/icon/IconCalendar.vue +31 -0
  17. package/components/icon/IconChatOutline.vue +27 -0
  18. package/components/icon/IconChatSolid.vue +26 -0
  19. package/components/icon/IconCheckCircle.vue +29 -0
  20. package/components/icon/IconContainer.vue +15 -0
  21. package/components/icon/IconDownload.vue +31 -0
  22. package/components/icon/IconExclamationCircle.vue +29 -0
  23. package/components/icon/IconHome.vue +31 -0
  24. package/components/icon/IconHourglass.vue +32 -0
  25. package/components/icon/IconLightbulb.vue +27 -0
  26. package/components/icon/IconLogo.vue +17 -0
  27. package/components/icon/IconMixcloud.vue +23 -0
  28. package/components/icon/IconMusic.vue +27 -0
  29. package/components/icon/IconPlay.vue +25 -0
  30. package/components/icon/IconShare.vue +27 -0
  31. package/components/layout/VioLayoutBreadcrumbs.vue +83 -0
  32. package/components/layout/VioLayoutFooter.vue +40 -0
  33. package/components/layout/VioLayoutFooterCategory.vue +17 -0
  34. package/components/layout/VioLayoutHeader.vue +98 -0
  35. package/components/layout/VioLayoutSpanList.vue +20 -0
  36. package/components/loader/indicator/VioLoaderIndicator.vue +14 -0
  37. package/components/loader/indicator/VioLoaderIndicatorPing.vue +12 -0
  38. package/components/loader/indicator/VioLoaderIndicatorSpinner.vue +24 -0
  39. package/components/{VioLegalNotice.vue → page/VioPageLegalNotice.vue} +1 -1
  40. package/components/{VioPrivacyPolicy.vue → page/VioPagePrivacyPolicy.vue} +1 -1
  41. package/composables/useAppLayout.ts +2 -2
  42. package/composables/useDateTime.ts +17 -0
  43. package/composables/useFavicons.ts +2 -2
  44. package/composables/useFireError.ts +17 -0
  45. package/composables/useGetServiceHref.ts +47 -0
  46. package/composables/useHeadDefault.ts +34 -0
  47. package/composables/useHeadLayout.ts +67 -0
  48. package/composables/useStrapiFetch.ts +10 -0
  49. package/error.vue +3 -2
  50. package/locales/de.json +7 -1
  51. package/locales/en.json +7 -1
  52. package/nuxt.config.ts +38 -2
  53. package/package.json +24 -10
  54. package/pages/legal-notice.vue +1 -1
  55. package/pages/privacy-policy.vue +1 -1
  56. package/plugins/dayjs.ts +46 -0
  57. package/plugins/i18n.ts +5 -0
  58. package/plugins/marked.ts +9 -0
  59. package/server/middleware/headers.ts +41 -10
  60. package/tailwind.config.ts +131 -8
  61. package/utils/constants.ts +9 -1
  62. package/utils/form.ts +19 -0
  63. package/utils/networking.ts +82 -0
  64. package/utils/text.ts +20 -0
  65. /package/components/{VioError.vue → _/VioError.vue} +0 -0
  66. /package/components/{VioLink.vue → _/VioLink.vue} +0 -0
  67. /package/components/{VioButton.vue → button/VioButton.vue} +0 -0
  68. /package/components/{VioLayout.vue → layout/VioLayout.vue} +0 -0
  69. /package/components/{VioHr.vue → layout/VioLayoutHr.vue} +0 -0
package/app.config.ts CHANGED
@@ -1,57 +1,101 @@
1
- import { useSeoMeta } from '@unhead/vue'
1
+ import { useServerSeoMeta } from '@unhead/vue'
2
2
 
3
3
  export default defineAppConfig({
4
- legalNotice: undefined,
5
- privacyPolicy: undefined,
6
- seoMeta: {
7
- twitterSite: '@dargmuesli',
4
+ vio: {
5
+ legalNotice: undefined,
6
+ privacyPolicy: undefined,
7
+ seoMeta: {
8
+ twitterSite: '@dargmuesli',
9
+ },
10
+ server: {
11
+ middleware: {
12
+ headers: {
13
+ csp: {
14
+ default: {
15
+ 'Cross-Origin-Opener-Policy': 'same-origin',
16
+ // 'Cross-Origin-Embedder-Policy', 'require-corp') // https://stackoverflow.com/questions/71904052/getting-notsameoriginafterdefaultedtosameoriginbycoep-error-with-helmet
17
+ 'Cross-Origin-Resource-Policy': 'same-origin',
18
+ // 'Expect-CT', 'max-age=0') // deprecated (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT)
19
+ NEL: '\'{"report_to":"default","max_age":31536000,"include_subdomains":true}\'',
20
+ 'Origin-Agent-Cluster': '?1',
21
+ 'Permissions-Policy': '',
22
+ 'Referrer-Policy': 'no-referrer',
23
+ 'Report-To':
24
+ '\'{"group":"default":"max_age":31536000:"endpoints":[{"url":"https://dargmuesli.report-uri.com/a/d/g"}]:"include_subdomains":true}\'',
25
+ 'X-Content-Type-Options': 'nosniff',
26
+ 'X-DNS-Prefetch-Control': 'off',
27
+ 'X-Download-Options': 'noopen',
28
+ 'X-Frame-Options': 'SAMEORIGIN',
29
+ 'X-Permitted-Cross-Domain-Policies': 'none',
30
+ 'X-XSS-Protection': '1; mode=block', // TODO: set back to `0` once CSP does not use `unsafe-*` anymore (https://github.com/maevsi/maevsi/issues/1047)
31
+ },
32
+ production: {
33
+ 'Strict-Transport-Security':
34
+ 'max-age=31536000; includeSubDomains; preload',
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ themeColor: '#202020',
8
41
  },
9
- themeColor: '#202020',
10
42
  })
11
43
 
12
44
  declare module 'nuxt/schema' {
13
- interface AppConfigInput {
14
- legalNotice?: {
15
- contact: {
16
- email: string
17
- }
18
- responsibility: {
19
- address: {
20
- city: string
21
- name: string
22
- street: string
23
- }
24
- }
25
- tmg: {
26
- address: {
27
- city: string
28
- name: string
29
- street: string
45
+ interface AppConfig {
46
+ vio: {
47
+ legalNotice?: {
48
+ contact: {
49
+ email: string
30
50
  }
31
- }
32
- }
33
- privacyPolicy?: {
34
- hostingCdn?: {
35
- external: {
51
+ responsibility: {
36
52
  address: {
37
53
  city: string
38
54
  name: string
39
55
  street: string
40
56
  }
41
57
  }
42
- }
43
- mandatoryInfo?: {
44
- responsible: {
58
+ tmg: {
45
59
  address: {
46
60
  city: string
47
- email: string
48
61
  name: string
49
62
  street: string
50
63
  }
51
64
  }
52
65
  }
66
+ privacyPolicy?: {
67
+ hostingCdn?: {
68
+ external: {
69
+ address: {
70
+ city: string
71
+ name: string
72
+ street: string
73
+ }
74
+ }
75
+ }
76
+ mandatoryInfo?: {
77
+ responsible: {
78
+ address: {
79
+ city: string
80
+ email: string
81
+ name: string
82
+ street: string
83
+ }
84
+ }
85
+ }
86
+ }
87
+ seoMeta?: Parameters<typeof useServerSeoMeta>[0]
88
+ server: {
89
+ middleware: {
90
+ headers: {
91
+ csp: {
92
+ default: Record<string, string>
93
+ production: Record<string, string>
94
+ }
95
+ }
96
+ }
97
+ }
98
+ themeColor?: string
53
99
  }
54
- seoMeta?: Parameters<typeof useSeoMeta>[0]
55
- themeColor?: string
56
100
  }
57
101
  }
@@ -14,19 +14,36 @@
14
14
  import { Locale } from '@dargmuesli/nuxt-cookie-control/dist/runtime/types'
15
15
 
16
16
  export interface Props {
17
- siteDescription: string
18
17
  ogImageAlt: string
19
18
  ogImageComponent?: string
19
+ siteDescription: string
20
20
  }
21
21
  const props = withDefaults(defineProps<Props>(), {
22
22
  ogImageComponent: undefined,
23
23
  })
24
+ const ogImageAltProp = toRef(() => props.ogImageAlt)
25
+ const ogImageComponentProp = toRef(() => props.ogImageComponent)
26
+ const siteDescriptionProp = toRef(() => props.siteDescription)
24
27
 
25
28
  const { locale } = useI18n()
26
29
  const cookieControl = useCookieControl()
27
30
 
28
31
  const { loadingIds, indicateLoadingDone } = useLoadingDoneIndicator('app')
29
32
 
33
+ // methods
34
+ const init = () => {
35
+ if (process.client) {
36
+ const cookieTimezone = useCookie(TIMEZONE_COOKIE_NAME, {
37
+ // default: () => undefined, // setting `default` on the client side only does not write the cookie
38
+ httpOnly: false,
39
+ sameSite: 'strict',
40
+ secure: true,
41
+ })
42
+ // @ts-ignore `tz` should be part of `$dayjs` (https://github.com/iamkun/dayjs/issues/2106)
43
+ cookieTimezone.value = $dayjs.tz.guess()
44
+ }
45
+ }
46
+
30
47
  // computations
31
48
  const isLoading = computed(() => !!loadingIds.value.length)
32
49
 
@@ -46,13 +63,14 @@ watch(
46
63
  )
47
64
 
48
65
  // initialization
66
+ init()
49
67
  updateSiteConfig({
50
- description: props.siteDescription,
68
+ description: siteDescriptionProp.value,
51
69
  })
52
70
  defineOgImage({
53
- alt: props.ogImageAlt,
54
- component: props.ogImageComponent,
55
- description: props.siteDescription,
71
+ alt: ogImageAltProp.value,
72
+ component: ogImageComponentProp.value,
73
+ description: siteDescriptionProp.value,
56
74
  })
57
75
  useAppLayout()
58
76
  useFavicons()
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <VioButton
3
+ :is-to-relative="isToRelative"
4
+ :aria-label="ariaLabel"
5
+ class="rounded-md border px-4 py-2 font-medium"
6
+ :class="
7
+ [
8
+ ...(isPrimary
9
+ ? [
10
+ 'border-transparent bg-gray-800 text-text-bright hover:bg-black dark:bg-yellow-500 dark:text-gray-800 dark:hover:bg-yellow-600',
11
+ ]
12
+ : [
13
+ 'border-gray-300 text-text-dark hover:bg-black/5 dark:border-gray-600 dark:text-text-bright dark:hover:bg-black/30',
14
+ ]),
15
+ ].join(' ')
16
+ "
17
+ :disabled="disabled"
18
+ :to="to"
19
+ :type="type"
20
+ @click="emit('click')"
21
+ >
22
+ <slot />
23
+ <template #prefix>
24
+ <slot name="prefix" />
25
+ </template>
26
+ <template #suffix>
27
+ <slot name="suffix" />
28
+ </template>
29
+ </VioButton>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ export interface Props {
34
+ ariaLabel: string
35
+ disabled?: boolean
36
+ isPrimary?: boolean
37
+ isToRelative?: boolean
38
+ to?: string
39
+ type?: 'button' | 'reset' | 'submit'
40
+ }
41
+ withDefaults(defineProps<Props>(), {
42
+ disabled: false,
43
+ isPrimary: true,
44
+ isToRelative: false,
45
+ to: undefined,
46
+ type: 'button',
47
+ })
48
+
49
+ const emit = defineEmits<{
50
+ click: []
51
+ }>()
52
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div
3
+ class="overflow-hidden rounded-lg"
4
+ :class="[backgroundColor, ...(isHigh ? ['px-4 py-6'] : ['p-4'])]"
5
+ >
6
+ <slot />
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ export interface Props {
12
+ backgroundColor?: string
13
+ isHigh?: boolean
14
+ }
15
+ withDefaults(defineProps<Props>(), {
16
+ backgroundColor: 'bg-background-brighten dark:bg-background-darken',
17
+ isHigh: false,
18
+ })
19
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <VioCard
3
+ :background-color="backgroundColor"
4
+ class="border-0 text-center font-medium text-white"
5
+ :class="{ 'rounded-none': isEdgy }"
6
+ >
7
+ <slot />
8
+ </VioCard>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ export interface Props {
13
+ backgroundColor?: string
14
+ isEdgy?: boolean
15
+ }
16
+ withDefaults(defineProps<Props>(), {
17
+ backgroundColor: undefined,
18
+ isEdgy: false,
19
+ })
20
+ </script>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <VioCardState background-color="bg-red-600" :is-edgy="isEdgy" role="alert">
3
+ <slot />
4
+ </VioCardState>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ export interface Props {
9
+ isEdgy?: boolean
10
+ }
11
+ withDefaults(defineProps<Props>(), {
12
+ isEdgy: false,
13
+ })
14
+ </script>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <form
3
+ v-if="form"
4
+ :class="[
5
+ { 'animate-shake rounded-lg border border-red-500': errors?.length },
6
+ formClass,
7
+ ]"
8
+ novalidate
9
+ @submit="(e) => emit('submit', e)"
10
+ >
11
+ <VioCard class="flex flex-col" is-high>
12
+ <div class="flex flex-col min-h-0 overflow-y-auto gap-6">
13
+ <slot />
14
+ <div class="flex flex-col items-center justify-between">
15
+ <VioButtonColored
16
+ :aria-label="submitName || t('submit')"
17
+ :class="{
18
+ 'animate-shake': form.$error,
19
+ }"
20
+ type="submit"
21
+ @click="emit('click')"
22
+ >
23
+ {{ submitName || t('submit') }}
24
+ <template #prefix>
25
+ <slot name="submit-icon" />
26
+ </template>
27
+ </VioButtonColored>
28
+ <VioFormInputStateError v-if="form.$error" class="mt-2">
29
+ {{ t('globalValidationFailed') }}
30
+ </VioFormInputStateError>
31
+ </div>
32
+ <VioCardStateAlert v-if="errorMessages?.length" class="my-4">
33
+ <VioLayoutSpanList :span="errorMessages" />
34
+ </VioCardStateAlert>
35
+ <div v-if="$slots.assistance" class="flex justify-center">
36
+ <slot name="assistance" />
37
+ </div>
38
+ </div>
39
+ </VioCard>
40
+ </form>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import type { BaseValidation } from '@vuelidate/core'
45
+
46
+ import type { BackendError } from '~/../types/api'
47
+
48
+ export interface Props {
49
+ errors?: BackendError[]
50
+ errorsPgIds?: Record<string, string>
51
+ form: BaseValidation
52
+ formClass?: string
53
+ isFormSent?: boolean
54
+ submitName?: string
55
+ }
56
+ const props = withDefaults(defineProps<Props>(), {
57
+ errors: undefined,
58
+ errorsPgIds: undefined,
59
+ formClass: undefined,
60
+ isFormSent: false,
61
+ submitName: undefined,
62
+ })
63
+
64
+ const emit = defineEmits<{
65
+ click: []
66
+ submit: [event: Event]
67
+ }>()
68
+
69
+ const { t } = useI18n()
70
+
71
+ // computations
72
+ const errorMessages = computed(() =>
73
+ props.errors
74
+ ? getCombinedErrorMessages(props.errors, props.errorsPgIds)
75
+ : undefined,
76
+ )
77
+ </script>
78
+
79
+ <i18n lang="yaml">
80
+ de:
81
+ submit: Absenden
82
+ en:
83
+ submit: Submit
84
+ </i18n>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div>
3
+ <input
4
+ :id="`input-${formKey}`"
5
+ class="rounded"
6
+ type="checkbox"
7
+ :checked="value"
8
+ @change="emit('change', ($event.target as HTMLInputElement).checked)"
9
+ />
10
+ <label class="pl-2" :for="`input-${formKey}`"><slot /></label>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ export interface Props {
16
+ formKey?: string
17
+ value?: boolean
18
+ }
19
+ withDefaults(defineProps<Props>(), {
20
+ formKey: undefined,
21
+ value: undefined,
22
+ })
23
+
24
+ const emit = defineEmits<{
25
+ change: [change: boolean]
26
+ }>()
27
+ </script>
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ :class="{
5
+ 'form-input-success': success,
6
+ 'form-input-warning': warning,
7
+ 'form-input-error': value?.$error,
8
+ }"
9
+ class="flex-wrap md:flex md:items-center"
10
+ >
11
+ <div class="mb-1 md:mb-0 md:w-1/3 md:pr-4 md:text-right">
12
+ <label
13
+ class="inline-flex items-baseline gap-2 font-semibold md:flex-col md:items-end md:gap-0"
14
+ :class="{
15
+ 'form-input-success': success,
16
+ 'form-input-warning': warning,
17
+ 'form-input-error': value?.$error,
18
+ }"
19
+ :for="idLabel"
20
+ >
21
+ <span>{{ title }}</span>
22
+ <span
23
+ class="text-xs font-medium text-gray-500 dark:text-gray-400 md:text-right"
24
+ >
25
+ <span v-if="isRequired">
26
+ {{ t('required') }}
27
+ </span>
28
+ <span v-if="isOptional">
29
+ {{ t('optional') }}
30
+ </span>
31
+ </span>
32
+ </label>
33
+ </div>
34
+ <div class="flex md:mt-1 md:w-2/3">
35
+ <div class="relative min-w-0 grow">
36
+ <slot v-if="$slots.default" />
37
+ <input
38
+ v-else
39
+ :id="idLabel"
40
+ class="form-input"
41
+ :class="{
42
+ 'rounded-r-none': $slots.icon,
43
+ }"
44
+ :disabled="isDisabled"
45
+ :placeholder="placeholder"
46
+ :readonly="isReadonly"
47
+ :type="type"
48
+ :value="valueFormatter(value?.$model as string)"
49
+ @input="emit('input', ($event.target as HTMLInputElement)?.value)"
50
+ @click="emit('click')"
51
+ />
52
+ <div v-if="validationProperty && isValidatable">
53
+ <VioFormInputIconWrapper v-if="validationProperty.$pending">
54
+ <IconHourglass
55
+ class="text-blue-600"
56
+ :title="t('globalLoading')"
57
+ />
58
+ </VioFormInputIconWrapper>
59
+ <VioFormInputIconWrapper
60
+ v-else-if="
61
+ validationProperty.$model && !validationProperty.$invalid
62
+ "
63
+ >
64
+ <IconCheckCircle class="text-green-600" :title="t('valid')" />
65
+ </VioFormInputIconWrapper>
66
+ <VioFormInputIconWrapper
67
+ v-else-if="
68
+ validationProperty.$model && validationProperty.$invalid
69
+ "
70
+ >
71
+ <IconExclamationCircle
72
+ class="text-red-600"
73
+ :title="t('validNot')"
74
+ />
75
+ </VioFormInputIconWrapper>
76
+ </div>
77
+ </div>
78
+ <span
79
+ v-if="$slots.icon"
80
+ class="inline-flex cursor-pointer items-center rounded-r-md border border-l-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-600"
81
+ @click="emit('icon')"
82
+ >
83
+ <slot name="icon" />
84
+ </span>
85
+ </div>
86
+ <div class="md:w-1/3" />
87
+ <div class="md:w-2/3">
88
+ <slot name="stateSuccess" />
89
+ </div>
90
+ <div class="md:w-1/3" />
91
+ <div class="md:w-2/3">
92
+ <slot name="stateInfo" />
93
+ <VioFormInputStateInfo v-if="value?.$pending">
94
+ {{ t('globalLoading') }}
95
+ </VioFormInputStateInfo>
96
+ </div>
97
+ <div class="md:w-1/3" />
98
+ <div class="md:w-2/3">
99
+ <slot name="stateWarning" />
100
+ </div>
101
+ <div class="md:w-1/3" />
102
+ <div class="md:w-2/3">
103
+ <slot name="stateError" />
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </template>
108
+
109
+ <script setup lang="ts">
110
+ import type { BaseValidation } from '@vuelidate/core'
111
+ import { consola } from 'consola'
112
+
113
+ export interface Props {
114
+ isDisabled?: boolean
115
+ isOptional?: boolean
116
+ isReadonly?: boolean
117
+ isRequired?: boolean
118
+ isValidatable?: boolean
119
+ idLabel?: string
120
+ placeholder?: string
121
+ success?: boolean
122
+ title?: string
123
+ type?: string
124
+ validationProperty?: BaseValidation
125
+ value?: BaseValidation
126
+ valueFormatter?: (x?: string) => typeof x | undefined
127
+ warning?: boolean
128
+ }
129
+ const props = withDefaults(defineProps<Props>(), {
130
+ isDisabled: false,
131
+ isOptional: false,
132
+ isReadonly: false,
133
+ isRequired: false,
134
+ isValidatable: false,
135
+ idLabel: undefined,
136
+ placeholder: undefined,
137
+ success: false,
138
+ title: undefined,
139
+ type: undefined,
140
+ validationProperty: undefined,
141
+ value: undefined,
142
+ valueFormatter: (x?: string) => x,
143
+ warning: false,
144
+ })
145
+ const typeProp = toRef(() => props.type)
146
+
147
+ const emit = defineEmits<{
148
+ icon: []
149
+ input: [input: string]
150
+ click: []
151
+ }>()
152
+
153
+ const { t } = useI18n()
154
+
155
+ // initialization
156
+ if (
157
+ !props.placeholder &&
158
+ typeProp.value &&
159
+ ![
160
+ 'checkbox',
161
+ 'datetime-local',
162
+ 'number',
163
+ 'select',
164
+ 'textarea',
165
+ 'tiptap',
166
+ 'radio',
167
+ ].includes(typeProp.value)
168
+ ) {
169
+ consola.warn(`placeholder is missing for ${props.idLabel}!`)
170
+ }
171
+
172
+ if (
173
+ !props.value &&
174
+ typeProp.value &&
175
+ !['checkbox', 'select'].includes(typeProp.value)
176
+ ) {
177
+ consola.warn(`value is missing for ${props.idLabel}!`)
178
+ }
179
+ </script>
180
+
181
+ <i18n lang="yaml">
182
+ de:
183
+ optional: optional
184
+ required: Pflichtfeld
185
+ valid: Gültig
186
+ validNot: Ungültig
187
+ en:
188
+ optional: optional
189
+ required: required
190
+ valid: valid
191
+ validNot: invalid
192
+ </i18n>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <span class="absolute right-0 top-1/2 -translate-y-1/2 px-3">
3
+ <div class="rounded-full bg-gray-50">
4
+ <slot />
5
+ </div>
6
+ </span>
7
+ </template>
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <VioFormInput
3
+ v-if="formInput"
4
+ :is-optional="isOptional"
5
+ :id-label="`input-${id}`"
6
+ :placeholder="t('globalPlaceholderUrl')"
7
+ :title="t('url')"
8
+ type="url"
9
+ :value="formInput"
10
+ @input="emit('input', $event)"
11
+ >
12
+ <template #stateError>
13
+ <VioFormInputStateError
14
+ :form-input="formInput"
15
+ validation-property="maxLength"
16
+ >
17
+ {{ t('globalValidationLength') }}
18
+ </VioFormInputStateError>
19
+ <VioFormInputStateError
20
+ :form-input="formInput"
21
+ validation-property="formatUrlHttps"
22
+ >
23
+ {{ t('globalValidationFormatUrlHttps') }}
24
+ </VioFormInputStateError>
25
+ </template>
26
+ </VioFormInput>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import type { BaseValidation } from '@vuelidate/core'
31
+
32
+ export interface Props {
33
+ formInput: BaseValidation
34
+ id?: string
35
+ isOptional?: boolean
36
+ }
37
+ withDefaults(defineProps<Props>(), {
38
+ id: 'phone-number',
39
+ isOptional: false,
40
+ })
41
+
42
+ const emit = defineEmits<{
43
+ input: [event: string]
44
+ }>()
45
+
46
+ const { t } = useI18n()
47
+ </script>
48
+
49
+ <i18n lang="yaml">
50
+ de:
51
+ url: Weblink
52
+ en:
53
+ url: Weblink
54
+ </i18n>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="flex items-center gap-1">
3
+ <slot />
4
+ </div>
5
+ </template>