@wishbone-media/spark 0.1.3 → 0.2.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 +359 -81
- package/package.json +6 -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/SparkAlert.vue +83 -0
- package/src/components/SparkAppSelector.vue +120 -0
- package/src/components/SparkBrandSelector.vue +60 -0
- package/src/components/SparkOverlay.vue +4 -1
- package/src/components/index.js +4 -1
- package/src/composables/useSparkOverlay.js +5 -3
- package/src/index.js +2 -3
- package/src/plugins/fontawesome.js +37 -0
- package/src/plugins/index.js +1 -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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
],
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@headlessui/vue": "^1.7.0",
|
|
13
|
-
"
|
|
13
|
+
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
14
|
+
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
|
15
|
+
"pinia": "^3.0.3",
|
|
16
|
+
"vue": "^3.5.16"
|
|
14
17
|
},
|
|
15
18
|
"devDependencies": {
|
|
16
19
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
17
20
|
"vite": "^6.3.5",
|
|
18
|
-
"vue": "^3.5.
|
|
21
|
+
"vue": "^3.5.16"
|
|
19
22
|
},
|
|
20
23
|
"packageManager": "pnpm@10.11.1",
|
|
21
24
|
"scripts": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="['rounded-md border p-4', containerClasses]">
|
|
3
|
+
<div class="flex items-center">
|
|
4
|
+
<div class="shrink-0 self-start">
|
|
5
|
+
<font-awesome-icon :icon="Icons[iconName]" :class="iconClasses" />
|
|
6
|
+
</div>
|
|
7
|
+
<div class="ml-3">
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
<div class="ml-auto pl-3 pt-1 self-start">
|
|
11
|
+
<div class="-mx-1.5 -my-1.5">
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
class="inline-flex rounded-md px-2 py-1.5"
|
|
15
|
+
:class="closeClasses"
|
|
16
|
+
@click="$emit('close')"
|
|
17
|
+
>
|
|
18
|
+
<font-awesome-icon :icon="Icons.farXmark" />
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
import { computed } from 'vue'
|
|
28
|
+
import { Icons } from '@/plugins/fontawesome'
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
type: {
|
|
32
|
+
type: String,
|
|
33
|
+
default: 'info',
|
|
34
|
+
validator: (value) => ['success', 'warning', 'danger', 'info'].includes(value),
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
defineEmits(['close'])
|
|
39
|
+
|
|
40
|
+
const containerClasses = computed(() => {
|
|
41
|
+
const types = {
|
|
42
|
+
success: 'bg-green-50 border-green-200 text-green-700',
|
|
43
|
+
warning: 'bg-yellow-50 border-yellow-200 text-yellow-700',
|
|
44
|
+
danger: 'bg-red-50 border-red-200 text-red-700',
|
|
45
|
+
info: 'bg-blue-50 border-blue-200 text-blue-700',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return types[props.type]
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const iconClasses = computed(() => {
|
|
52
|
+
const iconClasses = {
|
|
53
|
+
success: 'text-green-400',
|
|
54
|
+
warning: 'text-yellow-400',
|
|
55
|
+
danger: 'text-red-400',
|
|
56
|
+
info: 'text-blue-400',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return iconClasses[props.type]
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const iconName = computed(() => {
|
|
63
|
+
const icons = {
|
|
64
|
+
success: 'farCheckCircle',
|
|
65
|
+
warning: 'farExclamationTriangle',
|
|
66
|
+
danger: 'farCircleXmark',
|
|
67
|
+
info: 'farInfoCircle',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return icons[props.type]
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const closeClasses = computed(() => {
|
|
74
|
+
const closeClasses = {
|
|
75
|
+
success: 'text-green-400 hover:bg-green-100',
|
|
76
|
+
warning: 'text-yellow-400 hover:bg-yellow-100',
|
|
77
|
+
danger: 'text-red-400 hover:bg-red-100',
|
|
78
|
+
info: 'text-blue-400 hover:bg-blue-100',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return closeClasses[props.type]
|
|
82
|
+
})
|
|
83
|
+
</script>
|
|
@@ -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 brandFilterStore.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 { useBrandFilterStore } from '@/stores/brand-filter'
|
|
51
|
+
import { Icons } from '@/plugins/fontawesome'
|
|
52
|
+
|
|
53
|
+
const emit = defineEmits(['close', 'select'])
|
|
54
|
+
|
|
55
|
+
const brandFilterStore = useBrandFilterStore()
|
|
56
|
+
|
|
57
|
+
const selectBrand = (brand) => {
|
|
58
|
+
emit('select', brand)
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
position === 'left' ? 'relative left-[10px]' : 'absolute right-[10px] h-full',
|
|
35
35
|
]"
|
|
36
36
|
>
|
|
37
|
-
<component
|
|
37
|
+
<component
|
|
38
|
+
:is="overlayInstance.state.content"
|
|
39
|
+
v-bind="{ ...$attrs, ...overlayInstance.state.props }"
|
|
40
|
+
/>
|
|
38
41
|
</DialogPanel>
|
|
39
42
|
</TransitionChild>
|
|
40
43
|
</div>
|
package/src/components/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import SparkAlert from './SparkAlert.vue'
|
|
2
|
+
import SparkAppSelector from './SparkAppSelector.vue'
|
|
3
|
+
import SparkBrandSelector from './SparkBrandSelector.vue'
|
|
1
4
|
import SparkModal from './SparkModal.vue'
|
|
2
5
|
import SparkOverlay from './SparkOverlay.vue'
|
|
3
6
|
|
|
4
|
-
export { SparkModal, SparkOverlay }
|
|
7
|
+
export { SparkAlert, SparkAppSelector, SparkBrandSelector, SparkModal, SparkOverlay }
|
|
@@ -4,6 +4,7 @@ export function useSparkOverlay() {
|
|
|
4
4
|
const state = reactive({
|
|
5
5
|
isVisible: false,
|
|
6
6
|
content: null,
|
|
7
|
+
props: {},
|
|
7
8
|
})
|
|
8
9
|
|
|
9
10
|
const toggle = () => {
|
|
@@ -18,12 +19,13 @@ export function useSparkOverlay() {
|
|
|
18
19
|
state.isVisible = true
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const setContent = (content) => {
|
|
22
|
+
const setContent = (content, props = {}) => {
|
|
22
23
|
state.content = markRaw(content)
|
|
24
|
+
state.props = props
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const show = (content) => {
|
|
26
|
-
if (content) setContent(content)
|
|
27
|
+
const show = (content, props = {}) => {
|
|
28
|
+
if (content) setContent(content, props)
|
|
27
29
|
open()
|
|
28
30
|
}
|
|
29
31
|
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
faExclamationTriangle,
|
|
5
|
+
faCheckCircle,
|
|
6
|
+
faInfoCircle,
|
|
7
|
+
faCircleXmark,
|
|
8
|
+
faXmark,
|
|
9
|
+
faCheck,
|
|
10
|
+
faTimes,
|
|
11
|
+
faLaptopMobile,
|
|
12
|
+
faSatelliteDish,
|
|
13
|
+
faScaleBalanced,
|
|
14
|
+
faStreetView,
|
|
15
|
+
faFaceSmileRelaxed,
|
|
16
|
+
faComments,
|
|
17
|
+
} from '@fortawesome/pro-regular-svg-icons'
|
|
18
|
+
|
|
19
|
+
export const Icons = {
|
|
20
|
+
farExclamationTriangle: faExclamationTriangle,
|
|
21
|
+
farCheckCircle: faCheckCircle,
|
|
22
|
+
farInfoCircle: faInfoCircle,
|
|
23
|
+
farCircleXmark: faCircleXmark,
|
|
24
|
+
farXmark: faXmark,
|
|
25
|
+
farCheck: faCheck,
|
|
26
|
+
farTimes: faTimes,
|
|
27
|
+
farLaptopMobile: faLaptopMobile,
|
|
28
|
+
farSatelliteDish: faSatelliteDish,
|
|
29
|
+
farScaleBalanced: faScaleBalanced,
|
|
30
|
+
farStreetView: faStreetView,
|
|
31
|
+
farFaceSmileRelaxed: faFaceSmileRelaxed,
|
|
32
|
+
farComments: faComments,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function addSparkIcons() {
|
|
36
|
+
library.add(...Object.values(Icons))
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Icons, addSparkIcons } from './fontawesome.js'
|
|
@@ -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 useBrandFilterStore = 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'
|