create-nuxt-base 0.1.20 → 0.1.22
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/CHANGELOG.md +4 -0
- package/index.js +1 -1
- package/nuxt-base-template/formkit-theme.js +137 -0
- package/nuxt-base-template/formkit.config.js +35 -0
- package/nuxt-base-template/nuxt.config.ts +34 -3
- package/nuxt-base-template/package-lock.json +11248 -14131
- package/nuxt-base-template/package.json +37 -36
- package/nuxt-base-template/src/app.vue +8 -0
- package/nuxt-base-template/src/assets/css/tailwind.css +37 -2
- package/nuxt-base-template/src/components/ModalShare.vue +67 -0
- package/nuxt-base-template/src/components/SocialMediaBubble.vue +16 -0
- package/nuxt-base-template/src/components/base/BaseAccordion.vue +52 -0
- package/nuxt-base-template/src/components/base/BaseButton.vue +106 -0
- package/nuxt-base-template/src/components/base/BaseContainer.vue +5 -0
- package/nuxt-base-template/src/components/base/BaseInfinityList.vue +34 -0
- package/nuxt-base-template/src/components/base/BaseModalContainer.vue +7 -0
- package/nuxt-base-template/src/components/base/BaseNotification.vue +81 -0
- package/nuxt-base-template/src/components/base/BaseNotificationContainer.vue +34 -0
- package/nuxt-base-template/src/components/base/BaseProgressbar.vue +66 -0
- package/nuxt-base-template/src/components/base/BaseToggle.vue +20 -0
- package/nuxt-base-template/src/components/transition/TransitionFade.vue +24 -0
- package/nuxt-base-template/src/components/transition/TransitionFadeScale.vue +24 -0
- package/nuxt-base-template/src/components/transition/TransitionSlide.vue +14 -0
- package/nuxt-base-template/src/components/transition/TransitionSlideBottom.vue +14 -0
- package/nuxt-base-template/src/components/transition/TransitionSlideRevert.vue +14 -0
- package/nuxt-base-template/src/composables/use-auth-fetch.ts +19 -0
- package/nuxt-base-template/src/composables/use-file.ts +21 -0
- package/nuxt-base-template/src/composables/use-form-helper.ts +100 -0
- package/nuxt-base-template/src/composables/use-helper.ts +52 -0
- package/nuxt-base-template/src/composables/use-modal.ts +84 -0
- package/nuxt-base-template/src/composables/use-notification.ts +29 -0
- package/nuxt-base-template/src/composables/use-share.ts +22 -0
- package/nuxt-base-template/src/error.vue +53 -0
- package/nuxt-base-template/src/forms/inputs/InputCheckbox.vue +29 -0
- package/nuxt-base-template/src/forms/inputs/InputFreeTags.vue +98 -0
- package/nuxt-base-template/src/forms/inputs/InputImage.vue +65 -0
- package/nuxt-base-template/src/forms/inputs/InputTags.vue +112 -0
- package/nuxt-base-template/src/forms/inputs/InputToggle.vue +18 -0
- package/nuxt-base-template/src/forms/plugins/asterisk-plugin.ts +29 -0
- package/nuxt-base-template/src/forms/plugins/scroll-error-plugin.ts +36 -0
- package/nuxt-base-template/src/forms/plugins/value-changes-plugin.ts +13 -0
- package/nuxt-base-template/src/middleware/admin.global.ts +9 -0
- package/nuxt-base-template/src/middleware/auth.global.ts +5 -7
- package/nuxt-base-template/src/plugins/4.auth.server.ts +70 -0
- package/nuxt-base-template/src/tests/init.test.ts +12 -0
- package/nuxt-base-template/tailwind.config.js +42 -3
- package/nuxt-base-template/tsconfig.json +4 -1
- package/package.json +1 -1
- package/nuxt-base-template/cypress/e2e/spec.cy.ts +0 -5
- package/nuxt-base-template/cypress/fixtures/example.json +0 -5
- package/nuxt-base-template/cypress/integrations/Test/Test.feature +0 -8
- package/nuxt-base-template/cypress/integrations/Test/test.spec.js +0 -13
- package/nuxt-base-template/cypress/support/commands.ts +0 -37
- package/nuxt-base-template/cypress/support/e2e.ts +0 -20
- package/nuxt-base-template/cypress.config.ts +0 -54
- package/nuxt-base-template/plugins/index.js +0 -5
- package/nuxt-base-template/src/components/.gitkeep +0 -0
- package/nuxt-base-template/src/components/base/.gitkeep +0 -0
- package/nuxt-base-template/src/composables/.gitkeep +0 -0
- package/nuxt-base-template/src/forms/.gitkeep +0 -0
- package/nuxt-base-template/src/pages/.gitkeep +0 -0
- package/nuxt-base-template/src/tests/.gitkeep +0 -0
- package/nuxt-base-template/src/tests/hello-world.spec.ts +0 -11
|
@@ -3,56 +3,57 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"scripts": {
|
|
5
5
|
"init": "npm install",
|
|
6
|
-
"reinit": "rm -rf node_modules && rm -rf package-lock.json && npx nuxt cleanup && npm cache clean --force && npm i",
|
|
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
|
-
"
|
|
8
|
+
"build:test": "API_URL=123 nuxt build",
|
|
9
|
+
"start": "nuxt dev",
|
|
10
|
+
"start:test": "API_URL=123 node .output/server/index.mjs",
|
|
11
|
+
"generate-types": "GENERATE_TYPES=1 nuxt dev",
|
|
9
12
|
"dev": "nuxt dev",
|
|
10
13
|
"generate": "nuxt generate",
|
|
11
14
|
"preview": "nuxt preview",
|
|
12
15
|
"postinstall": "nuxt prepare",
|
|
13
|
-
"test": "vitest",
|
|
14
|
-
"
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
15
18
|
"lint": "eslint --ext .ts,.js,.vue .",
|
|
16
19
|
"lint:fix": "eslint --ext .ts,.js,.vue . --fix",
|
|
17
|
-
"
|
|
18
|
-
"release:minor": "standard-version --release-as minor && git push --follow-tags origin main",
|
|
19
|
-
"release:major": "standard-version --release-as major && git push --follow-tags origin main"
|
|
20
|
+
"postbuild": "cd .output/server/node_modules/tslib; npm pkg set 'exports[.].import.node'='./tslib.es6.mjs'"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"@formkit/
|
|
23
|
+
"@egoist/tailwindcss-icons": "1.2.0",
|
|
24
|
+
"@formkit/icons": "1.1.0",
|
|
25
|
+
"@formkit/nuxt": "1.1.0",
|
|
26
|
+
"@iconify-json/bi": "1.1.20",
|
|
24
27
|
"@lenne.tech/nuxt-base": "latest",
|
|
25
|
-
"@vueuse/core": "10.1
|
|
26
|
-
"@vueuse/
|
|
27
|
-
"
|
|
28
|
+
"@vueuse/core": "10.4.1",
|
|
29
|
+
"@vueuse/integrations": "10.4.1",
|
|
30
|
+
"@vueuse/nuxt": "10.4.1",
|
|
31
|
+
"pinia": "2.1.6",
|
|
32
|
+
"tailwind-merge": "1.14.0"
|
|
28
33
|
},
|
|
29
34
|
"devDependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@nuxt/test-utils": "3.
|
|
34
|
-
"@nuxtjs/
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
35
|
+
"@kevinmarrec/nuxt-pwa": "0.17.0",
|
|
36
|
+
"@lenne.tech/eslint-config-vue": "0.0.10",
|
|
37
|
+
"@nuxt/devtools": "0.8.5",
|
|
38
|
+
"@nuxt/test-utils": "3.7.4",
|
|
39
|
+
"@nuxtjs/color-mode": "3.3.0",
|
|
40
|
+
"@nuxtjs/google-fonts": "3.0.2",
|
|
41
|
+
"@nuxtjs/tailwindcss": "6.8.0",
|
|
42
|
+
"@tailwindcss/forms": "0.5.6",
|
|
43
|
+
"@tailwindcss/typography": "0.5.10",
|
|
44
|
+
"@vitejs/plugin-vue": "4.3.4",
|
|
45
|
+
"@vue/test-utils": "2.4.1",
|
|
46
|
+
"eslint": "8.50.0",
|
|
39
47
|
"jsdom": "22.1.0",
|
|
40
|
-
"nuxt": "3.
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"vitest": "0.31.0"
|
|
48
|
+
"nuxt": "3.7.4",
|
|
49
|
+
"ts-loader": "9.4.4",
|
|
50
|
+
"typescript": "5.2.2",
|
|
51
|
+
"vitest": "0.34.2"
|
|
45
52
|
},
|
|
46
53
|
"overrides": {
|
|
47
|
-
"vue": "latest"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"json": {
|
|
52
|
-
"enabled": true
|
|
53
|
-
},
|
|
54
|
-
"stepDefinitions": [
|
|
55
|
-
"cypress/integrations/**/*.{js,ts}"
|
|
56
|
-
]
|
|
54
|
+
"vue": "latest",
|
|
55
|
+
"@nuxt/test-utils": {
|
|
56
|
+
"vitest": "$vitest"
|
|
57
|
+
}
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
@tailwind base;
|
|
2
|
+
|
|
3
|
+
@layer base {
|
|
4
|
+
body {
|
|
5
|
+
@apply transition-colors duration-300;
|
|
6
|
+
}
|
|
7
|
+
h1 {
|
|
8
|
+
@apply text-[32px] leading-[140%] lg:text-[66px] lg:leading-[140%] font-bold;
|
|
9
|
+
}
|
|
10
|
+
h2 {
|
|
11
|
+
@apply text-[28px] leading-[140%] lg:text-[51px] lg:leading-[140%] font-semibold;
|
|
12
|
+
}
|
|
13
|
+
h3 {
|
|
14
|
+
@apply text-[21px] leading-[140%] lg:text-[39px] lg:leading-[140%] font-bold;
|
|
15
|
+
}
|
|
16
|
+
h4 {
|
|
17
|
+
@apply text-[18px] leading-[140%] lg:text-[30px] lg:leading-[140%] font-bold;
|
|
18
|
+
}
|
|
19
|
+
h5 {
|
|
20
|
+
@apply text-[15px] leading-[140%] lg:text-[23px] lg:leading-[140%] font-bold;
|
|
21
|
+
}
|
|
22
|
+
h6 {
|
|
23
|
+
@apply text-[16px] leading-[140%] lg:text-[18px] lg:leading-[140%] font-bold;
|
|
24
|
+
}
|
|
25
|
+
p {
|
|
26
|
+
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%];
|
|
27
|
+
}
|
|
28
|
+
small {
|
|
29
|
+
@apply text-[14px] leading-[140%] lg:text-[16px] lg:leading-[140%];
|
|
30
|
+
}
|
|
31
|
+
a {
|
|
32
|
+
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%];
|
|
33
|
+
}
|
|
34
|
+
button {
|
|
35
|
+
@apply text-[15px] leading-[140%] lg:text-[18px] lg:leading-[140%] font-semibold;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
2
39
|
@tailwind components;
|
|
3
40
|
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
/* Add applies here */
|
|
@@ -0,0 +1,67 @@
|
|
|
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 }>();
|
|
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=RegioKonneX&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 dark:text-white">
|
|
43
|
+
Teilen
|
|
44
|
+
</div>
|
|
45
|
+
<button class="absolute top-5 right-5" @click="close()">
|
|
46
|
+
<span class="i-bi-x text-2xl hover:text-primary-500"></span>
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="text-center dark:text-white">
|
|
50
|
+
<div class="flex justify-around max-w-md mx-auto gap-3 py-2 mb-5 overflow-x-scroll">
|
|
51
|
+
<SocialMediaBubble title="E-Mail" bg-color="#bbbbbb" icon="i-bi-envelope" @click="shareWith('mail')" />
|
|
52
|
+
<SocialMediaBubble title="Whatsapp" bg-color="#25d366" icon="i-bi-whatsapp" @click="shareWith('whatsapp')" />
|
|
53
|
+
<SocialMediaBubble title="Facebook" bg-color="#3b5998" icon="i-bi-facebook" @click="shareWith('facebook')" />
|
|
54
|
+
<SocialMediaBubble title="LinkedIn" bg-color="#0a66c2" icon="i-bi-linkedin" @click="shareWith('linkedin')" />
|
|
55
|
+
<SocialMediaBubble title="X / Twitter" bg-color="#000000" icon="i-bi-twitter-x" @click="shareWith('twitter')" />
|
|
56
|
+
</div>
|
|
57
|
+
<div class="flex items-center gap-3 justify-between p-3 border border-gray-300 rounded-lg">
|
|
58
|
+
<span class="text-ellipsis overflow-hidden">
|
|
59
|
+
{{ url }}
|
|
60
|
+
</span>
|
|
61
|
+
<BaseButton size="sm" @click="copyUrl()">
|
|
62
|
+
Kopieren
|
|
63
|
+
</BaseButton>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</Modal>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
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>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
3
|
+
import { useElementSize } from '@vueuse/core';
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
expanded?: boolean;
|
|
7
|
+
}>(), {
|
|
8
|
+
expanded: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const show = ref(false);
|
|
12
|
+
const contents = ref<HTMLElement>();
|
|
13
|
+
|
|
14
|
+
watch(() => props.expanded, () => {
|
|
15
|
+
show.value = props.expanded;
|
|
16
|
+
}, { immediate: true });
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const { height: targetHeight } = useElementSize(contents, undefined, {
|
|
20
|
+
box: 'border-box',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const height = computed(() => (show.value ? targetHeight.value : 0));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<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">
|
|
28
|
+
<div class="flex cursor-pointer items-center justify-center p-5 md:p-6">
|
|
29
|
+
<div class="text-base text-gray-900 transition group-hover:text-gray-950 md:text-lg">
|
|
30
|
+
<slot name="title"></slot>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="relative ml-auto flex items-center justify-center">
|
|
34
|
+
<span
|
|
35
|
+
:class="{ 'rotate-180': show, 'rotate-45': !show }"
|
|
36
|
+
class="i-bi-x h-5 w-5 text-gray-900 transition-transform duration-500 md:h-6 md:w-6 dark:bg-white"
|
|
37
|
+
></span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
:style="{
|
|
43
|
+
height: `${height}px`,
|
|
44
|
+
}"
|
|
45
|
+
class="overflow-hidden px-5 transition-[height] duration-500 will-change-[height] md:px-6"
|
|
46
|
+
>
|
|
47
|
+
<div ref="contents" class="space-y-4 pb-5 font-light leading-relaxed tracking-wide text-gray-900 md:pb-6">
|
|
48
|
+
<slot></slot>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
@@ -0,0 +1,106 @@
|
|
|
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' | 'calendar';
|
|
12
|
+
color?: 'primary' | 'secondary' | 'green' | 'yellow' | 'lightgrey' | 'lightprimary' | 'danger';
|
|
13
|
+
textColor?: 'white' | 'black' | 'primary' | 'fullblack' | '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-full text-white',
|
|
33
|
+
outline:
|
|
34
|
+
'rounded-full border border-primary bg-white 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-black hover:bg-transparent',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const sizeClasses: Record<typeof props.size, string> = {
|
|
39
|
+
sm: 'min-w-[110px] py-1.5 px-4 text-sm',
|
|
40
|
+
calendar: 'min-w-[23%] py-1.5 px-5 text-base',
|
|
41
|
+
md: 'min-w-[200px] py-2 px-3 text-base',
|
|
42
|
+
lg: 'min-w-[240px] py-3 px-4 text-lg',
|
|
43
|
+
auto: 'text-sm lg:text-lg',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const colorClasses: Record<typeof props.color, string> = {
|
|
47
|
+
primary: 'bg-primary-500 hover:bg-primary-400 text-primary-50',
|
|
48
|
+
secondary: 'bg-orange-500 hover:bg-orange-400 text-orange-50',
|
|
49
|
+
green: 'bg-green-500 hover:bg-green-400 text-green-50',
|
|
50
|
+
yellow: 'bg-yellow-500 hover:bg-yellow-400 text-yellow-950',
|
|
51
|
+
danger: 'bg-red-500 hover:bg-red-400 text-red-950',
|
|
52
|
+
lightgrey: 'bg-slate-100 text-slate-200',
|
|
53
|
+
lightprimary: 'bg-primary-300 text-primary-50',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const textColorClasses: Record<typeof props.textColor, string> = {
|
|
57
|
+
white: 'text-white',
|
|
58
|
+
black: 'text-black dark:text-white',
|
|
59
|
+
fullblack: 'text-black dark:text-black',
|
|
60
|
+
primary: 'text-primary',
|
|
61
|
+
gray: 'text-gray-400',
|
|
62
|
+
'': '',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const LoadingIcon = defineComponent({
|
|
66
|
+
render: () =>
|
|
67
|
+
h('svg', { fill: 'none', viewBox: '0 0 24 24', class: 'animate-spin h-5 w-5 text-white' }, [
|
|
68
|
+
h('circle', {
|
|
69
|
+
class: 'opacity-25',
|
|
70
|
+
cx: '12',
|
|
71
|
+
cy: '12',
|
|
72
|
+
r: '10',
|
|
73
|
+
stroke: 'currentColor',
|
|
74
|
+
'stroke-width': '4',
|
|
75
|
+
}),
|
|
76
|
+
h('path', {
|
|
77
|
+
class: 'opacity-75',
|
|
78
|
+
fill: 'currentColor',
|
|
79
|
+
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',
|
|
80
|
+
}),
|
|
81
|
+
]),
|
|
82
|
+
});
|
|
83
|
+
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';
|
|
84
|
+
const classes = twMerge(defaultClasses, sizeClasses[props.size], colorClasses[props.color], appearanceClasses[props.appearance], textColorClasses[props.textColor]);
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<component
|
|
89
|
+
:is="props.href ? 'a' : props.to ? NuxtLink : 'button'"
|
|
90
|
+
:class="[
|
|
91
|
+
classes,
|
|
92
|
+
{
|
|
93
|
+
'w-full': props.block,
|
|
94
|
+
'cursor-wait': props.loading,
|
|
95
|
+
},
|
|
96
|
+
]"
|
|
97
|
+
:to="props.to"
|
|
98
|
+
:active-class="props.appearance === 'none' ? '!text-primary-500' : ''"
|
|
99
|
+
:href="props.href"
|
|
100
|
+
:type="props.type"
|
|
101
|
+
>
|
|
102
|
+
<LoadingIcon v-if="props.loading" />
|
|
103
|
+
<p v-if="props.loadingText && props.loading" v-text="props.loadingText"></p>
|
|
104
|
+
<slot v-else></slot>
|
|
105
|
+
</component>
|
|
106
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
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>
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
}" class="bg-[--accent-color] h-[5px] duration-100 transition-[width] w-[width] absolute bottom-0"
|
|
50
|
+
></div>
|
|
51
|
+
<div class="p-4 ">
|
|
52
|
+
<div class="flex items-start">
|
|
53
|
+
<div class="flex-shrink-0">
|
|
54
|
+
<span :class="icon[props.type]" class=" h-6 w-6 text-[--accent-color]"></span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="ml-3 w-0 flex-1 pt-0.5">
|
|
57
|
+
<p class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
58
|
+
{{ props.title }}
|
|
59
|
+
</p>
|
|
60
|
+
<p v-if="props.text" class="mt-1 text-sm text-gray-500 dark:text-white">
|
|
61
|
+
{{ props.text }}
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="ml-4 flex flex-shrink-0">
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
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"
|
|
68
|
+
@click="emit('close')"
|
|
69
|
+
>
|
|
70
|
+
<span class="sr-only">Close</span>
|
|
71
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
72
|
+
<path
|
|
73
|
+
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"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
|
8
|
+
tag="div" name="list" class="flex w-full flex-col items-center space-y-4 sm:items-end"
|
|
9
|
+
>
|
|
10
|
+
<BaseNotification v-for="notification in notifications" :key="notification.uuid" :text="notification.text" :title="notification.title" :type="notification.type" :duration="notification.duration!" @close="remove(notification.uuid)" />
|
|
11
|
+
</TransitionGroup>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
.list-move,
|
|
17
|
+
/* apply transition to moving elements */
|
|
18
|
+
.list-enter-active,
|
|
19
|
+
.list-leave-active {
|
|
20
|
+
transition: all 0.5s ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.list-enter-from,
|
|
24
|
+
.list-leave-to {
|
|
25
|
+
opacity: 0;
|
|
26
|
+
transform: translateX(30px);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ensure leaving items are taken out of layout flow so that moving
|
|
30
|
+
animations can be calculated correctly. */
|
|
31
|
+
.list-leave-active {
|
|
32
|
+
position: absolute;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(defineProps<{
|
|
3
|
+
percent: number | `${number}`;
|
|
4
|
+
duration?: number | `${number}`;
|
|
5
|
+
label?: string;
|
|
6
|
+
color?: 'primary' | 'red' | 'green' | 'blue';
|
|
7
|
+
}>(), {
|
|
8
|
+
duration: 400,
|
|
9
|
+
color: 'primary',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type RecordFromUnion<T extends string> = {
|
|
13
|
+
[K in T]: string
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ColorTypes = 'text' | 'background' | 'background-light';
|
|
17
|
+
|
|
18
|
+
const colorClass: Record<typeof props.color, RecordFromUnion<ColorTypes>> = {
|
|
19
|
+
'primary': {
|
|
20
|
+
'text': 'text-primary-600',
|
|
21
|
+
'background': 'bg-primary-500',
|
|
22
|
+
'background-light': 'bg-gray-200',
|
|
23
|
+
},
|
|
24
|
+
'red': {
|
|
25
|
+
'text': 'text-red-600',
|
|
26
|
+
'background': 'bg-red-500',
|
|
27
|
+
'background-light': 'bg-red-200',
|
|
28
|
+
},
|
|
29
|
+
'green': {
|
|
30
|
+
'text': 'text-green-600',
|
|
31
|
+
'background': 'bg-green-500',
|
|
32
|
+
'background-light': 'bg-green-200',
|
|
33
|
+
},
|
|
34
|
+
'blue': {
|
|
35
|
+
'text': 'text-blue-600',
|
|
36
|
+
'background': 'bg-blue-500',
|
|
37
|
+
'background-light': 'bg-blue-200',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div :style="`--percent: ${props.percent}%; --duration: ${props.duration}ms;`" class="relative pt-1 w-full">
|
|
44
|
+
<div v-if="props.label" class="flex mb-2 items-center justify-between">
|
|
45
|
+
<div>
|
|
46
|
+
<span
|
|
47
|
+
:class="[colorClass[props.color].text, colorClass[props.color]['background-light']]"
|
|
48
|
+
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full"
|
|
49
|
+
>
|
|
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 ">
|
|
55
|
+
{{ (+props.percent).toFixed(0) }}%
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div :class="colorClass[props.color]['background-light']" class="overflow-hidden h-1 mb-4 text-xs flex rounded bg-[--color-light]">
|
|
60
|
+
<div
|
|
61
|
+
:class="colorClass[props.color].background"
|
|
62
|
+
class="transform ease-in-out w-[--percent] duration-[--duration] shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center"
|
|
63
|
+
></div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(defineProps<{
|
|
3
|
+
active?: boolean;
|
|
4
|
+
}>(), {
|
|
5
|
+
active: false,
|
|
6
|
+
});
|
|
7
|
+
const toggleActive = ref(false);
|
|
8
|
+
|
|
9
|
+
watch(() => props.active, () => {
|
|
10
|
+
toggleActive.value = props.active;
|
|
11
|
+
}, { immediate: true });
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div class="flex justify-between items-center" @click="toggleActive = !toggleActive">
|
|
16
|
+
<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 }">
|
|
17
|
+
<div class="bg-white w-4 h-4 rounded-full shadow-md transform duration-300 ease-in-out" :class="{ 'translate-x-2': toggleActive }"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|