@wishbone-media/spark 0.3.0 → 0.5.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,24 +1,33 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
7
7
  "files": [
8
8
  "dist",
9
- "src"
9
+ "src",
10
+ "formkit.config.js",
11
+ "formkit.theme.mjs"
10
12
  ],
11
13
  "peerDependencies": {
12
- "@headlessui/vue": "^1.7.0",
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
+ "@formkit/addons": "^1.6.9",
15
+ "@formkit/core": "^1.6.9",
16
+ "@formkit/icons": "^1.6.9",
17
+ "@formkit/pro": "^0.127.23",
18
+ "@formkit/themes": "^1.6.9",
19
+ "@formkit/vue": "^1.6.9",
20
+ "@fortawesome/fontawesome-svg-core": "^7.1.0",
21
+ "@fortawesome/pro-regular-svg-icons": "^7.1.0",
22
+ "@headlessui/vue": "^1.7.23",
23
+ "pinia": "^3.0.4",
24
+ "vue": "^3.5.24",
25
+ "vue-router": "^4.6.3"
17
26
  },
18
27
  "devDependencies": {
19
- "@vitejs/plugin-vue": "^5.2.4",
20
- "vite": "^6.3.5",
21
- "vue": "^3.5.16"
28
+ "@vitejs/plugin-vue": "^6.0.2",
29
+ "vite": "^7.2.2",
30
+ "vue": "^3.5.24"
22
31
  },
23
32
  "packageManager": "pnpm@10.11.1",
24
33
  "scripts": {
@@ -29,7 +38,11 @@
29
38
  },
30
39
  "exports": {
31
40
  ".": "./dist/index.js",
32
- "./src/*": "./src/*"
41
+ "./src/*": "./src/*",
42
+ "./formkit.config.js": "./formkit.config.js",
43
+ "./formkit.theme.mjs": "./formkit.theme.mjs",
44
+ "./assets/css/*": "./src/assets/css/*",
45
+ "./assets/fonts/*": "./src/assets/fonts/*"
33
46
  },
34
47
  "publishConfig": {
35
48
  "access": "public"
@@ -0,0 +1,72 @@
1
+ /* Inter font declarations */
2
+ @font-face {
3
+ font-family: 'Inter';
4
+ src: url('../fonts/inter/Inter-Thin.woff2') format('woff2');
5
+ font-weight: 100;
6
+ font-style: normal;
7
+ font-display: swap;
8
+ }
9
+
10
+ @font-face {
11
+ font-family: 'Inter';
12
+ src: url('../fonts/inter/Inter-ExtraLight.woff2') format('woff2');
13
+ font-weight: 200;
14
+ font-style: normal;
15
+ font-display: swap;
16
+ }
17
+
18
+ @font-face {
19
+ font-family: 'Inter';
20
+ src: url('../fonts/inter/Inter-Light.woff2') format('woff2');
21
+ font-weight: 300;
22
+ font-style: normal;
23
+ font-display: swap;
24
+ }
25
+
26
+ @font-face {
27
+ font-family: 'Inter';
28
+ src: url('../fonts/inter/Inter-Regular.woff2') format('woff2');
29
+ font-weight: 400;
30
+ font-style: normal;
31
+ font-display: swap;
32
+ }
33
+
34
+ @font-face {
35
+ font-family: 'Inter';
36
+ src: url('../fonts/inter/Inter-Medium.woff2') format('woff2');
37
+ font-weight: 500;
38
+ font-style: normal;
39
+ font-display: swap;
40
+ }
41
+
42
+ @font-face {
43
+ font-family: 'Inter';
44
+ src: url('../fonts/inter/Inter-SemiBold.woff2') format('woff2');
45
+ font-weight: 600;
46
+ font-style: normal;
47
+ font-display: swap;
48
+ }
49
+
50
+ @font-face {
51
+ font-family: 'Inter';
52
+ src: url('../fonts/inter/Inter-Bold.woff2') format('woff2');
53
+ font-weight: 700;
54
+ font-style: normal;
55
+ font-display: swap;
56
+ }
57
+
58
+ @font-face {
59
+ font-family: 'Inter';
60
+ src: url('../fonts/inter/Inter-ExtraBold.woff2') format('woff2');
61
+ font-weight: 800;
62
+ font-style: normal;
63
+ font-display: swap;
64
+ }
65
+
66
+ @font-face {
67
+ font-family: 'Inter';
68
+ src: url('../fonts/inter/Inter-Black.woff2') format('woff2');
69
+ font-weight: 900;
70
+ font-style: normal;
71
+ font-display: swap;
72
+ }
@@ -0,0 +1,29 @@
1
+ @import './fonts.css';
2
+
3
+ :root {
4
+ font-family:
5
+ 'Inter',
6
+ -apple-system,
7
+ BlinkMacSystemFont,
8
+ 'Segoe UI',
9
+ Roboto,
10
+ Oxygen,
11
+ Ubuntu,
12
+ Cantarell,
13
+ 'Open Sans',
14
+ 'Helvetica Neue',
15
+ sans-serif;
16
+ }
17
+
18
+ html,
19
+ body,
20
+ #app {
21
+ @apply h-full;
22
+ }
23
+
24
+ @layer base {
25
+ button:not(:disabled),
26
+ [role='button']:not(:disabled) {
27
+ cursor: pointer;
28
+ }
29
+ }
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div class="divide-y divide-gray-300 rounded-lg border border-gray-300 text-gray-700 bg-gray-100">
3
+ <div v-if="$slots.header" class="p-5">
4
+ <slot name="header" />
5
+ </div>
6
+
7
+ <div :class="[props.padded ? props.paddedClass : '']">
8
+ <slot></slot>
9
+ </div>
10
+
11
+ <div v-if="$slots.footer" class="p-5">
12
+ <slot name="footer" />
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup>
18
+ const props = defineProps({
19
+ padded: {
20
+ type: Boolean,
21
+ default: true,
22
+ },
23
+
24
+ paddedClass: {
25
+ type: String,
26
+ default: 'p-5',
27
+ },
28
+ })
29
+ </script>
@@ -3,6 +3,7 @@ export { default as SparkAppSelector } from './SparkAppSelector.vue'
3
3
  export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
4
4
  export { default as SparkButton } from './SparkButton.vue'
5
5
  export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
6
+ export { default as SparkCard } from './SparkCard.vue'
6
7
  export { default as SparkModalContainer } from './SparkModalContainer.vue'
7
8
  export { default as SparkModalDialog } from './SparkModalDialog.vue'
8
9
  export { default as SparkOverlay } from './SparkOverlay.vue'
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div :class="sidebarClass" class="fixed inset-y-0 flex transition-all z-100">
3
+ <div class="flex grow m-2.5 p-[10px] rounded-lg">
4
+ <nav class="flex flex-1 flex-col">
5
+ <ul class="flex flex-1 flex-col gap-y-7" role="list">
6
+ <li>
7
+ <ul role="list">
8
+ <li class="flex items-center pb-8">
9
+ <a
10
+ class="grid w-[40px] h-[40px] place-items-center rounded-md bg-primary-600 text-white text-[13px] cursor-pointer"
11
+ @click.prevent="mainNavStore.goto('dashboard')"
12
+ >
13
+ <font-awesome-icon :icon="Icons[appStore.state.icon]" class="size-5" />
14
+ </a>
15
+ <a
16
+ @click.prevent="mainNavStore.goto('dashboard')"
17
+ v-if="!mainNavStore.state.collapsed"
18
+ class="font-medium text-gray-800 ml-[10px] cursor-pointer"
19
+ >
20
+ {{ appStore.state.app }}
21
+ </a>
22
+ </li>
23
+ <template v-for="item in mainNavStore.state.menu" :key="item.name">
24
+ <li
25
+ :class="{
26
+ 'mt-[10px]': item.children,
27
+ }"
28
+ >
29
+ <a
30
+ :class="{
31
+ 'bg-gray-100': item.current,
32
+ 'hover:bg-gray-100': item?.href,
33
+ }"
34
+ :href="item?.href"
35
+ class="h-[37px] sgroup flex items-center gap-x-2 rounded-md p-3 text-gray-800 leading-5 transition-all duration-300 ease-in-out"
36
+ @click.prevent="mainNavStore.goto(item.href)"
37
+ >
38
+ <font-awesome-icon
39
+ :icon="Icons[item.icon]"
40
+ :class="[item.current ? 'text-gray-400' : 'text-gray-400']"
41
+ class="size-4"
42
+ v-if="item.icon"
43
+ />
44
+ <span
45
+ v-if="!mainNavStore.state.collapsed"
46
+ :class="{
47
+ 'text-[11px]': item?.children,
48
+ 'text-[13px]': !item?.children,
49
+ 'font-semibold': item?.children,
50
+ 'text-gray-500': item?.children,
51
+ }"
52
+ >
53
+ {{ item.name }}
54
+ </span>
55
+ <div v-else-if="item?.children" class="w-full flex justify-center">
56
+ <div class="w-[10px] h-px bg-gray-400"></div>
57
+ </div>
58
+ </a>
59
+ <ul v-if="item.children" class="mt-[5px] flex flex-col gap-[5px]">
60
+ <li v-for="child in item.children" :key="child.name">
61
+ <a
62
+ :class="[child.current ? 'bg-gray-100' : '', 'hover:bg-gray-100']"
63
+ :href="child.href"
64
+ class="h-[37px] sgroup flex items-center gap-x-2 rounded-md p-3 text-gray-800 leading-5 transition-all duration-300 ease-in-out"
65
+ @click.prevent="mainNavStore.goto(child.href)"
66
+ >
67
+ <font-awesome-icon
68
+ v-if="child.icon"
69
+ :icon="Icons[child.icon]"
70
+ :class="[child.current ? 'text-gray-400' : 'text-gray-400']"
71
+ class="size-4"
72
+ />
73
+ <span class="text-[13px]" v-if="!mainNavStore.state.collapsed">
74
+ {{ child.name }}
75
+ </span>
76
+ </a>
77
+ </li>
78
+ </ul>
79
+ </li>
80
+ </template>
81
+ </ul>
82
+ </li>
83
+ <li class="mt-auto">
84
+ <a
85
+ class="font-medium grid place-content-center gap-x-3 rounded-md h-10 p-2.5 text-gray-800 text-[13px] hover:bg-gray-100 transition-all duration-300 ease-in-out"
86
+ href="#"
87
+ @click.prevent="mainNavStore.toggleCollapsed()"
88
+ >
89
+ <font-awesome-icon
90
+ :icon="
91
+ Icons[mainNavStore.state.collapsed ? 'farArrowRightToLine' : 'farArrowLeftToLine']
92
+ "
93
+ class="class-5"
94
+ />
95
+ </a>
96
+ </li>
97
+ </ul>
98
+ </nav>
99
+ </div>
100
+ </div>
101
+
102
+ <div :class="contentClass" class="h-full transition-all flex flex-col">
103
+ <div class="p-[10px] flex-shrink-0">
104
+ <div class="flex flex-1 items-center gap-x-6">
105
+ <div class="relative flex flex-1 items-center justify-between">
106
+ <div class="cursor-pointer">
107
+ <font-awesome-icon
108
+ :icon="Icons.farBarsSort"
109
+ class="size-5"
110
+ @click="mainNavStore.toggleHidden()"
111
+ />
112
+ </div>
113
+
114
+ <div class="cursor-pointer h-9 flex items-center" @click="toggleBrandSelector">
115
+ <img
116
+ v-if="sparkBrandFilterStore.currentBrand"
117
+ :src="sparkBrandFilterStore.currentBrand.logo"
118
+ alt=""
119
+ class="h-[30px] w-auto"
120
+ />
121
+ </div>
122
+
123
+ <button
124
+ class="rounded-sm bg-white w-[42px] h-[42px] ring-1 ring-inset ring-gray-300"
125
+ type="button"
126
+ @click="toggleAppSelector"
127
+ >
128
+ <font-awesome-icon :icon="Icons.farGripDotsVertical" class="size-4 text-gray-400" />
129
+ </button>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <main class="mr-[10px] pb-[10px] flex-1 flex flex-col">
135
+ <router-view />
136
+ </main>
137
+ </div>
138
+
139
+ <spark-overlay
140
+ position="left"
141
+ :overlay-instance="sparkOverlayService.left"
142
+ @close="sparkOverlayService.closeLeft"
143
+ />
144
+ <spark-overlay
145
+ position="right"
146
+ :overlay-instance="sparkOverlayService.right"
147
+ @close="sparkOverlayService.closeRight"
148
+ />
149
+
150
+ <spark-modal-container />
151
+ </template>
152
+
153
+ <script setup>
154
+ import { computed } from 'vue'
155
+ import {
156
+ SparkOverlay,
157
+ SparkBrandSelector,
158
+ useSparkBrandFilterStore,
159
+ SparkAppSelector,
160
+ sparkOverlayService,
161
+ SparkModalContainer,
162
+ Icons,
163
+ } from '@/index.js'
164
+
165
+ const props = defineProps({
166
+ appStore: {
167
+ type: Object,
168
+ required: true,
169
+ },
170
+ mainNavStore: {
171
+ type: Object,
172
+ required: true,
173
+ },
174
+ })
175
+
176
+ const sparkBrandFilterStore = useSparkBrandFilterStore()
177
+
178
+ const toggleAppSelector = () => {
179
+ sparkOverlayService.showRight(SparkAppSelector, {
180
+ currentApp: props.appStore.state.app,
181
+ })
182
+ }
183
+
184
+ const toggleBrandSelector = () => {
185
+ sparkOverlayService.showLeft(
186
+ SparkBrandSelector,
187
+ {},
188
+ {
189
+ select: (brand) => {
190
+ sparkBrandFilterStore.toggleBrand(brand)
191
+ sparkOverlayService.closeLeft()
192
+ },
193
+ },
194
+ )
195
+ }
196
+
197
+ const sidebarClass = computed(() => {
198
+ if (props.mainNavStore.state.hidden) {
199
+ return ['w-0 overflow-hidden']
200
+ }
201
+ return [props.mainNavStore.state.collapsed ? 'w-[80px]' : 'w-[240px]']
202
+ })
203
+
204
+ const contentClass = computed(() => {
205
+ if (props.mainNavStore.state.hidden) {
206
+ return ['pl-2.5']
207
+ }
208
+ return [props.mainNavStore.state.collapsed ? 'pl-[80px]' : 'pl-[240px]']
209
+ })
210
+ </script>
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <main class="h-full">
3
+ <router-view />
4
+ </main>
5
+ </template>
6
+
7
+ <script setup>
8
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as SparkDefaultContainer } from './SparkDefaultContainer.vue'
2
+ export { default as SparkPublicContainer } from './SparkPublicContainer.vue'
package/src/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './components/index.js'
2
2
  export * from './composables/index.js'
3
+ export * from './containers/index.js'
3
4
  export * from './plugins/index.js'
4
- export * from './stores/index.js'
5
+ export * from './stores/index.js'
6
+ export * from './views/index.js'
@@ -1,37 +1,85 @@
1
1
  import { library } from '@fortawesome/fontawesome-svg-core'
2
+ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
2
3
 
3
4
  import {
4
- faExclamationTriangle,
5
+ faArrowLeftToLine,
6
+ faArrowRightToLine,
7
+ faBarsSort,
8
+ faBellRing,
9
+ faBullhorn,
10
+ faCheck,
5
11
  faCheckCircle,
6
- faInfoCircle,
12
+ faChevronDown,
13
+ faChevronLeft,
14
+ faChevronRight,
15
+ faChevronUp,
16
+ faCircleUser,
7
17
  faCircleXmark,
8
- faXmark,
9
- faCheck,
10
- faTimes,
18
+ faComments,
19
+ faEdit,
20
+ faEllipsis,
21
+ faEllipsisVertical,
22
+ faExclamationTriangle,
23
+ faFaceSmileRelaxed,
24
+ faFaceSmileWink,
25
+ faFlag,
26
+ faGearComplex,
27
+ faGripDotsVertical,
28
+ faInfoCircle,
11
29
  faLaptopMobile,
30
+ faLayerPlus,
12
31
  faSatelliteDish,
13
32
  faScaleBalanced,
33
+ faSort,
34
+ faSortDown,
35
+ faSortUp,
14
36
  faStreetView,
15
- faFaceSmileRelaxed,
16
- faComments,
37
+ faTimes,
38
+ faXmark,
17
39
  } from '@fortawesome/pro-regular-svg-icons'
18
40
 
19
41
  export const Icons = {
20
- farExclamationTriangle: faExclamationTriangle,
42
+ farArrowLeftToLine: faArrowLeftToLine,
43
+ farArrowRightToLine: faArrowRightToLine,
44
+ farBarsSort: faBarsSort,
45
+ farBellRing: faBellRing,
46
+ farBullhorn: faBullhorn,
47
+ farCheck: faCheck,
21
48
  farCheckCircle: faCheckCircle,
22
- farInfoCircle: faInfoCircle,
49
+ farChevronDown: faChevronDown,
50
+ farChevronLeft: faChevronLeft,
51
+ farChevronRight: faChevronRight,
52
+ farChevronUp: faChevronUp,
53
+ farCircleUser: faCircleUser,
23
54
  farCircleXmark: faCircleXmark,
24
- farXmark: faXmark,
25
- farCheck: faCheck,
26
- farTimes: faTimes,
55
+ farComments: faComments,
56
+ farEdit: faEdit,
57
+ farEllipsis: faEllipsis,
58
+ farEllipsisVertical: faEllipsisVertical,
59
+ farExclamationTriangle: faExclamationTriangle,
60
+ farFaceSmileRelaxed: faFaceSmileRelaxed,
61
+ farFaceSmileWink: faFaceSmileWink,
62
+ farFlag: faFlag,
63
+ farGearComplex: faGearComplex,
64
+ farGripDotsVertical: faGripDotsVertical,
65
+ farInfoCircle: faInfoCircle,
27
66
  farLaptopMobile: faLaptopMobile,
67
+ farLayerPlus: faLayerPlus,
28
68
  farSatelliteDish: faSatelliteDish,
29
69
  farScaleBalanced: faScaleBalanced,
70
+ farSort: faSort,
71
+ farSortDown: faSortDown,
72
+ farSortUp: faSortUp,
30
73
  farStreetView: faStreetView,
31
- farFaceSmileRelaxed: faFaceSmileRelaxed,
32
- farComments: faComments,
74
+ farTimes: faTimes,
75
+ farXmark: faXmark,
33
76
  }
34
77
 
35
78
  export function addSparkIcons() {
36
79
  library.add(...Object.values(Icons))
80
+ }
81
+
82
+ export function setupFontAwesome(app) {
83
+ library.add(...Object.values(Icons))
84
+ app.component('FontAwesomeIcon', FontAwesomeIcon)
37
85
  }
@@ -1 +1 @@
1
- export { Icons, addSparkIcons } from './fontawesome.js'
1
+ export { Icons, addSparkIcons, setupFontAwesome } from './fontawesome.js'
@@ -0,0 +1,19 @@
1
+ import { defineStore } from 'pinia'
2
+ import { reactive } from 'vue'
3
+
4
+ export const useSparkAppStore = defineStore('sparkApp', () => {
5
+ const state = reactive({
6
+ app: '',
7
+ icon: '',
8
+ })
9
+
10
+ const initialize = (config = {}) => {
11
+ state.app = config.app || ''
12
+ state.icon = config.icon || ''
13
+ }
14
+
15
+ return {
16
+ state,
17
+ initialize,
18
+ }
19
+ })
@@ -1 +1,3 @@
1
- export * from './brand-filter.js'
1
+ export * from './app.js'
2
+ export * from './brand-filter.js'
3
+ export * from './navigation.js'
@@ -0,0 +1,68 @@
1
+ import { defineStore } from 'pinia'
2
+ import { reactive } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+
5
+ export const useSparkNavStore = defineStore('sparkNav', () => {
6
+ const state = reactive({
7
+ menu: [],
8
+ collapsed: false,
9
+ hidden: false,
10
+ })
11
+
12
+ const router = useRouter()
13
+
14
+ const initialize = (menuConfig = []) => {
15
+ state.menu = menuConfig
16
+ }
17
+
18
+ const findMenuItemByRoute = (items, route) => {
19
+ for (const item of items) {
20
+ if (item.href === route) return item
21
+ if (item.children) {
22
+ const found = findMenuItemByRoute(item.children, route)
23
+ if (found) return found
24
+ }
25
+ }
26
+ return null
27
+ }
28
+
29
+ const goto = async (route) => {
30
+ if (route) {
31
+ const updateMenuItemCurrent = (items) => {
32
+ items.forEach((item) => {
33
+ item.current = item.href === route
34
+ if (item.children) {
35
+ updateMenuItemCurrent(item.children)
36
+ }
37
+ })
38
+ }
39
+
40
+ updateMenuItemCurrent(state.menu)
41
+
42
+ const menuItem = findMenuItemByRoute(state.menu, route)
43
+
44
+ if (menuItem && typeof menuItem.action === 'function') {
45
+ menuItem.action()
46
+ return
47
+ }
48
+
49
+ await router.push(route)
50
+ }
51
+ }
52
+
53
+ const toggleCollapsed = () => {
54
+ state.collapsed = !state.collapsed
55
+ }
56
+
57
+ const toggleHidden = () => {
58
+ state.hidden = !state.hidden
59
+ }
60
+
61
+ return {
62
+ state,
63
+ initialize,
64
+ goto,
65
+ toggleCollapsed,
66
+ toggleHidden,
67
+ }
68
+ })