create-nuxt-base 0.2.2 → 0.2.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/nuxt-base-template/.env.example +0 -1
  3. package/nuxt-base-template/package.json +2 -2
  4. package/nuxt-base-template/src/app.vue +0 -3
  5. package/nuxt-base-template/src/pages/index.vue +0 -30
  6. package/package.json +1 -1
  7. package/nuxt-base-template/src/components/SocialMediaBubble.vue +0 -16
  8. package/nuxt-base-template/src/components/base/BaseAccordion.vue +0 -55
  9. package/nuxt-base-template/src/components/base/BaseButton.vue +0 -103
  10. package/nuxt-base-template/src/components/base/BaseContainer.vue +0 -5
  11. package/nuxt-base-template/src/components/base/BaseContextMenuContainer.vue +0 -61
  12. package/nuxt-base-template/src/components/base/BaseInfinityList.vue +0 -34
  13. package/nuxt-base-template/src/components/base/BaseProgressbar.vue +0 -64
  14. package/nuxt-base-template/src/components/base/BaseToggle.vue +0 -27
  15. package/nuxt-base-template/src/components/form/FormInput.vue +0 -34
  16. package/nuxt-base-template/src/components/form/FormPassword.vue +0 -47
  17. package/nuxt-base-template/src/components/form/FormSelect.vue +0 -40
  18. package/nuxt-base-template/src/components/form/FormSubmit.vue +0 -18
  19. package/nuxt-base-template/src/components/form/FormTextarea.vue +0 -36
  20. package/nuxt-base-template/src/components/form/FormToggle.vue +0 -27
  21. package/nuxt-base-template/src/components/modal/Modal.vue +0 -48
  22. package/nuxt-base-template/src/components/modal/ModalConfirm.vue +0 -38
  23. package/nuxt-base-template/src/components/modal/ModalContainer.vue +0 -7
  24. package/nuxt-base-template/src/components/modal/ModalInfo.vue +0 -22
  25. package/nuxt-base-template/src/components/modal/ModalShare.vue +0 -63
  26. package/nuxt-base-template/src/components/notification/Notification.vue +0 -82
  27. package/nuxt-base-template/src/components/notification/NotificationContainer.vue +0 -40
  28. package/nuxt-base-template/src/components/pwa/pwa-install-banner.vue +0 -224
  29. /package/nuxt-base-template/src/components/{transition → Transition}/TransitionFade.vue +0 -0
  30. /package/nuxt-base-template/src/components/{transition → Transition}/TransitionFadeScale.vue +0 -0
  31. /package/nuxt-base-template/src/components/{transition → Transition}/TransitionSlide.vue +0 -0
  32. /package/nuxt-base-template/src/components/{transition → Transition}/TransitionSlideBottom.vue +0 -0
  33. /package/nuxt-base-template/src/components/{transition → Transition}/TransitionSlideRevert.vue +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.2.3](https://github.com/lenneTech/nuxt-base-starter/compare/v0.2.2...v0.2.3) (2024-02-20)
6
+
7
+
8
+ ### Features
9
+
10
+ * Remove components because of new cli components command ([6380979](https://github.com/lenneTech/nuxt-base-starter/commit/6380979ba7ca46ce6058f4e1dea4e511d1d0ea9d))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * fix scripts ([107aa14](https://github.com/lenneTech/nuxt-base-starter/commit/107aa1459f327543bfad03b236f9dc6cdbbc52f6))
16
+
5
17
  ### [0.2.2](https://github.com/lenneTech/nuxt-base-starter/compare/v0.2.1...v0.2.2) (2024-02-19)
6
18
 
7
19
 
@@ -5,4 +5,3 @@ WEB_PUSH_KEY=
5
5
  API_SCHEMA=../api/schema.gql
6
6
  STORAGE_PREFIX=base-dev
7
7
  GENERATE_TYPES=0
8
-
@@ -5,8 +5,8 @@
5
5
  "init": "npm install",
6
6
  "reinit": "rm -rf node_modules && rm -rf package-lock.json && yes | npx nuxt cleanup && npm cache clean --force && npm i",
7
7
  "build": "nuxt build",
8
- "build:develop": "npm run build",
9
- "build:test": "npm run build",
8
+ "build:develop": "nuxt build",
9
+ "build:test": "nuxt build",
10
10
  "start": "nuxt dev",
11
11
  "start:develop": "node .output/server/index.mjs",
12
12
  "start:test": "node .output/server/index.mjs",
@@ -1,9 +1,6 @@
1
1
  <template>
2
2
  <div>
3
3
  <NuxtLayout>
4
- <ModalContainer />
5
- <NotificationContainer />
6
- <BaseContextMenuContainer />
7
4
  <NuxtPage />
8
5
  </NuxtLayout>
9
6
  </div>
@@ -1,35 +1,5 @@
1
- <script setup lang="ts">
2
- import FormTextarea from '~/components/form/FormTextarea.vue';
3
-
4
- const isSubmitting = ref(false);
5
- </script>
6
-
7
1
  <template>
8
2
  <div class="flex h-screen items-center justify-center flex-col">
9
3
  <h1 class="font-extrabold text-transparent text-8xl bg-clip-text bg-gradient-to-r from-purple-400 to-pink-600">Lenne Nuxt Starter</h1>
10
-
11
- <h2 class="mt-5">DEMO</h2>
12
- <FormInput name="email" type="email" label="E-Mail" placeholder="max.mustermann@test.de" />
13
- <FormTextarea name="description" label="Description" placeholder="..." />
14
- <FormPassword name="password" label="Password" placeholder="Password" />
15
- <FormSelect
16
- name="select"
17
- label="Select"
18
- placeholder="placeholder"
19
- :options="[
20
- { label: 'Option 1', value: 1 },
21
- { label: 'Option 2', value: 2 },
22
- { label: 'Option 3', value: 3 },
23
- ]"
24
- />
25
- <FormToggle name="toggle" label="Toggle" />
26
- <div class="flex items-center gap-3">
27
- <FormSubmit label="Save" :is-submitting="isSubmitting" @click="isSubmitting = true" />
28
- <BaseButton size="sm" :loading="isSubmitting" @click="isSubmitting = true">Submit</BaseButton>
29
- <BaseButton size="lg" :loading="isSubmitting" @click="isSubmitting = true">Submit</BaseButton>
30
- <BaseButton size="md" :loading="isSubmitting" appearance="outline" @click="isSubmitting = true">Submit</BaseButton>
31
- <BaseButton size="md" :loading="isSubmitting" appearance="none" @click="isSubmitting = true">Submit</BaseButton>
32
- <BaseButton size="md" color="danger" @click="isSubmitting = false">Reset</BaseButton>
33
- </div>
34
4
  </div>
35
5
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nuxt-base",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint, @lenne.tech/nuxt-base, Unit Tests, Cypress etc.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,16 +0,0 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- icon: string;
4
- title: string;
5
- bgColor: string;
6
- }>();
7
- </script>
8
-
9
- <template>
10
- <button class="flex flex-col items-center group">
11
- <span class="rounded-full w-14 h-14 flex items-center justify-center transition-all duration-300 group-hover:-translate-y-1" :style="{ 'background-color': bgColor }">
12
- <span class="text-2xl text-white" :class="icon"></span>
13
- </span>
14
- <small class="text-sm font-light mt-2">{{ title }}</small>
15
- </button>
16
- </template>
@@ -1,55 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref } from 'vue';
3
- import { useElementSize } from '@vueuse/core';
4
-
5
- const props = withDefaults(
6
- defineProps<{
7
- expanded?: boolean;
8
- }>(),
9
- {
10
- expanded: false,
11
- }
12
- );
13
-
14
- const show = ref(false);
15
- const contents = ref<HTMLElement>();
16
-
17
- watch(
18
- () => props.expanded,
19
- () => {
20
- show.value = props.expanded;
21
- },
22
- { immediate: true }
23
- );
24
-
25
- const { height: targetHeight } = useElementSize(contents, undefined, {
26
- box: 'border-box',
27
- });
28
-
29
- const height = computed(() => (show.value ? targetHeight.value : 0));
30
- </script>
31
-
32
- <template>
33
- <div class="group border-b border-gray-800 bg-white dark:bg-gray-950 dark:border-gray-500 transition duration-500 hover:bg-gray-50">
34
- <div class="flex cursor-pointer items-center justify-center p-5 md:p-6">
35
- <div class="text-base text-gray-900 transition group-hover:text-gray-950 md:text-lg">
36
- <slot name="title"></slot>
37
- </div>
38
-
39
- <div class="relative ml-auto flex items-center justify-center">
40
- <span :class="{ 'rotate-180': show, 'rotate-45': !show }" class="i-bi-x h-5 w-5 text-gray-900 transition-transform duration-500 md:h-6 md:w-6 dark:bg-white"></span>
41
- </div>
42
- </div>
43
-
44
- <div
45
- :style="{
46
- height: `${height}px`,
47
- }"
48
- class="overflow-hidden px-5 transition-[height] duration-500 will-change-[height] md:px-6"
49
- >
50
- <div ref="contents" class="space-y-4 pb-5 font-light leading-relaxed tracking-wide text-gray-900 md:pb-6">
51
- <slot></slot>
52
- </div>
53
- </div>
54
- </div>
55
- </template>
@@ -1,103 +0,0 @@
1
- <script setup lang="ts">
2
- import { twMerge } from 'tailwind-merge';
3
- import { NuxtLink } from '#components';
4
- import type { RouteLocationRaw } from 'vue-router';
5
-
6
- const props = withDefaults(
7
- defineProps<{
8
- href?: string;
9
- to?: RouteLocationRaw;
10
- appearance?: 'regular' | 'outline' | 'none';
11
- size?: 'sm' | 'md' | 'lg' | 'auto';
12
- color?: 'primary' | 'secondary' | 'green' | 'yellow' | 'lightprimary' | 'danger';
13
- textColor?: 'white' | 'black' | 'primary' | 'gray' | '';
14
- type?: 'submit' | 'button';
15
- loading?: boolean;
16
- loadingText?: string;
17
- block?: boolean; // or width full
18
- }>(),
19
- {
20
- appearance: 'regular',
21
- size: 'md',
22
- type: 'button',
23
- color: 'primary',
24
- textColor: '',
25
- loading: false,
26
- loadingText: 'Loading',
27
- block: false,
28
- }
29
- );
30
-
31
- const appearanceClasses: Record<typeof props.appearance, string> = {
32
- regular: 'rounded-md text-white',
33
- outline:
34
- 'rounded-md border border-primary bg-background hover:bg-primary-500 text-primary hover:text-white disabled:bg-transparent disabled:text-gray-400 disabled:border-gray-200',
35
- none: 'bg-transparent border-transparent hover:text-primary-500 text-foreground hover:bg-transparent',
36
- };
37
-
38
- const sizeClasses: Record<typeof props.size, string> = {
39
- sm: 'min-w-[100px] py-1.5 px-1.5 text-sm',
40
- md: 'min-w-[100px] py-1.5 px-2 text-base',
41
- lg: 'min-w-[240px] py-3 px-4 text-lg',
42
- auto: 'text-sm lg:text-lg',
43
- };
44
-
45
- const colorClasses: Record<typeof props.color, string> = {
46
- primary: 'bg-primary-500 hover:bg-primary-400 text-primary-50',
47
- secondary: 'bg-orange-500 hover:bg-orange-400 text-orange-50',
48
- green: 'bg-green-500 hover:bg-green-400 text-green-50',
49
- yellow: 'bg-yellow-500 hover:bg-yellow-400 text-yellow-950',
50
- danger: 'bg-red-500 hover:bg-red-400 text-red-950',
51
- lightprimary: 'bg-primary-300 text-primary-50',
52
- };
53
-
54
- const textColorClasses: Record<typeof props.textColor, string> = {
55
- white: 'text-white',
56
- black: 'text-black',
57
- primary: 'text-primary',
58
- gray: 'text-gray-400',
59
- '': '',
60
- };
61
-
62
- const LoadingIcon = defineComponent({
63
- render: () =>
64
- h('svg', { fill: 'none', viewBox: '0 0 24 24', class: 'animate-spin h-5 w-5 text-white' }, [
65
- h('circle', {
66
- class: 'opacity-25',
67
- cx: '12',
68
- cy: '12',
69
- r: '10',
70
- stroke: 'currentColor',
71
- 'stroke-width': '4',
72
- }),
73
- h('path', {
74
- class: 'opacity-75',
75
- fill: 'currentColor',
76
- d: 'M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z',
77
- }),
78
- ]),
79
- });
80
- const defaultClasses = 'duration-200 flex gap-2 justify-center items-center whitespace-nowrap text-center disabled:bg-gray-200 disabled:cursor-not-allowed disabled:text-gray-400';
81
- const classes = twMerge(defaultClasses, sizeClasses[props.size], colorClasses[props.color], appearanceClasses[props.appearance], textColorClasses[props.textColor]);
82
- </script>
83
-
84
- <template>
85
- <component
86
- :is="props.href ? 'a' : props.to ? NuxtLink : 'button'"
87
- :class="[
88
- classes,
89
- {
90
- 'w-full': props.block,
91
- 'cursor-wait': props.loading,
92
- },
93
- ]"
94
- :to="props.to"
95
- :active-class="props.appearance === 'none' ? '!text-primary-500' : ''"
96
- :href="props.href"
97
- :type="props.type"
98
- >
99
- <LoadingIcon v-if="props.loading" />
100
- <p v-if="props.loadingText && props.loading" v-text="props.loadingText"></p>
101
- <slot v-else></slot>
102
- </component>
103
- </template>
@@ -1,5 +0,0 @@
1
- <template>
2
- <div class="w-full px-4 max-w-7xl mx-auto">
3
- <slot></slot>
4
- </div>
5
- </template>
@@ -1,61 +0,0 @@
1
- <script setup lang="ts">
2
- import { useMouse } from '@vueuse/core';
3
-
4
- import { useContextMenu } from '~/composables/use-context-menu';
5
-
6
- const { activeMenu, close } = useContextMenu();
7
- const target = ref();
8
- const isLeft = usePageLeave();
9
-
10
- onClickOutside(target, () => close());
11
-
12
- const { x, y } = useMouse();
13
- const left = ref(0);
14
- const top = ref(0);
15
-
16
- watch(
17
- () => isLeft.value,
18
- () => {
19
- close();
20
- }
21
- );
22
-
23
- watch(
24
- () => activeMenu.value,
25
- () => {
26
- if (activeMenu.value?.show) {
27
- left.value = unref(x.value);
28
- top.value = unref(y.value);
29
- }
30
- }
31
- );
32
- </script>
33
-
34
- <template>
35
- <ClientOnly>
36
- <Teleport to="body">
37
- <TransitionFade leave-duration="150" start-duration="150">
38
- <div
39
- v-show="activeMenu"
40
- @mouseleave="close()"
41
- ref="target"
42
- class="absolute w-56 right-0 origin-top-right border border-border bg-background overflow-hidden shadow-lg rounded-md focus:outline-none"
43
- :style="`top: ${top}px; left: ${left}px`"
44
- >
45
- <button
46
- v-for="item of activeMenu?.items"
47
- :key="item"
48
- type="button"
49
- class="w-full text-left text-foreground px-4 py-2 text-sm hover:bg-hover hover:text-foreground flex justify-between items-center"
50
- @click="
51
- item.click();
52
- close();
53
- "
54
- >
55
- {{ item.label }}
56
- </button>
57
- </div>
58
- </TransitionFade>
59
- </Teleport>
60
- </ClientOnly>
61
- </template>
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- const emit = defineEmits<{
3
- (event: 'loadMore'): void;
4
- }>();
5
- const scrollContainer = ref<HTMLElement | null>(null);
6
- let infinityScrollInProgress = false;
7
-
8
- const handleScroll = async () => {
9
- let element = scrollContainer.value;
10
- if (!infinityScrollInProgress && element && element.getBoundingClientRect().bottom < window.innerHeight) {
11
- infinityScrollInProgress = true;
12
-
13
- emit('loadMore');
14
-
15
- setTimeout(() => {
16
- infinityScrollInProgress = false;
17
- }, 1000);
18
- }
19
- };
20
-
21
- onMounted(() => {
22
- window.addEventListener('scroll', handleScroll);
23
- });
24
-
25
- onUnmounted(() => {
26
- window.removeEventListener('scroll', handleScroll);
27
- });
28
- </script>
29
-
30
- <template>
31
- <div ref="scrollContainer">
32
- <slot></slot>
33
- </div>
34
- </template>
@@ -1,64 +0,0 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(
3
- defineProps<{
4
- percent: number | `${number}`;
5
- duration?: number | `${number}`;
6
- label?: string;
7
- color?: 'primary' | 'red' | 'green' | 'blue';
8
- }>(),
9
- {
10
- duration: 400,
11
- color: 'primary',
12
- }
13
- );
14
-
15
- type RecordFromUnion<T extends string> = {
16
- [K in T]: string;
17
- };
18
-
19
- type ColorTypes = 'text' | 'background' | 'background-light';
20
-
21
- const colorClass: Record<typeof props.color, RecordFromUnion<ColorTypes>> = {
22
- primary: {
23
- text: 'text-primary-600',
24
- background: 'bg-primary-500',
25
- 'background-light': 'bg-gray-200',
26
- },
27
- red: {
28
- text: 'text-red-600',
29
- background: 'bg-red-500',
30
- 'background-light': 'bg-red-200',
31
- },
32
- green: {
33
- text: 'text-green-600',
34
- background: 'bg-green-500',
35
- 'background-light': 'bg-green-200',
36
- },
37
- blue: {
38
- text: 'text-blue-600',
39
- background: 'bg-blue-500',
40
- 'background-light': 'bg-blue-200',
41
- },
42
- };
43
- </script>
44
-
45
- <template>
46
- <div :style="`--percent: ${props.percent}%; --duration: ${props.duration}ms;`" class="relative pt-1 w-full">
47
- <div v-if="props.label" class="flex mb-2 items-center justify-between">
48
- <div>
49
- <span :class="[colorClass[props.color].text, colorClass[props.color]['background-light']]" class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full">
50
- {{ props.label }}
51
- </span>
52
- </div>
53
- <div class="text-right">
54
- <span :class="[colorClass[props.color].text]" class="text-xs font-semibold inline-block"> {{ (+props.percent).toFixed(0) }}% </span>
55
- </div>
56
- </div>
57
- <div :class="colorClass[props.color]['background-light']" class="overflow-hidden h-1 mb-4 text-xs flex rounded bg-[--color-light]">
58
- <div
59
- :class="colorClass[props.color].background"
60
- class="transform ease-in-out w-[--percent] duration-[--duration] shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center"
61
- ></div>
62
- </div>
63
- </div>
64
- </template>
@@ -1,27 +0,0 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(
3
- defineProps<{
4
- active?: boolean;
5
- }>(),
6
- {
7
- active: false,
8
- }
9
- );
10
- const toggleActive = ref(false);
11
-
12
- watch(
13
- () => props.active,
14
- () => {
15
- toggleActive.value = props.active;
16
- },
17
- { immediate: true }
18
- );
19
- </script>
20
-
21
- <template>
22
- <div class="flex justify-between items-center cursor-pointer" @click="toggleActive = !toggleActive">
23
- <div class="w-8 h-5 flex items-center bg-gray-300 rounded-full p-1 duration-300 ease-in-out" :class="{ 'bg-green-400': toggleActive }">
24
- <div class="bg-white w-4 h-4 rounded-full shadow-md transform duration-300 ease-in-out" :class="{ 'translate-x-2': toggleActive }"></div>
25
- </div>
26
- </div>
27
- </template>
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- import { ErrorMessage, useField } from 'vee-validate';
3
-
4
- const props = defineProps<{
5
- disabled?: boolean;
6
- label?: string;
7
- name: string;
8
- placeholder?: string;
9
- type: string;
10
- }>();
11
-
12
- const { handleBlur, handleChange, value, meta, setTouched } = useField(() => props.name);
13
- </script>
14
-
15
- <template>
16
- <div class="mt-3">
17
- <label v-if="label" :for="name" class="block text-sm font-medium leading-6 text-foreground">{{ label }}{{ meta.required ? '*' : '' }}</label>
18
- <div class="relative mt-2 pb-2">
19
- <input
20
- :id="name"
21
- :type="type"
22
- :name="name"
23
- v-model="value"
24
- :disabled="disabled"
25
- @blur="handleBlur"
26
- @focus="setTouched(true)"
27
- @change="handleChange"
28
- class="bg-background block w-full rounded-md border-0 py-1.5 text-foreground shadow-sm ring-1 ring-inset ring-border placeholder:text-foreground/50 focus:ring-1 focus:ring-inset focus:ring-primary-500 sm:text-sm sm:leading-6"
29
- :placeholder="placeholder"
30
- />
31
- <ErrorMessage class="absolute -bottom-2.5 text-xs font-light text-red-600" :name="name" />
32
- </div>
33
- </div>
34
- </template>
@@ -1,47 +0,0 @@
1
- <script setup lang="ts">
2
- import { ErrorMessage, useField } from 'vee-validate';
3
-
4
- enum InputType {
5
- Password = 'password',
6
- Text = 'text',
7
- }
8
-
9
- const props = defineProps<{
10
- label: string;
11
- name: string;
12
- placeholder: string;
13
- }>();
14
-
15
- const type = ref<InputType>(InputType.Password);
16
- const { handleBlur, handleChange, meta, value, setTouched } = useField(() => props.name);
17
-
18
- function changeType() {
19
- type.value = type.value === InputType.Password ? InputType.Text : InputType.Password;
20
- }
21
- </script>
22
-
23
- <template>
24
- <div class="mt-3">
25
- <label v-if="label" :for="name" class="block text-sm font-medium leading-6 text-foreground">{{ label }}{{ meta.required ? '*' : '' }}</label>
26
- <div class="relative mt-2 pb-2">
27
- <div class="relative">
28
- <input
29
- :id="name"
30
- v-model="value"
31
- :type="type"
32
- :name="name"
33
- @blur="handleBlur"
34
- @focus="setTouched(true)"
35
- @change="handleChange"
36
- class="bg-background block w-full rounded-md border-0 py-1.5 text-foreground shadow-sm ring-1 ring-inset ring-border placeholder:text-foreground/50 focus:ring-1 focus:ring-inset focus:ring-primary-500 sm:text-sm sm:leading-6"
37
- :placeholder="placeholder"
38
- />
39
- <button type="button" class="absolute right-2 bottom-1/2 transform translate-y-1/2 text-secondary-100 flex items-center hover:text-primary-500" @click="changeType()">
40
- <span v-if="type === 'password'" class="i-bi-eye"></span>
41
- <span v-else class="i-bi-eye-slash"></span>
42
- </button>
43
- </div>
44
- <ErrorMessage class="absolute -bottom-2.5 text-xs font-light text-red-600" :name="name" />
45
- </div>
46
- </div>
47
- </template>
@@ -1,40 +0,0 @@
1
- <script setup lang="ts">
2
- import { ErrorMessage, useField } from 'vee-validate';
3
-
4
- interface Option {
5
- label: string;
6
- value: any;
7
- }
8
-
9
- const props = defineProps<{
10
- disabled?: boolean;
11
- label?: string;
12
- multiple?: boolean;
13
- name: string;
14
- options: Option[];
15
- placeholder?: string;
16
- }>();
17
-
18
- const { handleBlur, handleChange, meta, value, setTouched } = useField(() => props.name);
19
- </script>
20
-
21
- <template>
22
- <div class="relative mt-3 pb-2">
23
- <label v-if="label" :for="name" class="block text-sm font-medium leading-6 text-foreground">{{ label }}{{ meta.required ? '*' : '' }}</label>
24
- <select
25
- :id="name"
26
- v-model="value"
27
- @focus="setTouched(true)"
28
- :name="name"
29
- :multiple="multiple ?? false"
30
- :disabled="disabled"
31
- class="bg-background mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-foreground ring-1 ring-inset ring-border focus:ring-1 focus:ring-primary-500 sm:text-sm sm:leading-6"
32
- @blur="handleBlur"
33
- @change="handleChange"
34
- >
35
- <option class="text-foreground/50" :value="null" disabled selected>{{ placeholder }}</option>
36
- <option v-for="option of options" :value="option.value">{{ option.label }}</option>
37
- </select>
38
- <ErrorMessage class="absolute -bottom-2.5 text-xs font-light text-red-600" :name="name" />
39
- </div>
40
- </template>
@@ -1,18 +0,0 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- isSubmitting?: boolean;
4
- label: string;
5
- }>();
6
- </script>
7
-
8
- <template>
9
- <div>
10
- <BaseButton v-if="isSubmitting" type="button" disabled>
11
- <span class="animate-bounce">.</span>
12
- <span class="animate-bounce delay-150">.</span>
13
- <span class="animate-bounce delay-300 me-1">.</span>
14
- Loading
15
- </BaseButton>
16
- <BaseButton v-else type="submit"> {{ label }} </BaseButton>
17
- </div>
18
- </template>
@@ -1,36 +0,0 @@
1
- <script setup lang="ts">
2
- import { ErrorMessage, useField } from 'vee-validate';
3
-
4
- const props = defineProps<{
5
- disabled?: boolean;
6
- label?: string;
7
- name: string;
8
- placeholder?: string;
9
- cols?: number;
10
- maxlength?: number;
11
- }>();
12
-
13
- const { handleBlur, handleChange, value, meta, setTouched } = useField(() => props.name);
14
- </script>
15
-
16
- <template>
17
- <div class="mt-3">
18
- <label v-if="label" :for="name" class="block text-sm font-medium leading-6 text-foreground">{{ label }}{{ meta.required ? '*' : '' }}</label>
19
- <div class="relative mt-2 pb-2">
20
- <textarea
21
- :id="name"
22
- :cols="cols"
23
- :maxlength="maxlength"
24
- :name="name"
25
- v-model="value"
26
- :disabled="disabled"
27
- @blur="handleBlur"
28
- @focus="setTouched(true)"
29
- @change="handleChange"
30
- class="bg-background block resize-none w-full rounded-md border-0 py-1.5 text-foreground shadow-sm ring-1 ring-inset ring-border placeholder:text-foreground/50 focus:ring-1 focus:ring-inset focus:ring-primary-500 sm:text-sm sm:leading-6"
31
- :placeholder="placeholder"
32
- />
33
- <ErrorMessage class="absolute -bottom-2.5 text-xs font-light text-red-600" :name="name" />
34
- </div>
35
- </div>
36
- </template>
@@ -1,27 +0,0 @@
1
- <script setup lang="ts">
2
- import { ErrorMessage, useField } from 'vee-validate';
3
-
4
- const props = defineProps<{
5
- disabled?: boolean;
6
- label?: string;
7
- name: string;
8
- }>();
9
-
10
- const { handleChange, meta, value, setTouched } = useField(() => props.name);
11
-
12
- watch(
13
- () => value.value,
14
- () => {
15
- handleChange(value.value);
16
- setTouched(true);
17
- }
18
- );
19
- </script>
20
-
21
- <template>
22
- <div class="relative mt-3 pb-2">
23
- <label v-if="label" :for="name" class="block text-sm font-medium leading-6 text-foreground">{{ label }}{{ meta.required ? '*' : '' }}</label>
24
- <BaseToggle v-model:active="value" :disabled="disabled" />
25
- <ErrorMessage class="absolute -bottom-2.5 text-xs font-light text-red-600" :name="name" />
26
- </div>
27
- </template>
@@ -1,48 +0,0 @@
1
- <script setup lang="ts">
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- defineOptions({
5
- inheritAttrs: false,
6
- });
7
-
8
- const props = withDefaults(
9
- defineProps<{
10
- show: boolean;
11
- showInner: boolean;
12
- backgroundColor?: string;
13
- size?: 'auto' | 'sm' | 'md' | 'lg';
14
- }>(),
15
- {
16
- backgroundColor: '#FFF',
17
- size: 'auto',
18
- }
19
- );
20
-
21
- const emit = defineEmits<{
22
- (event: 'cancel'): void;
23
- }>();
24
-
25
- const attributes = useAttrs() as { class: string };
26
- const sizeClasses: Record<typeof props.size, string> = {
27
- auto: 'w-4/5 md:w-auto',
28
- sm: 'w-4/5 md:w-1/4',
29
- md: 'w-4/5 md:w-3/6',
30
- lg: 'w-4/5 md:w-4/5',
31
- };
32
- const defaultClasses = 'duration-200 w-full overflow-hidden rounded-[20px] p-4 bg-[--bg-color] bg-background flex flex-col gap-4';
33
- const classes = twMerge(defaultClasses, sizeClasses[props.size], attributes?.class);
34
- </script>
35
-
36
- <template>
37
- <div class="relative z-50" aria-labelledby="modal-title" role="dialog" aria-modal="true" :style="`--bg-color: ${props.backgroundColor};`">
38
- <TransitionFade>
39
- <div v-show="show" class="fixed inset-0 bg-background/30 backdrop-blur-sm transition-opacity flex justify-center items-center" @click="emit('cancel')">
40
- <TransitionFade class="w-full flex justify-center">
41
- <div v-show="showInner" :class="classes" @click.stop>
42
- <slot></slot>
43
- </div>
44
- </TransitionFade>
45
- </div>
46
- </TransitionFade>
47
- </div>
48
- </template>
@@ -1,38 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ModalContext } from '~/composables/use-modal';
3
-
4
- const props = defineProps<{
5
- context: ModalContext<{
6
- cancelText: string;
7
- confirmColor?: 'primary' | 'secondary' | 'green' | 'yellow' | 'lightprimary' | 'danger';
8
- confirmText: string;
9
- text: string;
10
- title: string;
11
- }>;
12
- }>();
13
- const { close } = useModal();
14
- </script>
15
-
16
- <template>
17
- <Modal class="p-10 relative" :show="context.show" :show-inner="context.showInner" :size="context.size" @cancel="context.closable ? close() : null">
18
- <div class="flex items-center justify-center mb-2">
19
- <div class="font-semibold text-xl text-foreground">
20
- {{ context?.data?.title }}
21
- </div>
22
- <button class="absolute top-5 right-5" @click="close()">
23
- <span class="i-bi-x text-2xl hover:text-primary-500 duration-200"></span>
24
- </button>
25
- </div>
26
- <div class="mb-5 text-foreground">
27
- {{ context?.data?.text }}
28
- </div>
29
- <div class="grid grid-cols-1 md:grid-cols-2 gap-2">
30
- <BaseButton appearance="outline" color="primary" @click="context?.confirm?.(false)">
31
- {{ context?.data?.cancelText || 'Abbrechen' }}
32
- </BaseButton>
33
- <BaseButton :color="context?.data?.confirmColor || 'primary'" @click="context?.confirm?.(true)">
34
- {{ context?.data?.confirmText || 'Ok' }}
35
- </BaseButton>
36
- </div>
37
- </Modal>
38
- </template>
@@ -1,7 +0,0 @@
1
- <script setup lang="ts">
2
- const { activeModal } = useModal();
3
- </script>
4
-
5
- <template>
6
- <component :is="activeModal.component" v-if="activeModal" :context="activeModal" />
7
- </template>
@@ -1,22 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ModalContext } from '~/composables/use-modal';
3
-
4
- const props = defineProps<{ context: ModalContext<{ text: string; title: string }> }>();
5
- const { close } = useModal();
6
- </script>
7
-
8
- <template>
9
- <Modal class="p-10 relative" :show="context.show" :show-inner="context.showInner" :size="context.size" @cancel="context.closable ? close() : null">
10
- <div class="flex items-center justify-center mb-2">
11
- <div class="font-semibold text-xl text-foreground">
12
- {{ context?.data?.title }}
13
- </div>
14
- <button class="absolute top-5 right-5" @click="close()">
15
- <span class="i-bi-x text-2xl hover:text-primary-500 duration-200"></span>
16
- </button>
17
- </div>
18
- <div class="text-center text-foreground mb-6">
19
- {{ context?.data?.text }}
20
- </div>
21
- </Modal>
22
- </template>
@@ -1,63 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ModalContext } from '~/composables/use-modal';
3
- import SocialMediaBubble from '~/components/SocialMediaBubble.vue';
4
-
5
- const props = defineProps<{ context: ModalContext<{ link: string; name: string }> }>();
6
- const { close } = useModal();
7
- const url = ref<string>('');
8
-
9
- onMounted(() => {
10
- url.value = props.context.data?.link ? props.context.data?.link : window.location.href;
11
- });
12
-
13
- const copyUrl = () => {
14
- useClipboard({ source: url.value }).copy();
15
- useNotification().notify({ type: 'success', title: 'Erfolgreich', text: 'Link wurde erfolgreich kopiert!' });
16
- };
17
-
18
- const shareWith = (type: 'mail' | 'whatsapp' | 'facebook' | 'linkedin' | 'twitter') => {
19
- switch (type) {
20
- case 'mail':
21
- open(`mailto:?subject=${props.context.data?.name}&body=${encodeURIComponent(url.value)}`, '_blank');
22
- break;
23
- case 'whatsapp':
24
- open(`https://api.whatsapp.com/send/?text=${encodeURIComponent(url.value)}`, '_blank');
25
- break;
26
- case 'facebook':
27
- open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url.value)}`, '_blank');
28
- break;
29
- case 'linkedin':
30
- open(`https://www.linkedin.com/shareArticle?url=${encodeURIComponent(url.value)}`, '_blank');
31
- break;
32
- case 'twitter':
33
- open(`https://twitter.com/share?url=${encodeURIComponent(url.value)}`, '_blank');
34
- break;
35
- }
36
- };
37
- </script>
38
-
39
- <template>
40
- <Modal class="p-10 relative" :show="context.show" :show-inner="context.showInner" :size="context.size" @cancel="context.closable ? close() : null">
41
- <div class="flex items-center justify-center mb-4">
42
- <div class="font-semibold text-xl text-foreground">Teilen</div>
43
- <button class="absolute top-5 right-5" @click="close()">
44
- <span class="i-bi-x text-2xl hover:text-primary-500"></span>
45
- </button>
46
- </div>
47
- <div class="text-center text-foreground">
48
- <div class="flex justify-around max-w-md mx-auto gap-3 py-2 mb-5 overflow-x-scroll">
49
- <SocialMediaBubble title="E-Mail" bg-color="#bbbbbb" icon="i-bi-envelope" @click="shareWith('mail')" />
50
- <SocialMediaBubble title="Whatsapp" bg-color="#25d366" icon="i-bi-whatsapp" @click="shareWith('whatsapp')" />
51
- <SocialMediaBubble title="Facebook" bg-color="#3b5998" icon="i-bi-facebook" @click="shareWith('facebook')" />
52
- <SocialMediaBubble title="LinkedIn" bg-color="#0a66c2" icon="i-bi-linkedin" @click="shareWith('linkedin')" />
53
- <SocialMediaBubble title="X / Twitter" bg-color="#000000" icon="i-bi-twitter-x" @click="shareWith('twitter')" />
54
- </div>
55
- <div class="flex items-center gap-3 justify-between p-3 border border-border rounded-lg">
56
- <span class="text-ellipsis overflow-hidden">
57
- {{ url }}
58
- </span>
59
- <BaseButton size="sm" @click="copyUrl()"> Kopieren </BaseButton>
60
- </div>
61
- </div>
62
- </Modal>
63
- </template>
@@ -1,82 +0,0 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- title: string;
4
- text?: string;
5
- duration: number;
6
- type: 'success' | 'error' | 'warning' | 'info';
7
- }>();
8
-
9
- const emit = defineEmits<{
10
- (event: 'close'): void;
11
- }>();
12
-
13
- const process = ref(100);
14
- let durationLeft = props.duration - (500 + 130);
15
- onMounted(() => {
16
- const interval = setInterval(() => {
17
- durationLeft -= 30;
18
- process.value = (durationLeft / props.duration) * 100;
19
- }, 30);
20
- setTimeout(() => {
21
- clearInterval(interval);
22
- emit('close');
23
- }, props.duration);
24
- });
25
-
26
- const icon: Record<typeof props.type, string> = {
27
- success: 'i-bi-check-circle',
28
- error: 'i-bi-exclamation-circle',
29
- warning: 'i-bi-exclamation-triangle',
30
- info: 'i-bi-info-circle',
31
- };
32
-
33
- const accentColor: Record<typeof props.type, string> = {
34
- success: '#059669',
35
- error: '#DC2626',
36
- warning: '#D97706',
37
- info: '#3B82F6',
38
- };
39
- </script>
40
-
41
- <template>
42
- <div
43
- :style="`--accent-color: ${accentColor[props.type]}`"
44
- class="pointer-events-auto relative w-full max-w-sm overflow-hidden rounded-lg bg-white dark:bg-gray-950 shadow-lg ring-1 ring-black ring-opacity-5 z-[99999px]"
45
- >
46
- <div
47
- :style="{
48
- width: `${process}%`
49
- }"
50
- class="bg-[--accent-color] h-[5px] duration-100 transition-[width] w-[width] absolute bottom-0"
51
- ></div>
52
- <div class="p-4">
53
- <div class="flex items-start">
54
- <div class="flex-shrink-0">
55
- <span :class="icon[props.type]" class="h-6 w-6 text-[--accent-color]"></span>
56
- </div>
57
- <div class="ml-3 w-0 flex-1 pt-0.5">
58
- <p class="text-sm font-semibold text-gray-900 dark:text-white">
59
- {{ props.title }}
60
- </p>
61
- <p v-if="props.text" class="mt-1 text-sm text-gray-500 dark:text-white">
62
- {{ props.text }}
63
- </p>
64
- </div>
65
- <div class="ml-4 flex flex-shrink-0">
66
- <button
67
- type="button"
68
- class="inline-flex rounded-md bg-white text-gray-400 dark:bg-gray-100 dark:text-white hover:text-gray-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
69
- @click="emit('close')"
70
- >
71
- <span class="sr-only">Close</span>
72
- <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
73
- <path
74
- d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
75
- />
76
- </svg>
77
- </button>
78
- </div>
79
- </div>
80
- </div>
81
- </div>
82
- </template>
@@ -1,40 +0,0 @@
1
- <script setup lang="ts">
2
- const { notifications, remove } = useNotification();
3
- </script>
4
-
5
- <template>
6
- <div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-[99999]">
7
- <TransitionGroup tag="div" name="list" class="flex w-full flex-col items-center space-y-4 sm:items-end">
8
- <Notification
9
- v-for="notification in notifications"
10
- :key="notification.uuid"
11
- :text="notification.text"
12
- :title="notification.title"
13
- :type="notification.type"
14
- :duration="notification.duration!"
15
- @close="remove(notification.uuid)"
16
- />
17
- </TransitionGroup>
18
- </div>
19
- </template>
20
-
21
- <style>
22
- .list-move,
23
- /* apply transition to moving elements */
24
- .list-enter-active,
25
- .list-leave-active {
26
- transition: all 0.5s ease;
27
- }
28
-
29
- .list-enter-from,
30
- .list-leave-to {
31
- opacity: 0;
32
- transform: translateX(30px);
33
- }
34
-
35
- /* ensure leaving items are taken out of layout flow so that moving
36
- animations can be calculated correctly. */
37
- .list-leave-active {
38
- position: absolute;
39
- }
40
- </style>
@@ -1,224 +0,0 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(
3
- defineProps<{
4
- addHomeButtonLabel?: string;
5
- body?: string;
6
- buttonLabel?: string;
7
- closePrompt?: string;
8
- debug?: boolean;
9
- delay?: number;
10
- iosBackgroundColor?: string;
11
- iosBackgroundColorDark?: string;
12
- iosBorderColor?: string;
13
- iosBorderColorDark?: string;
14
- iosButtonColoDark?: string;
15
- iosButtonColor?: string;
16
- iosOverlayColor?: string;
17
- iosOverlayColorDark?: string;
18
- iosTextColor?: string;
19
- iosTextColorDark?: string;
20
- iosTitleColor?: string;
21
- iosTitleColorDark?: string;
22
- title?: string;
23
- }>(),
24
- {
25
- addHomeButtonLabel: '2) Drücken Sie auf "Zum Home-Bildschirm".',
26
- body: 'Diese Website verfügt über eine App-Funktionalität. Fügen Sie sie zu Ihrem Startbildschirm hinzu, um sie im Vollbildmodus und offline zu nutzen.',
27
- buttonLabel: '1) Drücken Sie die Schaltfläche "Teilen".',
28
- closePrompt: 'Abbrechen',
29
- debug: false,
30
-
31
- delay: 1000,
32
- iosBackgroundColor: 'rgba(255, 255, 255, 0.9)',
33
-
34
- iosBackgroundColorDark: 'rgba(65, 65, 65, 0.7)',
35
- iosBorderColor: 'rgba(60, 60, 67, 0.29)',
36
-
37
- iosBorderColorDark: 'rgba(140, 140, 140, 0.7)',
38
- iosButtonColoDark: 'rgba(9, 132, 255, 1)',
39
-
40
- iosButtonColor: 'rgba(0, 85, 179, 1)',
41
- iosOverlayColor: 'rgba(10, 10, 10, 0.5)',
42
-
43
- iosOverlayColorDark: 'rgba(10, 10, 10, 0.5)',
44
- iosTextColor: 'rgba(60, 60, 67, 0.6)',
45
-
46
- iosTextColorDark: 'rgba(235, 235, 245, 0.6)',
47
- iosTitleColor: 'rgba(0, 0, 0, 1)',
48
-
49
- iosTitleColorDark: 'rgba(255, 255, 255, 1)',
50
- title: 'Zum Startbildschirm hinzufügen',
51
- }
52
- );
53
-
54
- const cssVariables = [
55
- `--ios-overlay: ${props.iosOverlayColor}`,
56
- `--ios-overlay-dark: ${props.iosOverlayColorDark}`,
57
- `--ios-background: ${props.iosBackgroundColor}`,
58
- `--ios-background-dark: ${props.iosBackgroundColorDark}`,
59
- `--ios-button: ${props.iosButtonColor}`,
60
- `--ios-button-dark: ${props.iosButtonColor}`,
61
- `--ios-border: ${props.iosBorderColor}`,
62
- `--ios-border-dark: ${props.iosBorderColorDark}`,
63
- `--ios-title: ${props.iosTitleColor}`,
64
- `--ios-title-dark: ${props.iosTitleColorDark}`,
65
- `--ios-text: ${props.iosTextColor}`,
66
- `--ios-text-dark: ${props.iosTextColorDark}`,
67
- ].join(';');
68
-
69
- const HomeIcon = defineComponent({
70
- render: () =>
71
- h('svg', { fill: 'currentColor', viewBox: '0 0 578 584' }, [
72
- h('path', {
73
- 'clip-rule': 'evenodd',
74
- d: 'M101 35l19-1h333c12 0 23 0 35 3 17 3 34 12 44 27 13 16 16 38 16 58v329c0 19 0 39-8 57a65 65 0 0 1-37 37c-18 7-38 7-57 7H130c-21 1-44 0-63-10-14-7-25-20-30-34-6-15-8-30-8-45V121c1-21 5-44 19-61 13-16 33-23 53-25m7 46c-10 1-19 6-24 14-7 8-9 20-9 31v334c0 12 2 25 10 34 9 10 23 12 35 12h336c14 1 30-3 38-15 6-9 8-20 8-31V125c0-12-2-24-10-33-9-9-22-12-35-12H121l-13 1z',
75
- 'fill-rule': 'evenodd',
76
- }),
77
- h('path', {
78
- 'clip-rule': 'evenodd',
79
- d: 'M271 161c9-11 31-10 38 4 3 5 3 11 3 17v87h88c7 0 16 1 21 7 6 6 7 14 6 22a21 21 0 0 1-10 14c-5 4-11 5-17 5h-88v82c0 7-1 15-6 20-10 10-29 10-37-2-3-6-4-13-4-19v-81h-87c-8-1-17-3-23-9-5-6-6-15-4-22a21 21 0 0 1 11-14c6-3 13-3 19-3h84v-88c0-7 1-14 6-20z',
80
- 'fill-rule': 'evenodd',
81
- }),
82
- ]),
83
- });
84
-
85
- const ShareIcon = defineComponent({
86
- render: () =>
87
- h('svg', { fill: 'currentColor', viewBox: '0 0 566 670' }, [
88
- h('path', {
89
- 'clip-rule': 'evenodd',
90
- d: 'M255 12c4-4 10-8 16-8s12 3 16 8l94 89c3 4 6 7 8 12 2 6 0 14-5 19-7 8-20 9-28 2l-7-7-57-60 2 54v276c0 12-10 22-22 22-12 1-24-10-23-22V110l1-43-60 65c-5 5-13 8-21 6a19 19 0 0 1-16-17c-1-7 2-13 7-18l95-91z',
91
- 'fill-rule': 'evenodd',
92
- }),
93
- h('path', {
94
- 'clip-rule': 'evenodd',
95
- d: 'M43 207c16-17 40-23 63-23h83v46h-79c-12 0-25 3-33 13-8 9-10 21-10 33v260c0 13 0 27 6 38 5 12 18 18 30 19l14 1h302c14 0 28 0 40-8 11-7 16-21 16-34V276c0-11-2-24-9-33-8-10-22-13-34-13h-78v-46h75c13 0 25 1 37 4 16 4 31 13 41 27 11 17 14 37 14 57v280c0 20-3 41-15 58a71 71 0 0 1-45 27c-11 2-23 3-34 3H109c-19-1-40-4-56-15-14-9-23-23-27-38-4-12-5-25-5-38V270c1-22 6-47 22-63z',
96
- 'fill-rule': 'evenodd',
97
- }),
98
- ]),
99
- });
100
-
101
- const showBanner = ref(false);
102
-
103
- watch(
104
- () => showBanner.value,
105
- () => {
106
- if (showBanner.value) {
107
- // Disable scrolling of background
108
- document.body.style.overflowY = 'hidden';
109
- } else {
110
- // Deactivate scrolling of background
111
- document.body.style.overflowY = '';
112
- }
113
- }
114
- );
115
-
116
- interface BeforeInstallPromptEvent {
117
- prompt: () => void;
118
- }
119
- const { storagePrefix } = useRuntimeConfig().public;
120
- const deferredPrompt = ref<BeforeInstallPromptEvent | null>();
121
-
122
- const cancelCookie = () => useCookie(`${storagePrefix}_pwa_prompt_canceled`, { default: () => false });
123
-
124
- function cancel() {
125
- cancelCookie().value = true;
126
- showBanner.value = false;
127
- }
128
-
129
- function deviceCheck() {
130
- const isiOS = /iphone|ipad|ipod/.test(window.navigator.userAgent.toLowerCase());
131
- const isiPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints >= 2;
132
- const isStandalone = 'standalone' in window.navigator && window.navigator.standalone;
133
-
134
- return (isiOS || isiPadOS) && !isStandalone;
135
- }
136
-
137
- function reactivate() {
138
- cancelCookie().value = false;
139
- showBanner.value = true;
140
- }
141
-
142
- function showInAppInstallPromotion() {
143
- if (deferredPrompt.value) {
144
- deferredPrompt.value.prompt();
145
- }
146
- }
147
-
148
- onMounted(() => {
149
- if (!cancelCookie().value) {
150
- showBanner.value = deviceCheck();
151
- }
152
-
153
- // This variable will save the event for later use.
154
- let deferredPrompt;
155
- window.addEventListener('beforeinstallprompt', (e) => {
156
- // Prevents the default mini-infobar or install dialog from appearing on mobile
157
- e.preventDefault();
158
- // Save the event because you'll need to trigger it later.
159
- deferredPrompt = e;
160
- // Show your customized install prompt for your PWA
161
- // Your own UI doesn't have to be a single element, you
162
- // can have buttons in different locations, or wait to prompt
163
- // as part of a critical journey.
164
- showInAppInstallPromotion();
165
- });
166
- });
167
-
168
- defineExpose({
169
- reactivate,
170
- });
171
- </script>
172
-
173
- <template>
174
- <Transition
175
- enter-active-class="ease-out duration-500 delay-500"
176
- enter-from-class="translate-y-full"
177
- enter-to-class="translate-x-0"
178
- leave-active-class="ease-in duration-500"
179
- leave-from-class="translate-x-0"
180
- leave-to-class="translate-y-full"
181
- >
182
- <div v-show="showBanner" :style="cssVariables" class="bottom-0 w-full fixed !z-[9998] p-2 max-w-md mx-auto">
183
- <div class="bg-[var(--ios-background)] backdrop-blur-sm overflow-hidden dark:bg-[var(--ios-background-dark)] rounded-lg">
184
- <div class="flex justify-between border-b border-[var(--ios-border)] dark:border-[var(--ios-border-dark)]">
185
- <h4 class="text-[var(--ios-title)] dark:text-[var(--ios-title-dark)] text-md p-4">
186
- {{ props.title }}
187
- </h4>
188
- <button class="text-[var(--ios-button)] dark:text-[var(--ios-button-dark)] text-base p-4" @click="cancel()">
189
- {{ props.closePrompt }}
190
- </button>
191
- </div>
192
- <div class="px-8 py-4">
193
- <p class="text-[var(--ios-text)] dark:text-[var(--ios-text-dark)] text-center text-sm border-b px-4 pb-4 border-[var(--ios-border)] dark:border-[var(--ios-border-dark)]">
194
- {{ props.body }}
195
- </p>
196
- <div class="flex flex-col gap-4 p-4 pt-8">
197
- <div class="flex justify-start items-center gap-6">
198
- <ShareIcon class="h-8 w-auto text-[var(--ios-button)] dark:text-[var(--ios-button-dark)]" />
199
- <p class="text-sm text-left text-[var(--ios-text)] dark:text-[var(--ios-text-dark)]">
200
- {{ props.buttonLabel }}
201
- </p>
202
- </div>
203
- <div class="flex justify-start items-center gap-6">
204
- <HomeIcon class="h-8 w-auto text-[var(--ios-button)] dark:text-[var(--ios-button-dark)]" />
205
- <p class="text-sm text-left text-[var(--ios-text)] dark:text-[var(--ios-text-dark)]">
206
- {{ props.addHomeButtonLabel }}
207
- </p>
208
- </div>
209
- </div>
210
- </div>
211
- </div>
212
- </div>
213
- </Transition>
214
- <Transition
215
- enter-active-class="ease-out duration-1000"
216
- enter-from-class="opacity-0"
217
- enter-to-class="opacity-100"
218
- leave-active-class="ease-in duration-1000"
219
- leave-from-class="opacity-100"
220
- leave-to-class="opacity-0"
221
- >
222
- <div v-show="showBanner" :style="cssVariables" class="absolute inset-0 bg-black/50 !z-[9997] min-h-screen w-full"></div>
223
- </Transition>
224
- </template>