@fy-/fws-vue 2.1.6 → 2.1.7
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/components/fws/CmsArticleBoxed.vue +23 -20
- package/components/fws/CmsArticleSingle.vue +74 -68
- package/components/fws/DataTable.vue +132 -125
- package/components/fws/FilterData.vue +99 -101
- package/components/fws/UserData.vue +33 -32
- package/components/fws/UserFlow.vue +163 -155
- package/components/fws/UserOAuth2.vue +73 -72
- package/components/fws/UserProfile.vue +98 -101
- package/components/fws/UserProfileStrict.vue +65 -64
- package/components/ssr/ClientOnly.ts +7 -7
- package/components/ui/DefaultBreadcrumb.vue +13 -13
- package/components/ui/DefaultConfirm.vue +35 -34
- package/components/ui/DefaultDateSelection.vue +19 -17
- package/components/ui/DefaultDropdown.vue +25 -25
- package/components/ui/DefaultDropdownLink.vue +15 -14
- package/components/ui/DefaultGallery.vue +179 -168
- package/components/ui/DefaultInput.vue +121 -126
- package/components/ui/DefaultLoader.vue +17 -17
- package/components/ui/DefaultModal.vue +35 -33
- package/components/ui/DefaultNotif.vue +50 -52
- package/components/ui/DefaultPaging.vue +92 -95
- package/components/ui/DefaultSidebar.vue +29 -25
- package/components/ui/DefaultTagInput.vue +121 -119
- package/components/ui/transitions/CollapseTransition.vue +1 -1
- package/components/ui/transitions/ExpandTransition.vue +1 -1
- package/components/ui/transitions/FadeTransition.vue +1 -1
- package/components/ui/transitions/ScaleTransition.vue +1 -1
- package/components/ui/transitions/SlideTransition.vue +3 -3
- package/composables/event-bus.ts +10 -8
- package/composables/rest.ts +59 -56
- package/composables/seo.ts +106 -95
- package/composables/ssr.ts +64 -62
- package/composables/templating.ts +57 -57
- package/composables/translations.ts +13 -13
- package/env.d.ts +6 -4
- package/index.ts +101 -98
- package/package.json +7 -7
- package/stores/serverRouter.ts +25 -25
- package/stores/user.ts +79 -72
- package/types.d.ts +65 -65
|
@@ -1,112 +1,112 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import useVuelidate from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
2
|
+
import useVuelidate from '@vuelidate/core'
|
|
3
|
+
import { helpers, required } from '@vuelidate/validators'
|
|
4
|
+
import { computed, reactive, watchEffect } from 'vue'
|
|
5
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
6
|
+
import { useRest } from '../../composables/rest'
|
|
7
|
+
import { useTranslation } from '../../composables/translations'
|
|
8
|
+
import { useUserStore } from '../../stores/user'
|
|
9
|
+
import DefaultInput from '../ui/DefaultInput.vue'
|
|
10
10
|
|
|
11
|
-
const rest = useRest()
|
|
11
|
+
const rest = useRest()
|
|
12
12
|
const props = withDefaults(
|
|
13
13
|
defineProps<{
|
|
14
|
-
onCompleted?: (data: any) => void
|
|
15
|
-
termsText?: string
|
|
16
|
-
force18?: boolean
|
|
14
|
+
onCompleted?: (data: any) => void
|
|
15
|
+
termsText?: string
|
|
16
|
+
force18?: boolean
|
|
17
17
|
}>(),
|
|
18
18
|
{
|
|
19
19
|
onCompleted: () => {},
|
|
20
|
-
termsText:
|
|
20
|
+
termsText: '',
|
|
21
21
|
force18: false,
|
|
22
22
|
},
|
|
23
|
-
)
|
|
24
|
-
const currentDate = new Date()
|
|
23
|
+
)
|
|
24
|
+
const currentDate = new Date()
|
|
25
25
|
const defaultDate = new Date(
|
|
26
26
|
currentDate.setFullYear(currentDate.getFullYear() - 18),
|
|
27
27
|
)
|
|
28
28
|
.toISOString()
|
|
29
|
-
.split(
|
|
29
|
+
.split('T')[0]
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
const today = new Date()
|
|
33
|
-
const birthDate = new Date(value)
|
|
34
|
-
let age = today.getFullYear() - birthDate.getFullYear()
|
|
35
|
-
const m = today.getMonth() - birthDate.getMonth()
|
|
36
|
-
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) age
|
|
37
|
-
return age >= 18 && age <= 2020
|
|
38
|
-
}
|
|
39
|
-
const userStore = useUserStore()
|
|
40
|
-
const userData = computed(() => userStore.user)
|
|
41
|
-
const eventBus = useEventBus()
|
|
31
|
+
function ageValidator(value: any) {
|
|
32
|
+
const today = new Date()
|
|
33
|
+
const birthDate = new Date(value)
|
|
34
|
+
let age = today.getFullYear() - birthDate.getFullYear()
|
|
35
|
+
const m = today.getMonth() - birthDate.getMonth()
|
|
36
|
+
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) age--
|
|
37
|
+
return age >= 18 && age <= 2020
|
|
38
|
+
}
|
|
39
|
+
const userStore = useUserStore()
|
|
40
|
+
const userData = computed(() => userStore.user)
|
|
41
|
+
const eventBus = useEventBus()
|
|
42
42
|
const state = reactive({
|
|
43
43
|
userData: {
|
|
44
|
-
Username: userData.value?.UserProfile?.Username ||
|
|
45
|
-
Gender: userData.value?.UserProfile?.Gender ||
|
|
44
|
+
Username: userData.value?.UserProfile?.Username || '',
|
|
45
|
+
Gender: userData.value?.UserProfile?.Gender || '',
|
|
46
46
|
Birthdate: userData.value?.UserProfile?.Birthdate || defaultDate,
|
|
47
47
|
AcceptedTerms: userData.value?.AcceptedTerms || true,
|
|
48
48
|
},
|
|
49
|
-
})
|
|
49
|
+
})
|
|
50
50
|
watchEffect(() => {
|
|
51
51
|
state.userData = {
|
|
52
|
-
Username: userData.value?.UserProfile?.Username ||
|
|
53
|
-
Gender: userData.value?.UserProfile?.Gender ||
|
|
52
|
+
Username: userData.value?.UserProfile?.Username || '',
|
|
53
|
+
Gender: userData.value?.UserProfile?.Gender || '',
|
|
54
54
|
Birthdate: userData.value?.UserProfile?.Birthdate
|
|
55
55
|
? new Date(userData.value?.UserProfile?.Birthdate.unixms)
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
.toISOString()
|
|
57
|
+
.split('T')[0]
|
|
58
58
|
: defaultDate,
|
|
59
59
|
AcceptedTerms: userData.value?.AcceptedTerms || true,
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
const translate = useTranslation()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
const translate = useTranslation()
|
|
63
63
|
const rules = {
|
|
64
64
|
userData: {
|
|
65
65
|
Username: {
|
|
66
|
-
required
|
|
66
|
+
required,
|
|
67
67
|
},
|
|
68
68
|
Gender: {
|
|
69
|
-
required
|
|
69
|
+
required,
|
|
70
70
|
},
|
|
71
71
|
Birthdate: {
|
|
72
72
|
required,
|
|
73
73
|
ageValidator: props.force18
|
|
74
74
|
? helpers.withMessage(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
translate('fws_under_18_error_message'),
|
|
76
|
+
ageValidator,
|
|
77
|
+
)
|
|
78
78
|
: undefined,
|
|
79
79
|
},
|
|
80
80
|
AcceptedTerms: {
|
|
81
|
-
required
|
|
81
|
+
required,
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
|
-
}
|
|
85
|
-
const v$ = useVuelidate(rules, state)
|
|
84
|
+
}
|
|
85
|
+
const v$ = useVuelidate(rules, state)
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
eventBus.emit(
|
|
87
|
+
async function patchUser() {
|
|
88
|
+
eventBus.emit('main-loading', true)
|
|
89
89
|
if (await v$.value.userData.$validate()) {
|
|
90
|
-
const data = { ...state.userData }
|
|
91
|
-
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`)
|
|
90
|
+
const data = { ...state.userData }
|
|
91
|
+
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`)
|
|
92
92
|
try {
|
|
93
|
-
const birtdateAtUnixms = birtdate.getTime()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// @ts-
|
|
98
|
-
data.Birthdate = data.Birthdate.toISOString().split(
|
|
93
|
+
const birtdateAtUnixms = birtdate.getTime()
|
|
94
|
+
data.Birthdate = new Date(birtdateAtUnixms).toISOString().split('T')[0]
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// @ts-expect-error: Birthdate is a string
|
|
98
|
+
data.Birthdate = data.Birthdate.toISOString().split('T')[0]
|
|
99
99
|
}
|
|
100
|
-
const response = await rest(
|
|
101
|
-
if (response && response.result
|
|
100
|
+
const response = await rest('User/_ForceProfile', 'PATCH', data)
|
|
101
|
+
if (response && response.result === 'success') {
|
|
102
102
|
if (props.onCompleted) {
|
|
103
|
-
props.onCompleted(response)
|
|
103
|
+
props.onCompleted(response)
|
|
104
104
|
}
|
|
105
|
-
eventBus.emit(
|
|
105
|
+
eventBus.emit('user:refresh', true)
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
eventBus.emit(
|
|
109
|
-
}
|
|
108
|
+
eventBus.emit('main-loading', false)
|
|
109
|
+
}
|
|
110
110
|
</script>
|
|
111
111
|
|
|
112
112
|
<template>
|
|
@@ -121,12 +121,11 @@ const patchUser = async () => {
|
|
|
121
121
|
:error-vuelidate="v$.userData.Username.$errors"
|
|
122
122
|
:disabled="userData?.UserProfile?.HasUsernameAndSlug ? true : false"
|
|
123
123
|
/>
|
|
124
|
-
<!-- @vue-skip -->
|
|
125
124
|
<DefaultInput
|
|
126
125
|
id="birthdateFWS"
|
|
127
126
|
v-model="state.userData.Birthdate"
|
|
128
127
|
class="mb-4"
|
|
129
|
-
:
|
|
128
|
+
:disable-dates-under18="true"
|
|
130
129
|
type="datepicker"
|
|
131
130
|
:label="$t('fws_birthdate_label')"
|
|
132
131
|
:error-vuelidate="v$.userData.Birthdate.$errors"
|
|
@@ -154,7 +153,9 @@ const patchUser = async () => {
|
|
|
154
153
|
:help="$t('fws_accepted_terms_help')"
|
|
155
154
|
:error-vuelidate="v$.userData.AcceptedTerms.$errors"
|
|
156
155
|
/>
|
|
157
|
-
<p
|
|
156
|
+
<p v-if="props.termsText" class="terms-box">
|
|
157
|
+
{{ props.termsText }}
|
|
158
|
+
</p>
|
|
158
159
|
|
|
159
160
|
<div class="flex">
|
|
160
161
|
<button type="submit" class="btn defaults primary">
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineComponent, onMounted, ref } from 'vue'
|
|
2
2
|
|
|
3
3
|
export const ClientOnly = defineComponent({
|
|
4
|
-
__name:
|
|
4
|
+
__name: 'ClientOnly',
|
|
5
5
|
setup(_, { slots }) {
|
|
6
|
-
const show = ref(false)
|
|
6
|
+
const show = ref(false)
|
|
7
7
|
onMounted(() => {
|
|
8
|
-
show.value = true
|
|
9
|
-
})
|
|
10
|
-
return () => (show.value && slots.default ? slots.default() : null)
|
|
8
|
+
show.value = true
|
|
9
|
+
})
|
|
10
|
+
return () => (show.value && slots.default ? slots.default() : null)
|
|
11
11
|
},
|
|
12
|
-
})
|
|
12
|
+
})
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { defineBreadcrumb, useSchemaOrg } from
|
|
2
|
+
import type { BreadcrumbLink } from '../../types'
|
|
3
|
+
import { ChevronRightIcon, HomeIcon } from '@heroicons/vue/24/solid'
|
|
4
|
+
import { defineBreadcrumb, useSchemaOrg } from '@unhead/schema-org'
|
|
5
5
|
|
|
6
6
|
const props = withDefaults(
|
|
7
7
|
defineProps<{
|
|
8
|
-
nav: BreadcrumbLink[]
|
|
9
|
-
showHome:
|
|
8
|
+
nav: BreadcrumbLink[]
|
|
9
|
+
showHome: boolean
|
|
10
10
|
}>(),
|
|
11
11
|
{
|
|
12
12
|
nav: () => [],
|
|
13
13
|
showHome: () => true,
|
|
14
14
|
},
|
|
15
|
-
)
|
|
15
|
+
)
|
|
16
16
|
|
|
17
17
|
const breadcrumbsSchemaFormat = props.nav.map((item, index) => {
|
|
18
18
|
return {
|
|
19
19
|
position: index + 1,
|
|
20
20
|
name: item.name,
|
|
21
21
|
item: item.to,
|
|
22
|
-
}
|
|
23
|
-
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
24
|
|
|
25
25
|
useSchemaOrg([
|
|
26
26
|
defineBreadcrumb({
|
|
27
27
|
itemListElement: breadcrumbsSchemaFormat,
|
|
28
28
|
}),
|
|
29
|
-
])
|
|
29
|
+
])
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<template>
|
|
@@ -34,9 +34,9 @@ useSchemaOrg([
|
|
|
34
34
|
<template v-for="(item, index) in nav" :key="`bc_${index.toString()}`">
|
|
35
35
|
<li class="inline-flex items-center">
|
|
36
36
|
<ChevronRightIcon
|
|
37
|
-
v-if="index
|
|
37
|
+
v-if="index !== 0"
|
|
38
38
|
:class="
|
|
39
|
-
index
|
|
39
|
+
index === 0
|
|
40
40
|
? 'w-4 h-4 mr-2 inline-block'
|
|
41
41
|
: 'w-5 h-5 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
42
42
|
"
|
|
@@ -46,7 +46,7 @@ useSchemaOrg([
|
|
|
46
46
|
v-if="item.to"
|
|
47
47
|
:to="item.to"
|
|
48
48
|
:class="
|
|
49
|
-
index
|
|
49
|
+
index === 0
|
|
50
50
|
? 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
|
|
51
51
|
: 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
|
|
52
52
|
"
|
|
@@ -54,7 +54,7 @@ useSchemaOrg([
|
|
|
54
54
|
<HomeIcon
|
|
55
55
|
v-if="showHome && index === 0"
|
|
56
56
|
:class="
|
|
57
|
-
index
|
|
57
|
+
index === 0
|
|
58
58
|
? 'w-4 h-4 mr-2 inline-block'
|
|
59
59
|
: 'w-4 h-4 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
60
60
|
"
|
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { useEventBus } from
|
|
4
|
-
import DefaultModal from
|
|
2
|
+
import { onMounted, onUnmounted, ref } from 'vue'
|
|
3
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
4
|
+
import DefaultModal from './DefaultModal.vue'
|
|
5
5
|
|
|
6
|
-
const eventBus = useEventBus()
|
|
7
|
-
const title = ref<string | null>(null)
|
|
8
|
-
const desc = ref<string | null>(null)
|
|
9
|
-
const onConfirm = ref<Function | null>(null)
|
|
6
|
+
const eventBus = useEventBus()
|
|
7
|
+
const title = ref<string | null>(null)
|
|
8
|
+
const desc = ref<string | null>(null)
|
|
9
|
+
const onConfirm = ref<Function | null>(null)
|
|
10
10
|
interface ConfirmModalData {
|
|
11
|
-
title: string
|
|
12
|
-
desc: string
|
|
13
|
-
onConfirm: Function
|
|
11
|
+
title: string
|
|
12
|
+
desc: string
|
|
13
|
+
onConfirm: Function
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
async function _onConfirm() {
|
|
16
16
|
if (onConfirm.value) {
|
|
17
|
-
await onConfirm.value()
|
|
17
|
+
await onConfirm.value()
|
|
18
18
|
}
|
|
19
|
-
resetConfirm()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
title.value = null
|
|
23
|
-
desc.value = null
|
|
24
|
-
onConfirm.value = null
|
|
25
|
-
eventBus.emit(
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
title.value = data.title
|
|
29
|
-
desc.value = data.desc
|
|
30
|
-
onConfirm.value = data.onConfirm
|
|
31
|
-
eventBus.emit(
|
|
32
|
-
}
|
|
19
|
+
resetConfirm()
|
|
20
|
+
}
|
|
21
|
+
function resetConfirm() {
|
|
22
|
+
title.value = null
|
|
23
|
+
desc.value = null
|
|
24
|
+
onConfirm.value = null
|
|
25
|
+
eventBus.emit('confirmModal', false)
|
|
26
|
+
}
|
|
27
|
+
function showConfirm(data: ConfirmModalData) {
|
|
28
|
+
title.value = data.title
|
|
29
|
+
desc.value = data.desc
|
|
30
|
+
onConfirm.value = data.onConfirm
|
|
31
|
+
eventBus.emit('confirmModal', true)
|
|
32
|
+
}
|
|
33
33
|
|
|
34
34
|
onMounted(() => {
|
|
35
|
-
eventBus.on(
|
|
36
|
-
eventBus.on(
|
|
37
|
-
})
|
|
35
|
+
eventBus.on('resetConfirm', resetConfirm)
|
|
36
|
+
eventBus.on('showConfirm', showConfirm)
|
|
37
|
+
})
|
|
38
38
|
onUnmounted(() => {
|
|
39
|
-
eventBus.off(
|
|
40
|
-
eventBus.off(
|
|
41
|
-
})
|
|
39
|
+
eventBus.off('resetConfirm', resetConfirm)
|
|
40
|
+
eventBus.off('showConfirm', showConfirm)
|
|
41
|
+
})
|
|
42
42
|
</script>
|
|
43
|
+
|
|
43
44
|
<template>
|
|
44
45
|
<DefaultModal id="confirm">
|
|
45
46
|
<div
|
|
@@ -49,9 +50,9 @@ onUnmounted(() => {
|
|
|
49
50
|
<p
|
|
50
51
|
class="mb-3 !text-left prose prose-invert prose-sm !min-w-full"
|
|
51
52
|
v-html="
|
|
52
|
-
desc ?
|
|
53
|
+
desc ? `<h2>${title}</h2>${desc}` : `<h2>${title}</h2>`
|
|
53
54
|
"
|
|
54
|
-
|
|
55
|
+
/>
|
|
55
56
|
<div class="flex justify-between gap-3 mt-4">
|
|
56
57
|
<button class="btn danger defaults" @click="_onConfirm()">
|
|
57
58
|
{{ $t("confirm_modal_cta_confirm") }}
|
|
@@ -1,40 +1,42 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { computed } from
|
|
3
|
-
import { DefaultInput } from
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { DefaultInput } from '../..'
|
|
4
|
+
|
|
4
5
|
interface DateInterval {
|
|
5
|
-
$between: [any, any]
|
|
6
|
+
$between: [any, any]
|
|
6
7
|
}
|
|
7
8
|
const props = withDefaults(
|
|
8
9
|
defineProps<{
|
|
9
|
-
mode?:
|
|
10
|
-
modelValue?: DateInterval
|
|
11
|
-
id: string
|
|
12
|
-
label?: string
|
|
10
|
+
mode?: 'interval' | 'single'
|
|
11
|
+
modelValue?: DateInterval
|
|
12
|
+
id: string
|
|
13
|
+
label?: string
|
|
13
14
|
}>(),
|
|
14
15
|
{
|
|
15
|
-
mode:
|
|
16
|
+
mode: 'single',
|
|
16
17
|
modelValue: () => {
|
|
17
|
-
return { $between: [undefined, undefined] }
|
|
18
|
+
return { $between: [undefined, undefined] }
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
|
-
)
|
|
21
|
+
)
|
|
21
22
|
|
|
22
|
-
const emit = defineEmits([
|
|
23
|
+
const emit = defineEmits(['update:modelValue'])
|
|
23
24
|
|
|
24
25
|
const model = computed({
|
|
25
26
|
get: () => props.modelValue,
|
|
26
27
|
set: (items) => {
|
|
27
|
-
emit(
|
|
28
|
+
emit('update:modelValue', items)
|
|
28
29
|
},
|
|
29
|
-
})
|
|
30
|
+
})
|
|
30
31
|
</script>
|
|
32
|
+
|
|
31
33
|
<template>
|
|
32
|
-
<div v-if="mode
|
|
34
|
+
<div v-if="mode === 'interval' && model">
|
|
33
35
|
<div class="flex flex-col md:flex-row">
|
|
34
36
|
<DefaultInput
|
|
37
|
+
:id="`${id}_start`"
|
|
35
38
|
v-model="model.$between[0]"
|
|
36
39
|
type="date"
|
|
37
|
-
:id="`${id}_start`"
|
|
38
40
|
class="w-full"
|
|
39
41
|
:label="`${label} (${$t('date_selection_start')})`"
|
|
40
42
|
/>
|
|
@@ -42,15 +44,15 @@ const model = computed({
|
|
|
42
44
|
<div>↭</div>
|
|
43
45
|
</div>
|
|
44
46
|
<DefaultInput
|
|
47
|
+
:id="`${id}_end`"
|
|
45
48
|
v-model="model.$between[1]"
|
|
46
49
|
type="date"
|
|
47
50
|
class="w-full"
|
|
48
|
-
:id="`${id}_end`"
|
|
49
51
|
:label="`${label} (${$t('date_selection_end')})`"
|
|
50
52
|
/>
|
|
51
53
|
</div>
|
|
52
54
|
</div>
|
|
53
55
|
<div v-else>
|
|
54
|
-
<DefaultInput v-model="model.$between[0]" type="date"
|
|
56
|
+
<DefaultInput :id="id" v-model="model.$between[0]" type="date" />
|
|
55
57
|
</div>
|
|
56
58
|
</template>
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { onMounted, onUnmounted } from
|
|
2
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
3
3
|
|
|
4
|
-
import ScaleTransition from
|
|
4
|
+
import ScaleTransition from './transitions/ScaleTransition.vue'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
|
-
show: boolean
|
|
8
|
-
handleClickOutside: any
|
|
9
|
-
preventClickOutside?: boolean
|
|
7
|
+
show: boolean
|
|
8
|
+
handleClickOutside: any
|
|
9
|
+
preventClickOutside?: boolean
|
|
10
10
|
coordinates?: {
|
|
11
|
-
left?: string
|
|
12
|
-
right?: string
|
|
13
|
-
top?: string
|
|
14
|
-
bottom?: string
|
|
15
|
-
}
|
|
16
|
-
position: string[]
|
|
17
|
-
closeDropdown: () => void
|
|
18
|
-
}>()
|
|
11
|
+
left?: string
|
|
12
|
+
right?: string
|
|
13
|
+
top?: string
|
|
14
|
+
bottom?: string
|
|
15
|
+
}
|
|
16
|
+
position: string[]
|
|
17
|
+
closeDropdown: () => void
|
|
18
|
+
}>()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
if ([
|
|
22
|
-
props.closeDropdown()
|
|
20
|
+
function handleCloseOnEscape(event: KeyboardEvent) {
|
|
21
|
+
if (['Escape', 'Esc'].includes(event.key)) {
|
|
22
|
+
props.closeDropdown()
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
onMounted(() => {
|
|
27
|
-
document.addEventListener(
|
|
28
|
-
})
|
|
27
|
+
document.addEventListener('keydown', handleCloseOnEscape)
|
|
28
|
+
})
|
|
29
29
|
|
|
30
30
|
onUnmounted(() => {
|
|
31
|
-
document.removeEventListener(
|
|
32
|
-
})
|
|
31
|
+
document.removeEventListener('keydown', handleCloseOnEscape)
|
|
32
|
+
})
|
|
33
33
|
</script>
|
|
34
34
|
|
|
35
35
|
<template>
|
|
@@ -37,14 +37,14 @@ onUnmounted(() => {
|
|
|
37
37
|
<div
|
|
38
38
|
v-if="props.show"
|
|
39
39
|
class="fixed left-0 top-0 z-[50] w-full h-full"
|
|
40
|
-
|
|
40
|
+
/>
|
|
41
41
|
|
|
42
42
|
<ScaleTransition>
|
|
43
43
|
<div
|
|
44
|
-
:class="props.position"
|
|
45
|
-
:style="props.coordinates"
|
|
46
44
|
v-show="props.show"
|
|
47
45
|
v-click-outside="props.handleClickOutside"
|
|
46
|
+
:class="props.position"
|
|
47
|
+
:style="props.coordinates"
|
|
48
48
|
class="absolute z-[100] w-[200px] mt-2 rounded-sm bg-white dark:bg-fv-neutral-900 shadow-lg border border-fv-neutral-100 dark:border-fv-neutral-600 focus:outline-none"
|
|
49
49
|
role="menu"
|
|
50
50
|
aria-orientation="vertical"
|
|
@@ -52,7 +52,7 @@ onUnmounted(() => {
|
|
|
52
52
|
tabindex="-1"
|
|
53
53
|
>
|
|
54
54
|
<div role="none">
|
|
55
|
-
<slot
|
|
55
|
+
<slot />
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
</ScaleTransition>
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from
|
|
2
|
+
import { computed } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps<{
|
|
5
|
-
handleClick?: () => void
|
|
6
|
-
label?: string
|
|
7
|
-
color?: string
|
|
8
|
-
}>()
|
|
5
|
+
handleClick?: () => void
|
|
6
|
+
label?: string
|
|
7
|
+
color?: string
|
|
8
|
+
}>()
|
|
9
9
|
|
|
10
10
|
const baseClasses = `w-full px-4 py-3 flex items-center border-b opacity-60
|
|
11
11
|
dark:opacity-70 outline-none text-sm border-fv-neutral-200 dark:border-fv-neutral-600
|
|
12
|
-
transition-all duration-200
|
|
12
|
+
transition-all duration-200`
|
|
13
13
|
|
|
14
14
|
const colorClasses = computed(() => {
|
|
15
|
-
if (props.color ===
|
|
16
|
-
return
|
|
17
|
-
}
|
|
15
|
+
if (props.color === 'danger') {
|
|
16
|
+
return 'text-red-500 dark:hover:text-red-50 hover:bg-red-50 active:bg-red-100 dark:hover:bg-red-900'
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
18
19
|
return `text-black dark:text-white active:bg-fv-neutral-100 dark:hover:bg-fv-neutral-600
|
|
19
|
-
dark:focus:bg-fv-neutral-600 hover:bg-fv-neutral-50
|
|
20
|
+
dark:focus:bg-fv-neutral-600 hover:bg-fv-neutral-50`
|
|
20
21
|
}
|
|
21
|
-
})
|
|
22
|
+
})
|
|
22
23
|
|
|
23
|
-
const classes = computed(() => `${baseClasses} ${colorClasses.value}`)
|
|
24
|
+
const classes = computed(() => `${baseClasses} ${colorClasses.value}`)
|
|
24
25
|
</script>
|
|
25
26
|
|
|
26
27
|
<template>
|
|
27
28
|
<button
|
|
28
29
|
:aria-label="props.label"
|
|
29
|
-
@click.prevent="props.handleClick"
|
|
30
30
|
:class="classes"
|
|
31
31
|
role="menuitem"
|
|
32
32
|
type="button"
|
|
33
|
+
@click.prevent="props.handleClick"
|
|
33
34
|
>
|
|
34
|
-
<slot
|
|
35
|
+
<slot />
|
|
35
36
|
</button>
|
|
36
37
|
</template>
|