@wishbone-media/spark 0.2.2 → 0.4.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/README.md +30 -0
- package/dist/index.js +399 -267
- package/package.json +12 -9
- package/src/components/SparkButton.vue +162 -0
- package/src/components/SparkButtonGroup.vue +27 -0
- package/src/components/SparkModalDialog.vue +13 -37
- package/src/components/index.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishbone-media/spark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"src"
|
|
10
10
|
],
|
|
11
11
|
"peerDependencies": {
|
|
12
|
-
"@
|
|
13
|
-
"@fortawesome/
|
|
14
|
-
"@
|
|
15
|
-
"pinia": "^3.0.
|
|
16
|
-
"vue": "^3.5.
|
|
12
|
+
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
|
13
|
+
"@fortawesome/pro-regular-svg-icons": "^7.1.0",
|
|
14
|
+
"@headlessui/vue": "^1.7.23",
|
|
15
|
+
"pinia": "^3.0.4",
|
|
16
|
+
"vue": "^3.5.24"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@vitejs/plugin-vue": "^
|
|
20
|
-
"vite": "^
|
|
21
|
-
"vue": "^3.5.
|
|
19
|
+
"@vitejs/plugin-vue": "^6.0.2",
|
|
20
|
+
"vite": "^7.2.2",
|
|
21
|
+
"vue": "^3.5.24"
|
|
22
22
|
},
|
|
23
23
|
"packageManager": "pnpm@10.11.1",
|
|
24
24
|
"scripts": {
|
|
@@ -33,5 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@wishbone-media/spark": "file:"
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
ref="buttonRef"
|
|
5
|
+
:class="computedButtonClass"
|
|
6
|
+
:disabled="disabled"
|
|
7
|
+
@click="$emit('click')"
|
|
8
|
+
>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</button>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { computed, inject, ref } from 'vue'
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
size: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: 'md',
|
|
20
|
+
validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
|
|
21
|
+
},
|
|
22
|
+
variant: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'primary',
|
|
25
|
+
},
|
|
26
|
+
buttonClass: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: '',
|
|
29
|
+
},
|
|
30
|
+
disabled: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const buttonRef = ref(null)
|
|
37
|
+
const buttonGroup = inject('buttonGroup', null)
|
|
38
|
+
|
|
39
|
+
const groupPosition = computed(() => {
|
|
40
|
+
if (!buttonGroup?.isInGroup || !buttonRef.value) return null
|
|
41
|
+
|
|
42
|
+
const index = buttonGroup.getButtonIndex(buttonRef.value)
|
|
43
|
+
const total = buttonGroup.getButtonCount()
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
isFirst: index === 0,
|
|
47
|
+
isLast: index === total - 1,
|
|
48
|
+
index,
|
|
49
|
+
total,
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const CONFLICT_GROUPS = {
|
|
54
|
+
paddingX: /^px-/,
|
|
55
|
+
paddingY: /^py-/,
|
|
56
|
+
paddingAll: /^p-/,
|
|
57
|
+
marginX: /^mx-/,
|
|
58
|
+
marginY: /^my-/,
|
|
59
|
+
marginAll: /^m-/,
|
|
60
|
+
borderRadius: /^rounded-/,
|
|
61
|
+
background: /^bg-/,
|
|
62
|
+
text: /^text-(?!white|black)/,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mergeClasses = (baseClasses, overrideClasses) => {
|
|
66
|
+
if (!overrideClasses) return baseClasses
|
|
67
|
+
|
|
68
|
+
const base = baseClasses.split(' ').filter(Boolean)
|
|
69
|
+
const overrides = overrideClasses.split(' ').filter(Boolean)
|
|
70
|
+
|
|
71
|
+
const filtered = base.filter((baseClass) => {
|
|
72
|
+
return !overrides.some((override) => {
|
|
73
|
+
const baseGroup = Object.entries(CONFLICT_GROUPS).find(([_, regex]) =>
|
|
74
|
+
regex.test(baseClass),
|
|
75
|
+
)?.[0]
|
|
76
|
+
|
|
77
|
+
const overrideGroup = Object.entries(CONFLICT_GROUPS).find(([_, regex]) =>
|
|
78
|
+
regex.test(override),
|
|
79
|
+
)?.[0]
|
|
80
|
+
|
|
81
|
+
return baseGroup && baseGroup === overrideGroup
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return [...filtered, ...overrides].join(' ')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const computedButtonClass = computed(() => {
|
|
89
|
+
let classes = ''
|
|
90
|
+
let roundingSize = ''
|
|
91
|
+
|
|
92
|
+
switch (props.size) {
|
|
93
|
+
case 'xs':
|
|
94
|
+
classes += ' px-2 py-1 text-xs'
|
|
95
|
+
roundingSize = 'sm'
|
|
96
|
+
break
|
|
97
|
+
case 'sm':
|
|
98
|
+
classes += ' px-2 py-1 text-sm'
|
|
99
|
+
roundingSize = 'sm'
|
|
100
|
+
break
|
|
101
|
+
case 'md':
|
|
102
|
+
classes += ' px-2.5 py-1.5 text-sm'
|
|
103
|
+
roundingSize = 'md'
|
|
104
|
+
break
|
|
105
|
+
case 'lg':
|
|
106
|
+
classes += ' px-3 py-2 text-sm'
|
|
107
|
+
roundingSize = 'md'
|
|
108
|
+
break
|
|
109
|
+
case 'xl':
|
|
110
|
+
classes += ' px-3.5 py-2.5 text-sm'
|
|
111
|
+
roundingSize = 'md'
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (buttonGroup?.isInGroup && groupPosition.value) {
|
|
116
|
+
const { isFirst, isLast } = groupPosition.value
|
|
117
|
+
|
|
118
|
+
classes += ` relative inline-flex items-center focus:z-10`
|
|
119
|
+
|
|
120
|
+
if (isFirst && isLast) {
|
|
121
|
+
// Single button in group
|
|
122
|
+
classes += ` rounded-${roundingSize}`
|
|
123
|
+
} else if (isFirst) {
|
|
124
|
+
classes += ` rounded-l-${roundingSize} rounded-r-none`
|
|
125
|
+
} else if (isLast) {
|
|
126
|
+
classes += ` rounded-r-${roundingSize} rounded-l-none -ml-px`
|
|
127
|
+
} else {
|
|
128
|
+
classes += ' rounded-none -ml-px'
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
classes += ` shadow-xs rounded-${roundingSize}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
switch (props.variant) {
|
|
135
|
+
case 'primary':
|
|
136
|
+
classes += ' bg-primary-600 hover:bg-primary-500 text-white'
|
|
137
|
+
break
|
|
138
|
+
case 'secondary':
|
|
139
|
+
classes += ' ring-1 ring-gray-300 ring-inset bg-white hover:bg-gray-50 text-gray-900'
|
|
140
|
+
break
|
|
141
|
+
case 'success':
|
|
142
|
+
classes += ' bg-green-600 hover:bg-green-500 text-white'
|
|
143
|
+
break
|
|
144
|
+
case 'warning':
|
|
145
|
+
classes += ' bg-amber-600 hover:bg-amber-500 text-white'
|
|
146
|
+
break
|
|
147
|
+
case 'danger':
|
|
148
|
+
classes += ' bg-red-600 hover:bg-red-500 text-white'
|
|
149
|
+
break
|
|
150
|
+
case 'info':
|
|
151
|
+
classes += ' bg-cyan-600 hover:bg-cyan-500 text-white'
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
default:
|
|
155
|
+
classes += ` bg-${props.variant}-600 hover:bg-${props.variant}-500 text-white`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
classes = mergeClasses(classes, props.buttonClass)
|
|
159
|
+
|
|
160
|
+
return classes
|
|
161
|
+
})
|
|
162
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="inline-flex rounded-md shadow-xs" ref="groupRef">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { provide, ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
const groupRef = ref(null)
|
|
11
|
+
|
|
12
|
+
const getButtonIndex = (buttonElement) => {
|
|
13
|
+
if (!groupRef.value) return -1
|
|
14
|
+
const buttons = Array.from(groupRef.value.children)
|
|
15
|
+
return buttons.indexOf(buttonElement)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getButtonCount = () => {
|
|
19
|
+
return groupRef.value?.children.length || 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
provide('buttonGroup', {
|
|
23
|
+
isInGroup: true,
|
|
24
|
+
getButtonIndex,
|
|
25
|
+
getButtonCount,
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
@@ -20,16 +20,14 @@
|
|
|
20
20
|
|
|
21
21
|
<!-- Actions -->
|
|
22
22
|
<div class="mt-5 sm:mt-6" :class="buttonContainerClass">
|
|
23
|
-
<button
|
|
23
|
+
<spark-button
|
|
24
24
|
v-for="(button, index) in buttonsToShow"
|
|
25
25
|
:key="index"
|
|
26
|
-
|
|
27
|
-
:class="getButtonClass(button, index)"
|
|
28
|
-
class="inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-xs focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
26
|
+
:variant="button.variant"
|
|
29
27
|
@click="$emit(button.event, button)"
|
|
30
28
|
>
|
|
31
29
|
{{ button.text }}
|
|
32
|
-
</button>
|
|
30
|
+
</spark-button>
|
|
33
31
|
</div>
|
|
34
32
|
</div>
|
|
35
33
|
</template>
|
|
@@ -37,6 +35,7 @@
|
|
|
37
35
|
<script setup>
|
|
38
36
|
import { computed } from 'vue'
|
|
39
37
|
import { Icons } from '@/plugins/fontawesome'
|
|
38
|
+
import { SparkButton } from "./index.js";
|
|
40
39
|
|
|
41
40
|
const props = defineProps({
|
|
42
41
|
title: {
|
|
@@ -79,41 +78,18 @@ const buttonsToShow = computed(() => {
|
|
|
79
78
|
})
|
|
80
79
|
|
|
81
80
|
const buttonContainerClass = computed(() => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
switch (buttonsToShow.value.length) {
|
|
82
|
+
case 1:
|
|
83
|
+
return 'sm:grid sm:grid-flow-row-dense'
|
|
84
|
+
case 2:
|
|
85
|
+
return 'sm:grid sm:grid-cols-2 sm:gap-3';
|
|
86
|
+
case 3:
|
|
87
|
+
return 'sm:grid sm:grid-cols-3 sm:gap-3';
|
|
88
|
+
default:
|
|
89
|
+
return 'flex flex-col gap-3';
|
|
87
90
|
}
|
|
88
|
-
return ''
|
|
89
91
|
})
|
|
90
92
|
|
|
91
|
-
const getButtonClass = (button, index) => {
|
|
92
|
-
const isPrimary = button.variant === 'primary'
|
|
93
|
-
const buttonCount = buttonsToShow.value.length
|
|
94
|
-
|
|
95
|
-
let baseClass = ''
|
|
96
|
-
|
|
97
|
-
// Position classes for 2-button layout
|
|
98
|
-
if (buttonCount === 2) {
|
|
99
|
-
if (isPrimary) {
|
|
100
|
-
baseClass = 'sm:col-start-2'
|
|
101
|
-
} else {
|
|
102
|
-
baseClass = 'mt-3 sm:col-start-1 sm:mt-0'
|
|
103
|
-
}
|
|
104
|
-
} else if (buttonCount > 2 && index > 0) {
|
|
105
|
-
baseClass = 'mt-3'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Style classes
|
|
109
|
-
if (isPrimary) {
|
|
110
|
-
return `bg-primary-600 text-white hover:bg-primary-500 focus-visible:outline-primary-600 ${baseClass}`
|
|
111
|
-
} else {
|
|
112
|
-
// Secondary button style
|
|
113
|
-
return `bg-white text-gray-900 ring-1 ring-gray-300 ring-inset hover:bg-gray-50 ${baseClass}`
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
93
|
const defaultIcons = {
|
|
118
94
|
info: 'farInfoCircle',
|
|
119
95
|
success: 'farCheckCircle',
|
package/src/components/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { default as SparkAlert } from './SparkAlert.vue'
|
|
2
2
|
export { default as SparkAppSelector } from './SparkAppSelector.vue'
|
|
3
3
|
export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
|
|
4
|
+
export { default as SparkButton } from './SparkButton.vue'
|
|
5
|
+
export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
|
|
4
6
|
export { default as SparkModalContainer } from './SparkModalContainer.vue'
|
|
5
7
|
export { default as SparkModalDialog } from './SparkModalDialog.vue'
|
|
6
8
|
export { default as SparkOverlay } from './SparkOverlay.vue'
|