@wishbone-media/spark 0.20.0 → 0.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -7,7 +7,7 @@
7
7
  <div class="ml-3">
8
8
  <slot />
9
9
  </div>
10
- <div class="ml-auto pl-3 pt-1 self-start">
10
+ <div v-if="closeable" class="ml-auto pl-3 pt-1 self-start">
11
11
  <div class="-mx-1.5 -my-1.5">
12
12
  <button
13
13
  type="button"
@@ -33,6 +33,10 @@ const props = defineProps({
33
33
  default: 'info',
34
34
  validator: (value) => ['success', 'warning', 'danger', 'info'].includes(value),
35
35
  },
36
+ closeable: {
37
+ type: Boolean,
38
+ default: true,
39
+ },
36
40
  })
37
41
 
38
42
  defineEmits(['close'])
@@ -13,6 +13,8 @@
13
13
  <script setup>
14
14
  import { computed, inject, ref } from 'vue'
15
15
 
16
+ defineEmits(['click'])
17
+
16
18
  const props = defineProps({
17
19
  size: {
18
20
  type: String,
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <Transition
3
+ enter-active-class="transition-all duration-300 ease-out"
4
+ enter-from-class="opacity-0 -translate-y-2"
5
+ enter-to-class="opacity-100 translate-y-0"
6
+ leave-active-class="transition-all duration-200 ease-in"
7
+ leave-from-class="opacity-100 translate-y-0"
8
+ leave-to-class="opacity-0 -translate-y-2"
9
+ mode="out-in"
10
+ >
11
+ <SparkAlert
12
+ v-if="outlet.state.isVisible"
13
+ :key="notificationKey"
14
+ :type="outlet.state.type"
15
+ :closeable="outlet.state.closeable"
16
+ @close="handleClose"
17
+ @mouseenter="handleMouseEnter"
18
+ @mouseleave="handleMouseLeave"
19
+ >
20
+ <component
21
+ v-if="outlet.state.component"
22
+ :is="outlet.state.component"
23
+ v-bind="outlet.state.props"
24
+ />
25
+ <template v-else>
26
+ {{ outlet.state.message }}
27
+ </template>
28
+ </SparkAlert>
29
+ </Transition>
30
+ </template>
31
+
32
+ <script setup>
33
+ import { computed, ref, watch } from 'vue'
34
+ import { sparkNotificationService } from '@/composables/sparkNotificationService'
35
+ import SparkAlert from '@/components/SparkAlert.vue'
36
+
37
+ const props = defineProps({
38
+ name: {
39
+ type: String,
40
+ default: 'default',
41
+ },
42
+ })
43
+
44
+ const outlet = computed(() => sparkNotificationService.getOutlet(props.name))
45
+
46
+ // Generate unique key for each notification to trigger transition on content change
47
+ const notificationKey = ref(0)
48
+ watch(
49
+ () => [outlet.value.state.message, outlet.value.state.component, outlet.value.state.type],
50
+ () => {
51
+ if (outlet.value.state.isVisible) {
52
+ notificationKey.value++
53
+ }
54
+ },
55
+ )
56
+
57
+ const handleClose = () => {
58
+ sparkNotificationService.hide(props.name)
59
+ }
60
+
61
+ const handleMouseEnter = () => {
62
+ sparkNotificationService.pause(props.name)
63
+ }
64
+
65
+ const handleMouseLeave = () => {
66
+ sparkNotificationService.resume(props.name)
67
+ }
68
+ </script>
@@ -34,7 +34,8 @@
34
34
  <DialogPanel
35
35
  ref="panelRef"
36
36
  :class="[
37
- 'flex w-[400px] py-2.5',
37
+ 'flex py-2.5',
38
+ widthClass,
38
39
  position === 'left' ? 'relative left-[10px]' : 'absolute right-[10px] h-full',
39
40
  ]"
40
41
  >
@@ -52,11 +53,19 @@
52
53
  </template>
53
54
 
54
55
  <script setup>
55
- import { ref } from 'vue'
56
+ import { computed, ref } from 'vue'
56
57
  import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
57
58
 
58
59
  const panelRef = ref(null)
59
60
 
61
+ const sizeClasses = {
62
+ xs: 'w-[250px]',
63
+ sm: 'w-[300px]',
64
+ md: 'w-[450px]',
65
+ lg: 'w-[810px]',
66
+ xl: 'w-[1000px]',
67
+ }
68
+
60
69
  const props = defineProps({
61
70
  position: {
62
71
  type: String,
@@ -72,6 +81,10 @@ const props = defineProps({
72
81
 
73
82
  const emit = defineEmits(['close'])
74
83
 
84
+ const widthClass = computed(() => {
85
+ return sizeClasses[props.overlayInstance.state.size] || sizeClasses.md
86
+ })
87
+
75
88
  const handleClose = () => {
76
89
  props.overlayInstance.close()
77
90
  emit('close')
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <!-- Top Left -->
4
+ <div class="fixed top-4 left-4 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
5
+ <TransitionGroup
6
+ enter-active-class="transition-all duration-300 ease-out"
7
+ enter-from-class="opacity-0 -translate-x-4"
8
+ enter-to-class="opacity-100 translate-x-0"
9
+ leave-active-class="transition-all duration-200 ease-in"
10
+ leave-from-class="opacity-100 translate-x-0"
11
+ leave-to-class="opacity-0 -translate-x-4"
12
+ >
13
+ <div v-for="toast in topLeftToasts" :key="toast.id" class="pointer-events-auto">
14
+ <SparkAlert
15
+ :type="toast.type"
16
+ :closeable="toast.closeable"
17
+ @close="handleClose(toast.id)"
18
+ @mouseenter="handleMouseEnter(toast.id)"
19
+ @mouseleave="handleMouseLeave(toast.id)"
20
+ >
21
+ <component
22
+ v-if="toast.component"
23
+ :is="toast.component"
24
+ v-bind="toast.props"
25
+ />
26
+ <template v-else>{{ toast.message }}</template>
27
+ </SparkAlert>
28
+ </div>
29
+ </TransitionGroup>
30
+ </div>
31
+
32
+ <!-- Top Right -->
33
+ <div class="fixed top-4 right-4 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
34
+ <TransitionGroup
35
+ enter-active-class="transition-all duration-300 ease-out"
36
+ enter-from-class="opacity-0 translate-x-4"
37
+ enter-to-class="opacity-100 translate-x-0"
38
+ leave-active-class="transition-all duration-200 ease-in"
39
+ leave-from-class="opacity-100 translate-x-0"
40
+ leave-to-class="opacity-0 translate-x-4"
41
+ >
42
+ <div v-for="toast in topRightToasts" :key="toast.id" class="pointer-events-auto">
43
+ <SparkAlert
44
+ :type="toast.type"
45
+ :closeable="toast.closeable"
46
+ @close="handleClose(toast.id)"
47
+ @mouseenter="handleMouseEnter(toast.id)"
48
+ @mouseleave="handleMouseLeave(toast.id)"
49
+ >
50
+ <component
51
+ v-if="toast.component"
52
+ :is="toast.component"
53
+ v-bind="toast.props"
54
+ />
55
+ <template v-else>{{ toast.message }}</template>
56
+ </SparkAlert>
57
+ </div>
58
+ </TransitionGroup>
59
+ </div>
60
+
61
+ <!-- Center (slightly above center) -->
62
+ <div class="fixed top-[40%] left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
63
+ <TransitionGroup
64
+ enter-active-class="transition-all duration-300 ease-out"
65
+ enter-from-class="opacity-0 scale-95"
66
+ enter-to-class="opacity-100 scale-100"
67
+ leave-active-class="transition-all duration-200 ease-in"
68
+ leave-from-class="opacity-100 scale-100"
69
+ leave-to-class="opacity-0 scale-95"
70
+ >
71
+ <div v-for="toast in centerToasts" :key="toast.id" class="pointer-events-auto">
72
+ <SparkAlert
73
+ :type="toast.type"
74
+ :closeable="toast.closeable"
75
+ @close="handleClose(toast.id)"
76
+ @mouseenter="handleMouseEnter(toast.id)"
77
+ @mouseleave="handleMouseLeave(toast.id)"
78
+ >
79
+ <component
80
+ v-if="toast.component"
81
+ :is="toast.component"
82
+ v-bind="toast.props"
83
+ />
84
+ <template v-else>{{ toast.message }}</template>
85
+ </SparkAlert>
86
+ </div>
87
+ </TransitionGroup>
88
+ </div>
89
+
90
+ <!-- Bottom Left -->
91
+ <div class="fixed bottom-4 left-4 z-[2000] flex flex-col-reverse gap-3 max-w-sm w-full pointer-events-none">
92
+ <TransitionGroup
93
+ enter-active-class="transition-all duration-300 ease-out"
94
+ enter-from-class="opacity-0 -translate-x-4"
95
+ enter-to-class="opacity-100 translate-x-0"
96
+ leave-active-class="transition-all duration-200 ease-in"
97
+ leave-from-class="opacity-100 translate-x-0"
98
+ leave-to-class="opacity-0 -translate-x-4"
99
+ >
100
+ <div v-for="toast in bottomLeftToasts" :key="toast.id" class="pointer-events-auto">
101
+ <SparkAlert
102
+ :type="toast.type"
103
+ :closeable="toast.closeable"
104
+ @close="handleClose(toast.id)"
105
+ @mouseenter="handleMouseEnter(toast.id)"
106
+ @mouseleave="handleMouseLeave(toast.id)"
107
+ >
108
+ <component
109
+ v-if="toast.component"
110
+ :is="toast.component"
111
+ v-bind="toast.props"
112
+ />
113
+ <template v-else>{{ toast.message }}</template>
114
+ </SparkAlert>
115
+ </div>
116
+ </TransitionGroup>
117
+ </div>
118
+
119
+ <!-- Bottom Right -->
120
+ <div class="fixed bottom-4 right-4 z-[2000] flex flex-col-reverse gap-3 max-w-sm w-full pointer-events-none">
121
+ <TransitionGroup
122
+ enter-active-class="transition-all duration-300 ease-out"
123
+ enter-from-class="opacity-0 translate-x-4"
124
+ enter-to-class="opacity-100 translate-x-0"
125
+ leave-active-class="transition-all duration-200 ease-in"
126
+ leave-from-class="opacity-100 translate-x-0"
127
+ leave-to-class="opacity-0 translate-x-4"
128
+ >
129
+ <div v-for="toast in bottomRightToasts" :key="toast.id" class="pointer-events-auto">
130
+ <SparkAlert
131
+ :type="toast.type"
132
+ :closeable="toast.closeable"
133
+ @close="handleClose(toast.id)"
134
+ @mouseenter="handleMouseEnter(toast.id)"
135
+ @mouseleave="handleMouseLeave(toast.id)"
136
+ >
137
+ <component
138
+ v-if="toast.component"
139
+ :is="toast.component"
140
+ v-bind="toast.props"
141
+ />
142
+ <template v-else>{{ toast.message }}</template>
143
+ </SparkAlert>
144
+ </div>
145
+ </TransitionGroup>
146
+ </div>
147
+ </Teleport>
148
+ </template>
149
+
150
+ <script setup>
151
+ import { computed } from 'vue'
152
+ import { sparkNotificationService } from '@/composables/sparkNotificationService'
153
+ import SparkAlert from '@/components/SparkAlert.vue'
154
+
155
+ const toastState = sparkNotificationService.toastState
156
+
157
+ const topLeftToasts = computed(() =>
158
+ toastState.toasts.filter(t => t.position === 'top-left')
159
+ )
160
+ const topRightToasts = computed(() =>
161
+ toastState.toasts.filter(t => t.position === 'top-right')
162
+ )
163
+ const centerToasts = computed(() =>
164
+ toastState.toasts.filter(t => t.position === 'center')
165
+ )
166
+ const bottomLeftToasts = computed(() =>
167
+ toastState.toasts.filter(t => t.position === 'bottom-left')
168
+ )
169
+ const bottomRightToasts = computed(() =>
170
+ toastState.toasts.filter(t => t.position === 'bottom-right')
171
+ )
172
+
173
+ const handleClose = (toastId) => {
174
+ sparkNotificationService.hideToast(toastId)
175
+ }
176
+
177
+ const handleMouseEnter = (toastId) => {
178
+ sparkNotificationService.pauseToast(toastId)
179
+ }
180
+
181
+ const handleMouseLeave = (toastId) => {
182
+ sparkNotificationService.resumeToast(toastId)
183
+ }
184
+ </script>
@@ -1,5 +1,7 @@
1
1
  export { default as SparkAlert } from './SparkAlert.vue'
2
2
  export { default as SparkAppSelector } from './SparkAppSelector.vue'
3
+ export { default as SparkNotificationOutlet } from './SparkNotificationOutlet.vue'
4
+ export { default as SparkToastContainer } from './SparkToastContainer.vue'
3
5
  export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
4
6
  export { default as SparkButton } from './SparkButton.vue'
5
7
  export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
@@ -1,4 +1,5 @@
1
1
  export { sparkModalService } from './sparkModalService.js'
2
+ export { sparkNotificationService } from './sparkNotificationService.js'
2
3
  export { sparkOverlayService } from './sparkOverlayService.js'
3
4
  export { useSparkOverlay } from './useSparkOverlay.js'
4
5
  export { useSparkTableRouteSync } from './useSparkTableRouteSync.js'