@wishbone-media/spark 0.2.1 → 0.3.0
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/README.md +9 -0
- package/dist/index.js +459 -246
- package/package.json +1 -1
- package/src/components/SparkButton.vue +162 -0
- package/src/components/SparkButtonGroup.vue +27 -0
- package/src/components/SparkModalContainer.vue +45 -0
- package/src/components/SparkModalDialog.vue +121 -0
- package/src/components/index.js +8 -7
- package/src/composables/index.js +3 -2
- package/src/composables/sparkModalService.js +26 -0
- package/src/components/SparkModal.vue +0 -109
package/package.json
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
ref="buttonRef"
|
|
5
|
+
:class="computedButtonClass"
|
|
6
|
+
:disabled="disabled"
|
|
7
|
+
@click="$emit('click')"
|
|
8
|
+
>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</button>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { computed, inject, ref } from 'vue'
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
size: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: 'md',
|
|
20
|
+
validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
|
|
21
|
+
},
|
|
22
|
+
variant: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'primary',
|
|
25
|
+
},
|
|
26
|
+
buttonClass: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: '',
|
|
29
|
+
},
|
|
30
|
+
disabled: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const buttonRef = ref(null)
|
|
37
|
+
const buttonGroup = inject('buttonGroup', null)
|
|
38
|
+
|
|
39
|
+
const groupPosition = computed(() => {
|
|
40
|
+
if (!buttonGroup?.isInGroup || !buttonRef.value) return null
|
|
41
|
+
|
|
42
|
+
const index = buttonGroup.getButtonIndex(buttonRef.value)
|
|
43
|
+
const total = buttonGroup.getButtonCount()
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
isFirst: index === 0,
|
|
47
|
+
isLast: index === total - 1,
|
|
48
|
+
index,
|
|
49
|
+
total,
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const CONFLICT_GROUPS = {
|
|
54
|
+
paddingX: /^px-/,
|
|
55
|
+
paddingY: /^py-/,
|
|
56
|
+
paddingAll: /^p-/,
|
|
57
|
+
marginX: /^mx-/,
|
|
58
|
+
marginY: /^my-/,
|
|
59
|
+
marginAll: /^m-/,
|
|
60
|
+
borderRadius: /^rounded-/,
|
|
61
|
+
background: /^bg-/,
|
|
62
|
+
text: /^text-(?!white|black)/,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mergeClasses = (baseClasses, overrideClasses) => {
|
|
66
|
+
if (!overrideClasses) return baseClasses
|
|
67
|
+
|
|
68
|
+
const base = baseClasses.split(' ').filter(Boolean)
|
|
69
|
+
const overrides = overrideClasses.split(' ').filter(Boolean)
|
|
70
|
+
|
|
71
|
+
const filtered = base.filter((baseClass) => {
|
|
72
|
+
return !overrides.some((override) => {
|
|
73
|
+
const baseGroup = Object.entries(CONFLICT_GROUPS).find(([_, regex]) =>
|
|
74
|
+
regex.test(baseClass),
|
|
75
|
+
)?.[0]
|
|
76
|
+
|
|
77
|
+
const overrideGroup = Object.entries(CONFLICT_GROUPS).find(([_, regex]) =>
|
|
78
|
+
regex.test(override),
|
|
79
|
+
)?.[0]
|
|
80
|
+
|
|
81
|
+
return baseGroup && baseGroup === overrideGroup
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return [...filtered, ...overrides].join(' ')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const computedButtonClass = computed(() => {
|
|
89
|
+
let classes = ''
|
|
90
|
+
let roundingSize = ''
|
|
91
|
+
|
|
92
|
+
switch (props.size) {
|
|
93
|
+
case 'xs':
|
|
94
|
+
classes += ' px-2 py-1 text-xs'
|
|
95
|
+
roundingSize = 'sm'
|
|
96
|
+
break
|
|
97
|
+
case 'sm':
|
|
98
|
+
classes += ' px-2 py-1 text-sm'
|
|
99
|
+
roundingSize = 'sm'
|
|
100
|
+
break
|
|
101
|
+
case 'md':
|
|
102
|
+
classes += ' px-2.5 py-1.5 text-sm'
|
|
103
|
+
roundingSize = 'md'
|
|
104
|
+
break
|
|
105
|
+
case 'lg':
|
|
106
|
+
classes += ' px-3 py-2 text-sm'
|
|
107
|
+
roundingSize = 'md'
|
|
108
|
+
break
|
|
109
|
+
case 'xl':
|
|
110
|
+
classes += ' px-3.5 py-2.5 text-sm'
|
|
111
|
+
roundingSize = 'md'
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (buttonGroup?.isInGroup && groupPosition.value) {
|
|
116
|
+
const { isFirst, isLast } = groupPosition.value
|
|
117
|
+
|
|
118
|
+
classes += ` relative inline-flex items-center focus:z-10`
|
|
119
|
+
|
|
120
|
+
if (isFirst && isLast) {
|
|
121
|
+
// Single button in group
|
|
122
|
+
classes += ` rounded-${roundingSize}`
|
|
123
|
+
} else if (isFirst) {
|
|
124
|
+
classes += ` rounded-l-${roundingSize} rounded-r-none`
|
|
125
|
+
} else if (isLast) {
|
|
126
|
+
classes += ` rounded-r-${roundingSize} rounded-l-none -ml-px`
|
|
127
|
+
} else {
|
|
128
|
+
classes += ' rounded-none -ml-px'
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
classes += ` shadow-xs rounded-${roundingSize}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
switch (props.variant) {
|
|
135
|
+
case 'primary':
|
|
136
|
+
classes += ' bg-primary-600 hover:bg-primary-500 text-white'
|
|
137
|
+
break
|
|
138
|
+
case 'secondary':
|
|
139
|
+
classes += ' ring-1 ring-gray-300 ring-inset bg-white hover:bg-gray-50 text-gray-900'
|
|
140
|
+
break
|
|
141
|
+
case 'success':
|
|
142
|
+
classes += ' bg-green-600 hover:bg-green-500 text-white'
|
|
143
|
+
break
|
|
144
|
+
case 'warning':
|
|
145
|
+
classes += ' bg-amber-600 hover:bg-amber-500 text-white'
|
|
146
|
+
break
|
|
147
|
+
case 'danger':
|
|
148
|
+
classes += ' bg-red-600 hover:bg-red-500 text-white'
|
|
149
|
+
break
|
|
150
|
+
case 'info':
|
|
151
|
+
classes += ' bg-cyan-600 hover:bg-cyan-500 text-white'
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
default:
|
|
155
|
+
classes += ` bg-${props.variant}-600 hover:bg-${props.variant}-500 text-white`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
classes = mergeClasses(classes, props.buttonClass)
|
|
159
|
+
|
|
160
|
+
return classes
|
|
161
|
+
})
|
|
162
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="inline-flex rounded-md shadow-xs" ref="groupRef">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { provide, ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
const groupRef = ref(null)
|
|
11
|
+
|
|
12
|
+
const getButtonIndex = (buttonElement) => {
|
|
13
|
+
if (!groupRef.value) return -1
|
|
14
|
+
const buttons = Array.from(groupRef.value.children)
|
|
15
|
+
return buttons.indexOf(buttonElement)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getButtonCount = () => {
|
|
19
|
+
return groupRef.value?.children.length || 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
provide('buttonGroup', {
|
|
23
|
+
isInGroup: true,
|
|
24
|
+
getButtonIndex,
|
|
25
|
+
getButtonCount,
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TransitionRoot as="template" :show="sparkModalService.state.isVisible">
|
|
3
|
+
<Dialog class="relative z-50" @close="sparkModalService.hide">
|
|
4
|
+
<TransitionChild
|
|
5
|
+
as="template"
|
|
6
|
+
enter="ease-out duration-300"
|
|
7
|
+
enter-from="opacity-0"
|
|
8
|
+
enter-to="opacity-100"
|
|
9
|
+
leave="ease-in duration-200"
|
|
10
|
+
leave-from="opacity-100"
|
|
11
|
+
leave-to="opacity-0"
|
|
12
|
+
>
|
|
13
|
+
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" />
|
|
14
|
+
</TransitionChild>
|
|
15
|
+
|
|
16
|
+
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
|
|
17
|
+
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
|
18
|
+
<TransitionChild
|
|
19
|
+
as="template"
|
|
20
|
+
enter="ease-out duration-300"
|
|
21
|
+
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
22
|
+
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
23
|
+
leave="ease-in duration-200"
|
|
24
|
+
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
25
|
+
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
26
|
+
>
|
|
27
|
+
<DialogPanel class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
|
28
|
+
<!-- Render dynamic component -->
|
|
29
|
+
<component
|
|
30
|
+
:is="sparkModalService.state.content"
|
|
31
|
+
v-bind="sparkModalService.state.props"
|
|
32
|
+
v-on="sparkModalService.state.eventHandlers"
|
|
33
|
+
/>
|
|
34
|
+
</DialogPanel>
|
|
35
|
+
</TransitionChild>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</Dialog>
|
|
39
|
+
</TransitionRoot>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup>
|
|
43
|
+
import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
|
44
|
+
import { sparkModalService } from '@/composables/sparkModalService'
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="px-4 pt-5 pb-4 sm:p-6">
|
|
3
|
+
<!-- Icon -->
|
|
4
|
+
<div v-if="iconToUse" class="mx-auto flex size-12 items-center justify-center rounded-full" :class="iconBgClass">
|
|
5
|
+
<font-awesome-icon :icon="Icons[iconToUse]" class="h-5 w-5" :class="iconTextClass" />
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<!-- Content -->
|
|
9
|
+
<div class="text-center" :class="{ 'mt-3 sm:mt-5': iconToUse }">
|
|
10
|
+
<!-- Title -->
|
|
11
|
+
<h3 v-if="title" class="text-lg font-medium text-gray-900">
|
|
12
|
+
{{ title }}
|
|
13
|
+
</h3>
|
|
14
|
+
|
|
15
|
+
<!-- Message -->
|
|
16
|
+
<div v-if="message" :class="{ 'mt-2': title }" class="text-sm text-gray-500">
|
|
17
|
+
{{ message }}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Actions -->
|
|
22
|
+
<div class="mt-5 sm:mt-6" :class="buttonContainerClass">
|
|
23
|
+
<spark-button
|
|
24
|
+
v-for="(button, index) in buttonsToShow"
|
|
25
|
+
:key="index"
|
|
26
|
+
:variant="button.variant"
|
|
27
|
+
@click="$emit(button.event, button)"
|
|
28
|
+
>
|
|
29
|
+
{{ button.text }}
|
|
30
|
+
</spark-button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import { computed } from 'vue'
|
|
37
|
+
import { Icons } from '@/plugins/fontawesome'
|
|
38
|
+
import { SparkButton } from "./index.js";
|
|
39
|
+
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
title: {
|
|
42
|
+
type: String,
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
message: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: '',
|
|
48
|
+
},
|
|
49
|
+
type: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: 'info',
|
|
52
|
+
validator: (value) => ['info', 'success', 'warning', 'danger'].includes(value),
|
|
53
|
+
},
|
|
54
|
+
icon: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: null,
|
|
57
|
+
},
|
|
58
|
+
buttons: {
|
|
59
|
+
type: Array,
|
|
60
|
+
default: () => [
|
|
61
|
+
{ text: 'OK', variant: 'primary', event: 'ok' }
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
defineEmits([
|
|
67
|
+
'ok', 'confirm', 'cancel', 'close', 'save', 'discard', 'delete',
|
|
68
|
+
'approve', 'reject', 'submit', 'reset', 'continue', 'retry',
|
|
69
|
+
'edit', 'view', 'download', 'upload', 'share', 'copy', 'input'
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
const buttonsToShow = computed(() => {
|
|
73
|
+
// Default to single OK button if no buttons provided
|
|
74
|
+
if (!props.buttons || props.buttons.length === 0) {
|
|
75
|
+
return [{ text: 'OK', variant: 'primary', event: 'ok' }]
|
|
76
|
+
}
|
|
77
|
+
return props.buttons
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const buttonContainerClass = computed(() => {
|
|
81
|
+
switch (buttonsToShow.value.length) {
|
|
82
|
+
case 1:
|
|
83
|
+
return 'sm:grid sm:grid-flow-row-dense'
|
|
84
|
+
case 2:
|
|
85
|
+
return 'sm:grid sm:grid-cols-2 sm:gap-3';
|
|
86
|
+
case 3:
|
|
87
|
+
return 'sm:grid sm:grid-cols-3 sm:gap-3';
|
|
88
|
+
default:
|
|
89
|
+
return 'flex flex-col gap-3';
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const defaultIcons = {
|
|
94
|
+
info: 'farInfoCircle',
|
|
95
|
+
success: 'farCheckCircle',
|
|
96
|
+
warning: 'farExclamationTriangle',
|
|
97
|
+
danger: 'farCircleXmark',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const iconToUse = computed(() => props.icon || defaultIcons[props.type])
|
|
101
|
+
|
|
102
|
+
const iconBgClass = computed(() => {
|
|
103
|
+
const colors = {
|
|
104
|
+
info: 'bg-blue-100',
|
|
105
|
+
success: 'bg-green-100',
|
|
106
|
+
warning: 'bg-yellow-100',
|
|
107
|
+
danger: 'bg-red-100',
|
|
108
|
+
}
|
|
109
|
+
return colors[props.type]
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const iconTextClass = computed(() => {
|
|
113
|
+
const colors = {
|
|
114
|
+
info: 'text-blue-400',
|
|
115
|
+
success: 'text-green-400',
|
|
116
|
+
warning: 'text-yellow-400',
|
|
117
|
+
danger: 'text-red-400',
|
|
118
|
+
}
|
|
119
|
+
return colors[props.type]
|
|
120
|
+
})
|
|
121
|
+
</script>
|
package/src/components/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export {
|
|
1
|
+
export { default as SparkAlert } from './SparkAlert.vue'
|
|
2
|
+
export { default as SparkAppSelector } from './SparkAppSelector.vue'
|
|
3
|
+
export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
|
|
4
|
+
export { default as SparkButton } from './SparkButton.vue'
|
|
5
|
+
export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
|
|
6
|
+
export { default as SparkModalContainer } from './SparkModalContainer.vue'
|
|
7
|
+
export { default as SparkModalDialog } from './SparkModalDialog.vue'
|
|
8
|
+
export { default as SparkOverlay } from './SparkOverlay.vue'
|
package/src/composables/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { sparkOverlayService } from './sparkOverlayService.js'
|
|
1
|
+
export { sparkModalService } from './sparkModalService.js'
|
|
2
|
+
export { sparkOverlayService } from './sparkOverlayService.js'
|
|
3
|
+
export { useSparkOverlay } from './useSparkOverlay.js'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { reactive, markRaw } from 'vue'
|
|
2
|
+
|
|
3
|
+
class SparkModalService {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.state = reactive({
|
|
6
|
+
isVisible: false,
|
|
7
|
+
content: null,
|
|
8
|
+
props: {},
|
|
9
|
+
eventHandlers: {},
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
show = (component, props = {}, eventHandlers = {}) => {
|
|
14
|
+
this.state.content = markRaw(component)
|
|
15
|
+
this.state.props = props
|
|
16
|
+
this.state.eventHandlers = eventHandlers
|
|
17
|
+
this.state.isVisible = true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
hide = () => {
|
|
21
|
+
this.state.isVisible = false
|
|
22
|
+
this.state.eventHandlers = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const sparkModalService = new SparkModalService()
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<TransitionRoot as="template" :show="open">
|
|
3
|
-
<Dialog class="relative z-50" @close="$emit('close')">
|
|
4
|
-
<TransitionChild
|
|
5
|
-
as="template"
|
|
6
|
-
enter="ease-out duration-300"
|
|
7
|
-
enter-from="opacity-0"
|
|
8
|
-
enter-to="opacity-100"
|
|
9
|
-
leave="ease-in duration-200"
|
|
10
|
-
leave-from="opacity-100"
|
|
11
|
-
leave-to="opacity-0"
|
|
12
|
-
>
|
|
13
|
-
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" />
|
|
14
|
-
</TransitionChild>
|
|
15
|
-
|
|
16
|
-
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
|
|
17
|
-
<div
|
|
18
|
-
class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
|
|
19
|
-
>
|
|
20
|
-
<TransitionChild
|
|
21
|
-
as="template"
|
|
22
|
-
enter="ease-out duration-300"
|
|
23
|
-
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
24
|
-
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
25
|
-
leave="ease-in duration-200"
|
|
26
|
-
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
27
|
-
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
28
|
-
>
|
|
29
|
-
<DialogPanel
|
|
30
|
-
:class="[
|
|
31
|
-
'relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:p-6',
|
|
32
|
-
sizeClasses,
|
|
33
|
-
]"
|
|
34
|
-
>
|
|
35
|
-
<div>
|
|
36
|
-
<!-- Icon -->
|
|
37
|
-
<div v-if="$slots.icon">
|
|
38
|
-
<slot name="icon" />
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div class="text-center sm:mt-5">
|
|
42
|
-
<!-- Header with title -->
|
|
43
|
-
<template v-if="$slots.title">
|
|
44
|
-
<DialogTitle as="div" class="text-base font-semibold text-gray-900">
|
|
45
|
-
<slot name="title" />
|
|
46
|
-
</DialogTitle>
|
|
47
|
-
</template>
|
|
48
|
-
|
|
49
|
-
<!-- Main content -->
|
|
50
|
-
<div :class="{ 'mt-2': $slots.title }">
|
|
51
|
-
<slot />
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<!-- Actions/Buttons -->
|
|
57
|
-
<div
|
|
58
|
-
v-if="$slots.actions"
|
|
59
|
-
class="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3"
|
|
60
|
-
>
|
|
61
|
-
<slot name="actions" />
|
|
62
|
-
</div>
|
|
63
|
-
</DialogPanel>
|
|
64
|
-
</TransitionChild>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</Dialog>
|
|
68
|
-
</TransitionRoot>
|
|
69
|
-
</template>
|
|
70
|
-
|
|
71
|
-
<script setup>
|
|
72
|
-
import { computed } from 'vue'
|
|
73
|
-
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
|
74
|
-
|
|
75
|
-
const props = defineProps({
|
|
76
|
-
open: {
|
|
77
|
-
type: Boolean,
|
|
78
|
-
required: true,
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
size: {
|
|
82
|
-
type: String,
|
|
83
|
-
default: 'md',
|
|
84
|
-
validator: (value) => ['sm', 'md', 'lg', 'xl', '2xl'].includes(value),
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
icon: {
|
|
88
|
-
type: String,
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
iconColor: {
|
|
92
|
-
type: String,
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
defineEmits(['close'])
|
|
97
|
-
|
|
98
|
-
const sizeClasses = computed(() => {
|
|
99
|
-
const sizes = {
|
|
100
|
-
sm: 'sm:max-w-sm',
|
|
101
|
-
md: 'sm:max-w-lg',
|
|
102
|
-
lg: 'sm:max-w-2xl',
|
|
103
|
-
xl: 'sm:max-w-4xl',
|
|
104
|
-
'2xl': 'sm:max-w-6xl',
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return sizes[props.size]
|
|
108
|
-
})
|
|
109
|
-
</script>
|