@fy-/fws-vue 2.3.80 → 2.3.81
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/ui/DefaultConfirmWithInput.vue +174 -0
- package/index.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useDebounceFn } from '@vueuse/core'
|
|
3
|
+
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
|
4
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
5
|
+
import DefaultInput from './DefaultInput.vue'
|
|
6
|
+
import DefaultModal from './DefaultModal.vue'
|
|
7
|
+
|
|
8
|
+
const eventBus = useEventBus()
|
|
9
|
+
const title = ref<string | null>(null)
|
|
10
|
+
const desc = ref<string | null>(null)
|
|
11
|
+
const inputLabel = ref<string | undefined>(undefined)
|
|
12
|
+
const inputPlaceholder = ref<string | undefined>(undefined)
|
|
13
|
+
const expectedValue = ref<string | null>(null)
|
|
14
|
+
const errorMessage = ref<string | null>(null)
|
|
15
|
+
const onConfirm = ref<Function | null>(null)
|
|
16
|
+
const isOpen = ref<boolean>(false)
|
|
17
|
+
const modalRef = ref<HTMLElement | null>(null)
|
|
18
|
+
const inputValue = ref<string>('')
|
|
19
|
+
const inputError = ref<string>('')
|
|
20
|
+
let previouslyFocusedElement: HTMLElement | null = null
|
|
21
|
+
|
|
22
|
+
interface ConfirmWithInputModalData {
|
|
23
|
+
title: string
|
|
24
|
+
desc: string
|
|
25
|
+
inputLabel?: string
|
|
26
|
+
inputPlaceholder?: string
|
|
27
|
+
expectedValue?: string
|
|
28
|
+
errorMessage?: string
|
|
29
|
+
onConfirm: (inputValue: string) => void | Promise<void>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const _onConfirm = useDebounceFn(async () => {
|
|
33
|
+
// If expectedValue is set, validate the input
|
|
34
|
+
if (expectedValue.value && inputValue.value !== expectedValue.value) {
|
|
35
|
+
inputError.value = errorMessage.value || 'Incorrect value entered'
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (onConfirm.value) {
|
|
40
|
+
await onConfirm.value(inputValue.value)
|
|
41
|
+
}
|
|
42
|
+
resetConfirm()
|
|
43
|
+
}, 300)
|
|
44
|
+
|
|
45
|
+
function resetConfirm() {
|
|
46
|
+
title.value = null
|
|
47
|
+
desc.value = null
|
|
48
|
+
inputLabel.value = undefined
|
|
49
|
+
inputPlaceholder.value = undefined
|
|
50
|
+
expectedValue.value = null
|
|
51
|
+
errorMessage.value = null
|
|
52
|
+
onConfirm.value = null
|
|
53
|
+
isOpen.value = false
|
|
54
|
+
inputValue.value = ''
|
|
55
|
+
inputError.value = ''
|
|
56
|
+
eventBus.emit('confirmWithInputModal', false)
|
|
57
|
+
if (previouslyFocusedElement) {
|
|
58
|
+
previouslyFocusedElement.focus()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showConfirmWithInput(data: ConfirmWithInputModalData) {
|
|
63
|
+
title.value = data.title
|
|
64
|
+
desc.value = data.desc
|
|
65
|
+
inputLabel.value = data.inputLabel || undefined
|
|
66
|
+
inputPlaceholder.value = data.inputPlaceholder || undefined
|
|
67
|
+
expectedValue.value = data.expectedValue || null
|
|
68
|
+
errorMessage.value = data.errorMessage || null
|
|
69
|
+
onConfirm.value = data.onConfirm
|
|
70
|
+
inputValue.value = ''
|
|
71
|
+
inputError.value = ''
|
|
72
|
+
|
|
73
|
+
// Emit event first to ensure it's registered before opening the modal
|
|
74
|
+
eventBus.emit('confirmWithInputModal', true)
|
|
75
|
+
|
|
76
|
+
// Use requestAnimationFrame instead of setTimeout for better performance
|
|
77
|
+
requestAnimationFrame(() => {
|
|
78
|
+
isOpen.value = true
|
|
79
|
+
eventBus.emit('confirmWithInputModal', true)
|
|
80
|
+
|
|
81
|
+
nextTick(() => {
|
|
82
|
+
previouslyFocusedElement = document.activeElement as HTMLElement
|
|
83
|
+
try {
|
|
84
|
+
modalRef.value?.focus()
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
eventBus.on('resetConfirmWithInput', resetConfirm)
|
|
94
|
+
eventBus.on('showConfirmWithInput', showConfirmWithInput)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
onUnmounted(() => {
|
|
98
|
+
eventBus.off('resetConfirmWithInput', resetConfirm)
|
|
99
|
+
eventBus.off('showConfirmWithInput', showConfirmWithInput)
|
|
100
|
+
})
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<DefaultModal
|
|
105
|
+
id="confirmWithInput"
|
|
106
|
+
ref="modalRef"
|
|
107
|
+
m-size="!max-w-3xl w-full"
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
class="bg-gradient-to-br from-gray-900/70 to-gray-800/50 rounded-lg border border-gray-700/30 overflow-hidden"
|
|
111
|
+
:aria-labelledby="title ? 'confirm-input-modal-title' : undefined"
|
|
112
|
+
:aria-describedby="desc ? 'confirm-input-modal-desc' : undefined"
|
|
113
|
+
aria-modal="true"
|
|
114
|
+
role="dialog"
|
|
115
|
+
tabindex="-1"
|
|
116
|
+
>
|
|
117
|
+
<!-- Header with gradient background -->
|
|
118
|
+
<div
|
|
119
|
+
v-if="title"
|
|
120
|
+
class="bg-gradient-to-r from-indigo-900/30 to-indigo-800/20 p-4 border-b border-indigo-700/30"
|
|
121
|
+
>
|
|
122
|
+
<h3
|
|
123
|
+
id="confirm-input-modal-title"
|
|
124
|
+
class="text-xl font-semibold text-white"
|
|
125
|
+
>
|
|
126
|
+
{{ title }}
|
|
127
|
+
</h3>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Content area with styled box -->
|
|
131
|
+
<div class="p-5 text-fv-neutral-100">
|
|
132
|
+
<div v-if="desc" class="bg-gradient-to-r from-blue-950/50 to-indigo-950/50 p-4 rounded-lg border border-blue-700/30 mb-6 shadow-md">
|
|
133
|
+
<p
|
|
134
|
+
id="confirm-input-modal-desc"
|
|
135
|
+
class="text-sm sm:text-base text-gray-200 prose prose-invert prose-sm min-w-full"
|
|
136
|
+
v-html="desc"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- Input field -->
|
|
141
|
+
<div class="mb-6">
|
|
142
|
+
<DefaultInput
|
|
143
|
+
id="confirmInput"
|
|
144
|
+
v-model="inputValue"
|
|
145
|
+
type="text"
|
|
146
|
+
:label="inputLabel"
|
|
147
|
+
:placeholder="inputPlaceholder"
|
|
148
|
+
:error="inputError"
|
|
149
|
+
autocomplete="off"
|
|
150
|
+
class="w-full"
|
|
151
|
+
@keyup.enter="_onConfirm()"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Action buttons with modern styling -->
|
|
156
|
+
<div class="flex justify-center gap-4 mt-6">
|
|
157
|
+
<button
|
|
158
|
+
class="btn accent large shadow-lg hover:shadow-fuchsia-500/30 transition-all duration-300 hover:scale-105 active:scale-95 px-8 rounded-lg"
|
|
159
|
+
:disabled="!inputValue.trim()"
|
|
160
|
+
@click="_onConfirm()"
|
|
161
|
+
>
|
|
162
|
+
{{ $t("confirm_modal_cta_confirm") }}
|
|
163
|
+
</button>
|
|
164
|
+
<button
|
|
165
|
+
class="btn neutral large shadow-md transform transition hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-gray-500 px-8 rounded-lg"
|
|
166
|
+
@click="resetConfirm()"
|
|
167
|
+
>
|
|
168
|
+
{{ $t("confirm_modal_cta_cancel") }}
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</DefaultModal>
|
|
174
|
+
</template>
|
package/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import UserProfileStrict from './components/fws/UserProfileStrict.vue'
|
|
|
17
17
|
import { ClientOnly } from './components/ssr/ClientOnly'
|
|
18
18
|
import DefaultBreadcrumb from './components/ui/DefaultBreadcrumb.vue'
|
|
19
19
|
import DefaultConfirm from './components/ui/DefaultConfirm.vue'
|
|
20
|
+
import DefaultConfirmWithInput from './components/ui/DefaultConfirmWithInput.vue'
|
|
20
21
|
import DefaultDropdown from './components/ui/DefaultDropdown.vue'
|
|
21
22
|
|
|
22
23
|
import DefaultDropdownLink from './components/ui/DefaultDropdownLink.vue'
|
|
@@ -103,6 +104,7 @@ export {
|
|
|
103
104
|
DataTable,
|
|
104
105
|
DefaultBreadcrumb,
|
|
105
106
|
DefaultConfirm,
|
|
107
|
+
DefaultConfirmWithInput,
|
|
106
108
|
DefaultDropdown,
|
|
107
109
|
DefaultDropdownLink,
|
|
108
110
|
DefaultGallery,
|