@dargmuesli/nuxt-vio 2.0.1 → 3.0.0-beta.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.
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>