@wishbone-media/spark 0.1.4 → 0.2.1
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 +334 -110
- package/package.json +4 -3
- package/src/assets/images/mr-antenna.png +0 -0
- package/src/assets/images/mr-gutter-cleaning.png +0 -0
- package/src/assets/images/mr-pest-controller.png +0 -0
- package/src/components/SparkAppSelector.vue +120 -0
- package/src/components/SparkBrandSelector.vue +60 -0
- package/src/components/SparkOverlay.vue +28 -18
- package/src/components/index.js +3 -1
- package/src/composables/index.js +1 -0
- package/src/composables/sparkOverlayService.js +31 -0
- package/src/composables/useSparkOverlay.js +9 -4
- package/src/index.js +2 -1
- package/src/plugins/fontawesome.js +14 -0
- package/src/stores/brand-filter.js +40 -0
- package/src/stores/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishbone-media/spark",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"@headlessui/vue": "^1.7.0",
|
|
13
13
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
14
14
|
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
|
15
|
-
"
|
|
15
|
+
"pinia": "^3.0.3",
|
|
16
|
+
"vue": "^3.5.16"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
19
20
|
"vite": "^6.3.5",
|
|
20
|
-
"vue": "^3.5.
|
|
21
|
+
"vue": "^3.5.16"
|
|
21
22
|
},
|
|
22
23
|
"packageManager": "pnpm@10.11.1",
|
|
23
24
|
"scripts": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white rounded-lg">
|
|
3
|
+
<div class="flex flex-1 flex-col">
|
|
4
|
+
<div class="divide-y divide-gray-200">
|
|
5
|
+
<div class="flex px-[22px] py-2.5 text-[12px] items-center">
|
|
6
|
+
<div>Mr Group Network</div>
|
|
7
|
+
<div class="ml-auto flex items-center">
|
|
8
|
+
<font-awesome-icon
|
|
9
|
+
:icon="Icons.farTimes"
|
|
10
|
+
class="h-[15px] w-[15px] shrink-0 text-gray-400 cursor-pointer"
|
|
11
|
+
@click="emit('close')"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
v-for="app in appsWithCurrent"
|
|
17
|
+
:key="app.name"
|
|
18
|
+
:class="app.current ? 'bg-gray-50' : 'hover:bg-gray-50'"
|
|
19
|
+
class="flex px-[22px] py-[15px] cursor-pointer"
|
|
20
|
+
@click="openNewTab(app)"
|
|
21
|
+
>
|
|
22
|
+
<div class="gap-y-1">
|
|
23
|
+
<div class="text-base text-gray-800 flex items-center">
|
|
24
|
+
<div class="font-medium">{{ app.name }}</div>
|
|
25
|
+
<span
|
|
26
|
+
v-if="app.current"
|
|
27
|
+
class="inline-flex items-center rounded-full bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700 ml-1"
|
|
28
|
+
>
|
|
29
|
+
Active
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="text-sm text-gray-500">{{ app.description }}</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="ml-auto flex items-center">
|
|
35
|
+
<font-awesome-icon
|
|
36
|
+
:class="app.current ? 'text-gray-700' : 'text-gray-400'"
|
|
37
|
+
:icon="Icons[app.icon]"
|
|
38
|
+
class="h-5 w-5 shrink-0"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="mt-auto">
|
|
45
|
+
<div class="p-6">Learn More</div>
|
|
46
|
+
<div class="bg-gray-50 p-6">Footer</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup>
|
|
53
|
+
import { computed } from 'vue'
|
|
54
|
+
import { Icons } from '@/plugins/fontawesome'
|
|
55
|
+
|
|
56
|
+
const props = defineProps({
|
|
57
|
+
appItems: {
|
|
58
|
+
type: Array,
|
|
59
|
+
default() {
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
name: '3CX',
|
|
63
|
+
description: 'VOIP Phone',
|
|
64
|
+
href: 'https://3cx.letsbolt.com.au',
|
|
65
|
+
icon: 'farLaptopMobile',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Buzz',
|
|
69
|
+
description: 'Communication on the go',
|
|
70
|
+
href: 'https://buzz.letsbolt.com.au',
|
|
71
|
+
icon: 'farSatelliteDish',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Dash',
|
|
75
|
+
description: 'Financial powerhouse',
|
|
76
|
+
href: 'https://dash.letsbolt.com.au',
|
|
77
|
+
icon: 'farScaleBalanced',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'MAPit',
|
|
81
|
+
description: 'Geolocation everything',
|
|
82
|
+
href: 'https://mapit.letsbolt.com.au',
|
|
83
|
+
icon: 'farStreetView',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'ProspectR',
|
|
87
|
+
description: 'Leads management',
|
|
88
|
+
href: 'https://prospectr.letsbolt.com.au',
|
|
89
|
+
icon: 'farFaceSmileRelaxed',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'ReVuze',
|
|
93
|
+
description: 'Get Customer feedback',
|
|
94
|
+
href: 'https://revuze.letsbolt.com.au',
|
|
95
|
+
icon: 'farComments',
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
currentApp: {
|
|
101
|
+
type: String,
|
|
102
|
+
default: 'Buzz',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const emit = defineEmits(['close', 'select'])
|
|
107
|
+
|
|
108
|
+
const appsWithCurrent = computed(() => {
|
|
109
|
+
return props.appItems.map(app => ({
|
|
110
|
+
...app,
|
|
111
|
+
current: app.name === props.currentApp
|
|
112
|
+
}))
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const openNewTab = (app) => {
|
|
116
|
+
window.open(app.href, '_blank')
|
|
117
|
+
|
|
118
|
+
emit('select', app)
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white rounded-lg">
|
|
3
|
+
<div class="flex flex-1 flex-col">
|
|
4
|
+
<div class="divide-y divide-gray-200">
|
|
5
|
+
<div class="flex px-[22px] py-2.5 text-[12px] items-center">
|
|
6
|
+
<div>Filter by Brand</div>
|
|
7
|
+
<div class="ml-auto flex items-center">
|
|
8
|
+
<font-awesome-icon
|
|
9
|
+
:icon="Icons.farTimes"
|
|
10
|
+
class="size-4 text-gray-400 cursor-pointer"
|
|
11
|
+
@click="emit('close')"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
v-for="brand in sparkBrandFilterStore.allBrands"
|
|
17
|
+
:key="brand.name"
|
|
18
|
+
:class="brand.current ? 'bg-gray-50' : 'hover:bg-gray-50'"
|
|
19
|
+
class="flex px-[22px] py-[15px] cursor-pointer"
|
|
20
|
+
@click="selectBrand(brand)"
|
|
21
|
+
>
|
|
22
|
+
<div class="gap-y-1 flex">
|
|
23
|
+
<div class="flex items-center mr-4">
|
|
24
|
+
<img :src="brand.logo" :alt="`${brand.name} logo`" class="h-8 w-auto" />
|
|
25
|
+
</div>
|
|
26
|
+
<div class="ml-auto flex flex-col">
|
|
27
|
+
<div class="text-base text-gray-800 flex items-center">
|
|
28
|
+
<div class="font-medium">{{ brand.name }}</div>
|
|
29
|
+
<span
|
|
30
|
+
v-if="brand.current"
|
|
31
|
+
class="inline-flex items-center rounded-full bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700 ml-1"
|
|
32
|
+
>
|
|
33
|
+
Current
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="text-sm text-gray-500">
|
|
37
|
+
{{ brand.current ? 'Current Brand' : 'Change to' }}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="mt-auto"></div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup>
|
|
50
|
+
import { useSparkBrandFilterStore } from '@/stores/brand-filter'
|
|
51
|
+
import { Icons } from '@/plugins/fontawesome'
|
|
52
|
+
|
|
53
|
+
const emit = defineEmits(['close', 'select'])
|
|
54
|
+
|
|
55
|
+
const sparkBrandFilterStore = useSparkBrandFilterStore()
|
|
56
|
+
|
|
57
|
+
const selectBrand = (brand) => {
|
|
58
|
+
emit('select', brand)
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<TransitionRoot :show="overlayInstance.state.isVisible" as="template">
|
|
3
|
-
<Dialog
|
|
3
|
+
<Dialog
|
|
4
|
+
:initialFocus="panelRef"
|
|
5
|
+
class="relative z-200"
|
|
6
|
+
@close="overlayInstance.close"
|
|
7
|
+
>
|
|
4
8
|
<TransitionChild
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
as="template"
|
|
10
|
+
enter="transition-opacity ease-linear duration-150"
|
|
11
|
+
enter-from="opacity-0"
|
|
12
|
+
enter-to="opacity-100"
|
|
13
|
+
leave="transition-opacity ease-linear duration-150"
|
|
14
|
+
leave-from="opacity-100"
|
|
15
|
+
leave-to="opacity-0"
|
|
12
16
|
>
|
|
13
17
|
<div class="fixed inset-0 bg-gray-600/30" />
|
|
14
18
|
</TransitionChild>
|
|
15
19
|
|
|
16
20
|
<div class="fixed inset-0 flex">
|
|
17
21
|
<TransitionChild
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
as="template"
|
|
23
|
+
enter="transition ease-in-out duration-150 transform"
|
|
24
|
+
:enter-from="
|
|
21
25
|
position === 'left' ? '-translate-x-full opacity-0' : 'translate-x-full opacity-0'
|
|
22
26
|
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
enter-to="translate-x-0 opacity-100"
|
|
28
|
+
leave="transition ease-in-out duration-150 transform"
|
|
29
|
+
leave-from="translate-x-0 opacity-100"
|
|
30
|
+
:leave-to="
|
|
27
31
|
position === 'left' ? '-translate-x-full opacity-0' : 'translate-x-full opacity-0'
|
|
28
32
|
"
|
|
29
33
|
>
|
|
30
34
|
<DialogPanel
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
ref="panelRef"
|
|
36
|
+
:class="[
|
|
33
37
|
'flex w-[400px] py-2.5',
|
|
34
38
|
position === 'left' ? 'relative left-[10px]' : 'absolute right-[10px] h-full',
|
|
35
39
|
]"
|
|
36
40
|
>
|
|
37
|
-
|
|
41
|
+
<!-- Bind props and event handlers dynamically -->
|
|
42
|
+
<component
|
|
43
|
+
:is="overlayInstance.state.content"
|
|
44
|
+
v-bind="{ ...$attrs, ...overlayInstance.state.props }"
|
|
45
|
+
v-on="overlayInstance.state.eventHandlers"
|
|
46
|
+
/>
|
|
38
47
|
</DialogPanel>
|
|
39
48
|
</TransitionChild>
|
|
40
49
|
</div>
|
|
@@ -54,6 +63,7 @@ defineProps({
|
|
|
54
63
|
required: true,
|
|
55
64
|
validator: (value) => ['left', 'right'].includes(value),
|
|
56
65
|
},
|
|
66
|
+
|
|
57
67
|
overlayInstance: {
|
|
58
68
|
type: Object,
|
|
59
69
|
required: true,
|
package/src/components/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import SparkAlert from './SparkAlert.vue'
|
|
2
|
+
import SparkAppSelector from './SparkAppSelector.vue'
|
|
3
|
+
import SparkBrandSelector from './SparkBrandSelector.vue'
|
|
2
4
|
import SparkModal from './SparkModal.vue'
|
|
3
5
|
import SparkOverlay from './SparkOverlay.vue'
|
|
4
6
|
|
|
5
|
-
export { SparkAlert, SparkModal, SparkOverlay }
|
|
7
|
+
export { SparkAlert, SparkAppSelector, SparkBrandSelector, SparkModal, SparkOverlay }
|
package/src/composables/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useSparkOverlay } from '@/composables/useSparkOverlay'
|
|
2
|
+
|
|
3
|
+
class SparkOverlayService {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.left = useSparkOverlay()
|
|
6
|
+
this.right = useSparkOverlay()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
showLeft = (component, props = {}, eventHandlers = {}) => {
|
|
10
|
+
this.left.show(component, props, eventHandlers)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
showRight = (component, props = {}, eventHandlers = {}) => {
|
|
14
|
+
this.right.show(component, props, eventHandlers)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
closeLeft = () => {
|
|
18
|
+
this.left.close()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
closeRight = () => {
|
|
22
|
+
this.right.close()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
closeAll = () => {
|
|
26
|
+
this.left.close()
|
|
27
|
+
this.right.close()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const sparkOverlayService = new SparkOverlayService()
|
|
@@ -4,6 +4,8 @@ export function useSparkOverlay() {
|
|
|
4
4
|
const state = reactive({
|
|
5
5
|
isVisible: false,
|
|
6
6
|
content: null,
|
|
7
|
+
props: {},
|
|
8
|
+
eventHandlers: {},
|
|
7
9
|
})
|
|
8
10
|
|
|
9
11
|
const toggle = () => {
|
|
@@ -12,18 +14,21 @@ export function useSparkOverlay() {
|
|
|
12
14
|
|
|
13
15
|
const close = () => {
|
|
14
16
|
state.isVisible = false
|
|
17
|
+
state.eventHandlers = {}
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const open = () => {
|
|
18
21
|
state.isVisible = true
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
const setContent = (content) => {
|
|
24
|
+
const setContent = (content, props = {}, eventHandlers = {}) => {
|
|
22
25
|
state.content = markRaw(content)
|
|
26
|
+
state.props = props
|
|
27
|
+
state.eventHandlers = eventHandlers
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
const show = (content) => {
|
|
26
|
-
if (content) setContent(content)
|
|
30
|
+
const show = (content, props = {}, eventHandlers = {}) => {
|
|
31
|
+
if (content) setContent(content, props, eventHandlers)
|
|
27
32
|
open()
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -35,4 +40,4 @@ export function useSparkOverlay() {
|
|
|
35
40
|
setContent,
|
|
36
41
|
show,
|
|
37
42
|
}
|
|
38
|
-
}
|
|
43
|
+
}
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,13 @@ import {
|
|
|
7
7
|
faCircleXmark,
|
|
8
8
|
faXmark,
|
|
9
9
|
faCheck,
|
|
10
|
+
faTimes,
|
|
11
|
+
faLaptopMobile,
|
|
12
|
+
faSatelliteDish,
|
|
13
|
+
faScaleBalanced,
|
|
14
|
+
faStreetView,
|
|
15
|
+
faFaceSmileRelaxed,
|
|
16
|
+
faComments,
|
|
10
17
|
} from '@fortawesome/pro-regular-svg-icons'
|
|
11
18
|
|
|
12
19
|
export const Icons = {
|
|
@@ -16,6 +23,13 @@ export const Icons = {
|
|
|
16
23
|
farCircleXmark: faCircleXmark,
|
|
17
24
|
farXmark: faXmark,
|
|
18
25
|
farCheck: faCheck,
|
|
26
|
+
farTimes: faTimes,
|
|
27
|
+
farLaptopMobile: faLaptopMobile,
|
|
28
|
+
farSatelliteDish: faSatelliteDish,
|
|
29
|
+
farScaleBalanced: faScaleBalanced,
|
|
30
|
+
farStreetView: faStreetView,
|
|
31
|
+
farFaceSmileRelaxed: faFaceSmileRelaxed,
|
|
32
|
+
farComments: faComments,
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
export function addSparkIcons() {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, reactive } from 'vue'
|
|
3
|
+
|
|
4
|
+
import mrPestControllerLogo from '@/assets/images/mr-pest-controller.png'
|
|
5
|
+
import mrGutterCleaningLogo from '@/assets/images/mr-gutter-cleaning.png'
|
|
6
|
+
import mrAntennaLogo from '@/assets/images/mr-antenna.png'
|
|
7
|
+
|
|
8
|
+
export const useSparkBrandFilterStore = defineStore('brandFilter', () => {
|
|
9
|
+
const state = reactive({
|
|
10
|
+
brands: [
|
|
11
|
+
{
|
|
12
|
+
name: 'Mr Pest Controller',
|
|
13
|
+
logo: mrPestControllerLogo,
|
|
14
|
+
current: false,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Mr Gutter Cleaning',
|
|
18
|
+
logo: mrGutterCleaningLogo,
|
|
19
|
+
current: false,
|
|
20
|
+
},
|
|
21
|
+
{ name: 'Mr Antenna', logo: mrAntennaLogo, current: true },
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const currentBrand = computed(() => state.brands.find((item) => item.current))
|
|
26
|
+
const allBrands = computed(() => state.brands)
|
|
27
|
+
|
|
28
|
+
const toggleBrand = (brand) => {
|
|
29
|
+
state.brands.forEach((item) => {
|
|
30
|
+
item.current = item === brand
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
state,
|
|
36
|
+
currentBrand,
|
|
37
|
+
allBrands,
|
|
38
|
+
toggleBrand,
|
|
39
|
+
}
|
|
40
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './brand-filter.js'
|