@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.1.4",
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
- "vue": "^3.5.0"
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.13"
21
+ "vue": "^3.5.16"
21
22
  },
22
23
  "packageManager": "pnpm@10.11.1",
23
24
  "scripts": {
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 :initialFocus="panelRef" class="relative z-200" @close="overlayInstance.close">
3
+ <Dialog
4
+ :initialFocus="panelRef"
5
+ class="relative z-200"
6
+ @close="overlayInstance.close"
7
+ >
4
8
  <TransitionChild
5
- as="template"
6
- enter="transition-opacity ease-linear duration-150"
7
- enter-from="opacity-0"
8
- enter-to="opacity-100"
9
- leave="transition-opacity ease-linear duration-150"
10
- leave-from="opacity-100"
11
- leave-to="opacity-0"
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
- as="template"
19
- enter="transition ease-in-out duration-150 transform"
20
- :enter-from="
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
- enter-to="translate-x-0 opacity-100"
24
- leave="transition ease-in-out duration-150 transform"
25
- leave-from="translate-x-0 opacity-100"
26
- :leave-to="
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
- ref="panelRef"
32
- :class="[
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
- <component :is="overlayInstance.state.content" v-bind="$attrs" />
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,
@@ -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 }
@@ -1 +1,2 @@
1
1
  export { useSparkOverlay } from './useSparkOverlay.js'
2
+ export { sparkOverlayService } from './sparkOverlayService.js'
@@ -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
@@ -1,3 +1,4 @@
1
1
  export * from './components/index.js'
2
2
  export * from './composables/index.js'
3
- export * from './plugins/index.js'
3
+ export * from './plugins/index.js'
4
+ export * from './stores/index.js'
@@ -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'