@wishbone-media/spark 0.36.0 → 0.38.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/dist/index.js +7772 -3148
- package/package.json +3 -1
- package/src/assets/css/spark-fancybox.css +7 -0
- package/src/assets/css/spark-tooltip.css +44 -0
- package/src/components/SparkEntityBadge.vue +175 -0
- package/src/components/SparkFancybox.vue +59 -0
- package/src/components/SparkImageUpload.vue +4 -3
- package/src/components/SparkTooltip.vue +162 -0
- package/src/components/index.js +3 -0
- package/src/directives/sparkTooltip.js +228 -0
- package/src/plugins/fontawesome.js +8 -0
- package/src/plugins/index.js +2 -1
- package/src/plugins/tooltip.js +11 -0
- package/src/utils/sparkTable/renderers/image.js +20 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishbone-media/spark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -65,6 +65,8 @@
|
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
+
"@fancyapps/ui": "^6.1.13",
|
|
69
|
+
"@floating-ui/vue": "^1.1.11",
|
|
68
70
|
"@googlemaps/js-api-loader": "^2.0.2"
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SparkTooltip & v-spark-tooltip styles
|
|
3
|
+
*
|
|
4
|
+
* Import in consuming app:
|
|
5
|
+
* @import '@wishbone-media/spark/assets/css/spark-tooltip.css';
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.spark-tooltip {
|
|
9
|
+
z-index: 9999;
|
|
10
|
+
max-width: 300px;
|
|
11
|
+
padding: 0.375rem 0.625rem;
|
|
12
|
+
font-size: 0.75rem;
|
|
13
|
+
line-height: 1.25rem;
|
|
14
|
+
color: #fff;
|
|
15
|
+
background: #1f2937; /* gray-800 */
|
|
16
|
+
border-radius: 0.375rem;
|
|
17
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
word-wrap: break-word;
|
|
20
|
+
white-space: pre-line;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.spark-tooltip-arrow {
|
|
24
|
+
position: absolute;
|
|
25
|
+
width: 8px;
|
|
26
|
+
height: 8px;
|
|
27
|
+
background: #1f2937; /* same as tooltip bg */
|
|
28
|
+
transform: rotate(45deg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Transitions (for SparkTooltip component) */
|
|
32
|
+
.spark-tooltip-enter-active {
|
|
33
|
+
transition: opacity 150ms ease, transform 150ms ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.spark-tooltip-leave-active {
|
|
37
|
+
transition: opacity 100ms ease, transform 100ms ease;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.spark-tooltip-enter-from,
|
|
41
|
+
.spark-tooltip-leave-to {
|
|
42
|
+
opacity: 0;
|
|
43
|
+
transform: scale(0.95);
|
|
44
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="isLink ? 'a' : 'span'"
|
|
4
|
+
:href="resolvedHref || undefined"
|
|
5
|
+
:target="isLink && openNew ? '_blank' : undefined"
|
|
6
|
+
:rel="isLink && openNew ? 'noopener noreferrer' : undefined"
|
|
7
|
+
v-spark-tooltip="tooltip"
|
|
8
|
+
class="inline-flex items-center gap-1 rounded-full font-medium whitespace-nowrap"
|
|
9
|
+
:class="[variantClasses, sizeClasses, interactiveClasses]"
|
|
10
|
+
@click="handleClick"
|
|
11
|
+
>
|
|
12
|
+
<font-awesome-icon v-if="icon" :icon="icon" />
|
|
13
|
+
<slot>{{ label }}</slot>
|
|
14
|
+
<font-awesome-icon
|
|
15
|
+
v-if="copyToClipboard"
|
|
16
|
+
:icon="Icons.farCopy"
|
|
17
|
+
class="opacity-60"
|
|
18
|
+
/>
|
|
19
|
+
<font-awesome-icon
|
|
20
|
+
v-else-if="isNavigable && openNew"
|
|
21
|
+
:icon="Icons.farArrowUpRightFromSquare"
|
|
22
|
+
class="opacity-60 text-[0.6em]"
|
|
23
|
+
/>
|
|
24
|
+
</component>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { computed } from 'vue'
|
|
29
|
+
import { useRouter } from 'vue-router'
|
|
30
|
+
import { Icons } from '../plugins/fontawesome'
|
|
31
|
+
import { sparkNotificationService } from '../composables/sparkNotificationService'
|
|
32
|
+
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
/** Display text */
|
|
35
|
+
label: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: '',
|
|
38
|
+
},
|
|
39
|
+
/** FontAwesome icon object */
|
|
40
|
+
icon: {
|
|
41
|
+
type: [Object, Array],
|
|
42
|
+
default: null,
|
|
43
|
+
},
|
|
44
|
+
/** Color scheme */
|
|
45
|
+
variant: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: 'primary',
|
|
48
|
+
},
|
|
49
|
+
/** Size: 'sm', 'md', 'lg' */
|
|
50
|
+
size: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: 'md',
|
|
53
|
+
validator: (v) => ['sm', 'md', 'lg'].includes(v),
|
|
54
|
+
},
|
|
55
|
+
/** Tooltip text (native title attribute) */
|
|
56
|
+
tooltip: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
/** Vue Router route name for navigation */
|
|
61
|
+
routeName: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: null,
|
|
64
|
+
},
|
|
65
|
+
/** Entity ID — used with routeName as { name, params: { id } } */
|
|
66
|
+
entityId: {
|
|
67
|
+
type: [Number, String],
|
|
68
|
+
default: null,
|
|
69
|
+
},
|
|
70
|
+
/** External link URL */
|
|
71
|
+
href: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: null,
|
|
74
|
+
},
|
|
75
|
+
/** Open link in new tab */
|
|
76
|
+
openNew: {
|
|
77
|
+
type: Boolean,
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
/** Copy tooltip content to clipboard on click */
|
|
81
|
+
copyToClipboard: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: false,
|
|
84
|
+
},
|
|
85
|
+
/** Disable all interaction */
|
|
86
|
+
disabled: {
|
|
87
|
+
type: Boolean,
|
|
88
|
+
default: false,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const emit = defineEmits(['click'])
|
|
93
|
+
|
|
94
|
+
const router = useRouter()
|
|
95
|
+
|
|
96
|
+
// --- Variant classes ---
|
|
97
|
+
|
|
98
|
+
const VARIANT_CLASSES = {
|
|
99
|
+
primary: 'bg-blue-100 text-blue-700',
|
|
100
|
+
secondary: 'bg-gray-100 text-gray-600',
|
|
101
|
+
success: 'bg-green-100 text-green-700',
|
|
102
|
+
warning: 'bg-yellow-100 text-yellow-700',
|
|
103
|
+
danger: 'bg-red-100 text-red-700',
|
|
104
|
+
info: 'bg-cyan-100 text-cyan-700',
|
|
105
|
+
muted: 'bg-gray-50 text-gray-500',
|
|
106
|
+
bordered: 'border border-gray-300 text-gray-700 bg-white',
|
|
107
|
+
'bordered-blue': 'border border-blue-300 text-blue-600 bg-white',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SIZE_CLASSES = {
|
|
111
|
+
sm: 'text-[11px] px-1.5 py-0.5',
|
|
112
|
+
md: 'text-xs px-2 py-0.5',
|
|
113
|
+
lg: 'text-sm px-2.5 py-1',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const variantClasses = computed(() => VARIANT_CLASSES[props.variant] || VARIANT_CLASSES.primary)
|
|
117
|
+
const sizeClasses = computed(() => SIZE_CLASSES[props.size] || SIZE_CLASSES.md)
|
|
118
|
+
|
|
119
|
+
// --- Navigation ---
|
|
120
|
+
|
|
121
|
+
const isNavigable = computed(
|
|
122
|
+
() => (props.routeName && props.entityId != null) || props.href,
|
|
123
|
+
)
|
|
124
|
+
const isInteractive = computed(() => isNavigable.value || props.copyToClipboard)
|
|
125
|
+
const isLink = computed(() => isNavigable.value && !props.disabled)
|
|
126
|
+
|
|
127
|
+
const resolvedHref = computed(() => {
|
|
128
|
+
if (props.href) return props.href
|
|
129
|
+
if (props.routeName && props.entityId != null) {
|
|
130
|
+
try {
|
|
131
|
+
return router.resolve({ name: props.routeName, params: { id: props.entityId } }).href
|
|
132
|
+
} catch {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const interactiveClasses = computed(() => {
|
|
140
|
+
if (props.disabled) return 'opacity-50 cursor-not-allowed'
|
|
141
|
+
if (isInteractive.value) return 'cursor-pointer hover:opacity-80 transition-opacity'
|
|
142
|
+
return 'cursor-default'
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// --- Click handling ---
|
|
146
|
+
|
|
147
|
+
function handleClick(event) {
|
|
148
|
+
if (props.disabled) return
|
|
149
|
+
|
|
150
|
+
emit('click')
|
|
151
|
+
|
|
152
|
+
if (props.copyToClipboard && props.tooltip) {
|
|
153
|
+
event.preventDefault()
|
|
154
|
+
navigator.clipboard.writeText(props.tooltip)
|
|
155
|
+
sparkNotificationService.toast({ type: 'info', message: 'Copied to clipboard' })
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// href links: native <a> handles openNew via target="_blank"
|
|
160
|
+
// For same-tab href, let native <a> handle it
|
|
161
|
+
if (props.href) return
|
|
162
|
+
|
|
163
|
+
// Route-based navigation
|
|
164
|
+
if (props.routeName && props.entityId != null) {
|
|
165
|
+
event.preventDefault()
|
|
166
|
+
const route = { name: props.routeName, params: { id: props.entityId } }
|
|
167
|
+
if (props.openNew) {
|
|
168
|
+
const resolved = router.resolve(route)
|
|
169
|
+
window.open(resolved.href, '_blank')
|
|
170
|
+
} else {
|
|
171
|
+
router.push(route)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="containerRef">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
/**
|
|
9
|
+
* Vue 3 wrapper around Fancybox.
|
|
10
|
+
* Wraps a container element and binds Fancybox to all [data-fancybox] children.
|
|
11
|
+
* Handles lifecycle: bind on mount, rebind on update (for dynamic content), unbind on unmount.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* <SparkFancybox>
|
|
15
|
+
* <a href="full.jpg" data-fancybox="gallery" data-caption="Photo #1">
|
|
16
|
+
* <img src="thumb.jpg" />
|
|
17
|
+
* </a>
|
|
18
|
+
* </SparkFancybox>
|
|
19
|
+
*/
|
|
20
|
+
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
|
|
21
|
+
import { Fancybox } from '@fancyapps/ui/dist/fancybox/'
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
/** Fancybox configuration options */
|
|
25
|
+
options: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: () => ({}),
|
|
28
|
+
},
|
|
29
|
+
/** CSS selector for bindable elements within the container */
|
|
30
|
+
selector: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: '[data-fancybox]',
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const containerRef = ref(null)
|
|
37
|
+
|
|
38
|
+
function bind() {
|
|
39
|
+
if (!containerRef.value) return
|
|
40
|
+
Fancybox.bind(containerRef.value, props.selector, { ...props.options })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function unbind() {
|
|
44
|
+
if (!containerRef.value) return
|
|
45
|
+
Fancybox.unbind(containerRef.value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onMounted(() => bind())
|
|
49
|
+
|
|
50
|
+
onUpdated(() => {
|
|
51
|
+
unbind()
|
|
52
|
+
bind()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
onUnmounted(() => {
|
|
56
|
+
unbind()
|
|
57
|
+
Fancybox.close()
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
3
|
<label v-if="label" class="block text-sm font-medium text-gray-700 mb-2">{{ label }}</label>
|
|
4
|
-
<
|
|
5
|
-
<a :href="modelValue"
|
|
4
|
+
<SparkFancybox v-if="modelValue" class="mb-2 relative inline-block">
|
|
5
|
+
<a :href="modelValue" data-fancybox="image-upload" :data-caption="label || ''">
|
|
6
6
|
<img
|
|
7
7
|
:src="modelValue"
|
|
8
8
|
:alt="label || 'Image preview'"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
>
|
|
18
18
|
<font-awesome-icon :icon="Icons.farXmark" class="text-xs" />
|
|
19
19
|
</button>
|
|
20
|
-
</
|
|
20
|
+
</SparkFancybox>
|
|
21
21
|
<div
|
|
22
22
|
v-if="!modelValue"
|
|
23
23
|
class="relative rounded-md transition-colors"
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
import { ref, inject } from 'vue'
|
|
52
52
|
import { Icons } from '@/plugins/fontawesome'
|
|
53
53
|
import { sparkNotificationService } from '@/composables/sparkNotificationService'
|
|
54
|
+
import SparkFancybox from './SparkFancybox.vue'
|
|
54
55
|
|
|
55
56
|
const props = defineProps({
|
|
56
57
|
modelValue: {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="referenceRef"
|
|
4
|
+
class="inline-flex"
|
|
5
|
+
@mouseenter="show"
|
|
6
|
+
@mouseleave="hide"
|
|
7
|
+
@focusin="show"
|
|
8
|
+
@focusout="hide"
|
|
9
|
+
>
|
|
10
|
+
<slot />
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<Teleport to="body">
|
|
14
|
+
<Transition name="spark-tooltip">
|
|
15
|
+
<div
|
|
16
|
+
v-if="isVisible"
|
|
17
|
+
ref="floatingRef"
|
|
18
|
+
:style="floatingStyles"
|
|
19
|
+
class="spark-tooltip"
|
|
20
|
+
role="tooltip"
|
|
21
|
+
>
|
|
22
|
+
<slot name="content">
|
|
23
|
+
<span v-if="html" v-html="content" />
|
|
24
|
+
<span v-else>{{ content }}</span>
|
|
25
|
+
</slot>
|
|
26
|
+
<div
|
|
27
|
+
v-if="showArrow"
|
|
28
|
+
ref="arrowRef"
|
|
29
|
+
class="spark-tooltip-arrow"
|
|
30
|
+
:style="arrowStyles"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
</Transition>
|
|
34
|
+
</Teleport>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup>
|
|
38
|
+
/**
|
|
39
|
+
* Tooltip component using @floating-ui/vue for positioning.
|
|
40
|
+
* Wraps a trigger element (default slot) and displays a floating tooltip on hover/focus.
|
|
41
|
+
*
|
|
42
|
+
* For simple text tooltips, prefer the v-spark-tooltip directive.
|
|
43
|
+
* Use this component when you need slot-based HTML content or complex tooltip layouts.
|
|
44
|
+
*/
|
|
45
|
+
import { ref, computed, useSlots, onUnmounted } from 'vue'
|
|
46
|
+
import {
|
|
47
|
+
useFloating,
|
|
48
|
+
autoUpdate,
|
|
49
|
+
offset as offsetMiddleware,
|
|
50
|
+
flip,
|
|
51
|
+
shift,
|
|
52
|
+
arrow as arrowMiddleware,
|
|
53
|
+
} from '@floating-ui/vue'
|
|
54
|
+
|
|
55
|
+
const props = defineProps({
|
|
56
|
+
/** Tooltip text (alternative to #content slot) */
|
|
57
|
+
content: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: '',
|
|
60
|
+
},
|
|
61
|
+
/** Render content prop as HTML */
|
|
62
|
+
html: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
/** Tooltip placement */
|
|
67
|
+
placement: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: 'top',
|
|
70
|
+
},
|
|
71
|
+
/** Distance in px from trigger */
|
|
72
|
+
offset: {
|
|
73
|
+
type: Number,
|
|
74
|
+
default: 8,
|
|
75
|
+
},
|
|
76
|
+
/** Show/hide delay in ms. Number applies to show only; object for { show, hide } */
|
|
77
|
+
delay: {
|
|
78
|
+
type: [Number, Object],
|
|
79
|
+
default: () => ({ show: 200, hide: 0 }),
|
|
80
|
+
},
|
|
81
|
+
/** Disable tooltip */
|
|
82
|
+
disabled: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false,
|
|
85
|
+
},
|
|
86
|
+
/** Show arrow pointing to trigger */
|
|
87
|
+
showArrow: {
|
|
88
|
+
type: Boolean,
|
|
89
|
+
default: true,
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const slots = useSlots()
|
|
94
|
+
|
|
95
|
+
const referenceRef = ref(null)
|
|
96
|
+
const floatingRef = ref(null)
|
|
97
|
+
const arrowRef = ref(null)
|
|
98
|
+
const isVisible = ref(false)
|
|
99
|
+
|
|
100
|
+
let showTimeout = null
|
|
101
|
+
let hideTimeout = null
|
|
102
|
+
|
|
103
|
+
const middleware = computed(() => {
|
|
104
|
+
const mw = [
|
|
105
|
+
offsetMiddleware(props.offset),
|
|
106
|
+
flip(),
|
|
107
|
+
shift({ padding: 8 }),
|
|
108
|
+
]
|
|
109
|
+
if (props.showArrow) {
|
|
110
|
+
mw.push(arrowMiddleware({ element: arrowRef, padding: 5 }))
|
|
111
|
+
}
|
|
112
|
+
return mw
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const { floatingStyles, middlewareData, placement: actualPlacement } = useFloating(
|
|
116
|
+
referenceRef,
|
|
117
|
+
floatingRef,
|
|
118
|
+
{
|
|
119
|
+
placement: computed(() => props.placement),
|
|
120
|
+
middleware,
|
|
121
|
+
whileElementsMounted: autoUpdate,
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const arrowStyles = computed(() => {
|
|
126
|
+
const data = middlewareData.value?.arrow
|
|
127
|
+
if (!data) return {}
|
|
128
|
+
|
|
129
|
+
const side = actualPlacement.value.split('-')[0]
|
|
130
|
+
const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[side]
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
left: data.x != null ? `${data.x}px` : '',
|
|
134
|
+
top: data.y != null ? `${data.y}px` : '',
|
|
135
|
+
[staticSide]: '-4px',
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
function show() {
|
|
140
|
+
if (props.disabled) return
|
|
141
|
+
if (!props.content && !slots.content) return
|
|
142
|
+
|
|
143
|
+
clearTimeout(hideTimeout)
|
|
144
|
+
const delay = typeof props.delay === 'number' ? props.delay : props.delay.show ?? 200
|
|
145
|
+
showTimeout = setTimeout(() => {
|
|
146
|
+
isVisible.value = true
|
|
147
|
+
}, delay)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hide() {
|
|
151
|
+
clearTimeout(showTimeout)
|
|
152
|
+
const delay = typeof props.delay === 'number' ? 0 : props.delay.hide ?? 0
|
|
153
|
+
hideTimeout = setTimeout(() => {
|
|
154
|
+
isVisible.value = false
|
|
155
|
+
}, delay)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onUnmounted(() => {
|
|
159
|
+
clearTimeout(showTimeout)
|
|
160
|
+
clearTimeout(hideTimeout)
|
|
161
|
+
})
|
|
162
|
+
</script>
|
package/src/components/index.js
CHANGED
|
@@ -7,6 +7,9 @@ export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
|
|
|
7
7
|
export { default as SparkButton } from './SparkButton.vue'
|
|
8
8
|
export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
|
|
9
9
|
export { default as SparkCard } from './SparkCard.vue'
|
|
10
|
+
export { default as SparkEntityBadge } from './SparkEntityBadge.vue'
|
|
11
|
+
export { default as SparkFancybox } from './SparkFancybox.vue'
|
|
12
|
+
export { default as SparkTooltip } from './SparkTooltip.vue'
|
|
10
13
|
export { default as SparkFileDragUpload } from './SparkFileDragUpload.vue'
|
|
11
14
|
export { default as SparkImageUpload } from './SparkImageUpload.vue'
|
|
12
15
|
export { default as SparkModalContainer } from './SparkModalContainer.vue'
|