@saasmakers/ui 0.1.35 → 0.1.36
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.
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { BaseIcon } from '../../types/bases'
|
|
3
|
+
import { NuxtLinkLocale } from '#components'
|
|
4
|
+
import { getIcon } from '../../composables/useIcons'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<BaseIcon>(), {
|
|
7
|
+
bold: false,
|
|
8
|
+
clickable: false,
|
|
9
|
+
color: undefined,
|
|
10
|
+
confirmation: false,
|
|
11
|
+
icon: undefined,
|
|
12
|
+
loading: false,
|
|
13
|
+
reverse: false,
|
|
14
|
+
size: 'base',
|
|
15
|
+
status: undefined,
|
|
16
|
+
text: '',
|
|
17
|
+
to: undefined,
|
|
18
|
+
truncate: false,
|
|
19
|
+
underline: false,
|
|
20
|
+
uppercase: true,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits<{
|
|
24
|
+
click: [event: MouseEvent]
|
|
25
|
+
confirm: [event: MouseEvent]
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
const confirming = ref(false)
|
|
29
|
+
|
|
30
|
+
const { t } = useI18n()
|
|
31
|
+
|
|
32
|
+
const statusIcon = computed((): string | undefined => {
|
|
33
|
+
switch (props.status) {
|
|
34
|
+
case 'error':
|
|
35
|
+
return getIcon('closeCircle')
|
|
36
|
+
|
|
37
|
+
case 'info':
|
|
38
|
+
return getIcon('infoCircle')
|
|
39
|
+
|
|
40
|
+
case 'success':
|
|
41
|
+
return getIcon('checkCircle')
|
|
42
|
+
|
|
43
|
+
case 'warning':
|
|
44
|
+
return getIcon('exclamationCircle')
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const statusColor = computed((): BaseColor | undefined => {
|
|
52
|
+
if (props.status === 'error') {
|
|
53
|
+
return 'red'
|
|
54
|
+
}
|
|
55
|
+
else if (props.status === 'info') {
|
|
56
|
+
return 'indigo'
|
|
57
|
+
}
|
|
58
|
+
else if (props.status === 'success') {
|
|
59
|
+
return 'green'
|
|
60
|
+
}
|
|
61
|
+
else if (props.status === 'warning') {
|
|
62
|
+
return 'orange'
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const finalIcon = computed(() => {
|
|
67
|
+
return statusIcon.value || props.icon
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const finalColor = computed(() => {
|
|
71
|
+
return statusColor.value || props.color
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function onClick(event: MouseEvent) {
|
|
75
|
+
if (props.confirmation) {
|
|
76
|
+
if (confirming.value) {
|
|
77
|
+
emit('confirm', event)
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
setTimeout(() => (confirming.value = false), 3 * 1000)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
confirming.value = !confirming.value
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
emit('confirm', event)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
emit('click', event)
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<div
|
|
95
|
+
class="flex items-center"
|
|
96
|
+
:class="{
|
|
97
|
+
'cursor-pointer hover:underline hover:text-gray-900 dark:hover:text-gray-100': clickable || to,
|
|
98
|
+
'flex-row': !reverse,
|
|
99
|
+
'flex-row-reverse': reverse,
|
|
100
|
+
}"
|
|
101
|
+
@click="onClick"
|
|
102
|
+
>
|
|
103
|
+
<component
|
|
104
|
+
:is="to ? NuxtLinkLocale : 'span'"
|
|
105
|
+
v-if="finalIcon && !loading"
|
|
106
|
+
class="flex items-center justify-center"
|
|
107
|
+
:class="{
|
|
108
|
+
'text-gray-900 dark:text-gray-100': finalColor === 'black',
|
|
109
|
+
'text-green-600 dark:text-green-400': finalColor === 'green',
|
|
110
|
+
'text-gray-600 dark:text-gray-400': finalColor === 'gray',
|
|
111
|
+
'text-indigo-600 dark:text-indigo-400': finalColor === 'indigo',
|
|
112
|
+
'text-orange-600 dark:text-orange-400': finalColor === 'orange',
|
|
113
|
+
'text-red-600 dark:text-red-400': finalColor === 'red',
|
|
114
|
+
'text-gray-100 dark:text-gray-900': finalColor === 'white',
|
|
115
|
+
}"
|
|
116
|
+
:to="to"
|
|
117
|
+
>
|
|
118
|
+
<Icon :name="finalIcon" />
|
|
119
|
+
</component>
|
|
120
|
+
|
|
121
|
+
<BaseSpinner
|
|
122
|
+
v-else-if="loading"
|
|
123
|
+
:color="color"
|
|
124
|
+
:size="size"
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<BaseText
|
|
128
|
+
v-if="text"
|
|
129
|
+
:bold="bold"
|
|
130
|
+
class="flex-1"
|
|
131
|
+
:has-margin="!!finalIcon || loading"
|
|
132
|
+
no-wrap
|
|
133
|
+
:reverse="reverse"
|
|
134
|
+
:size="size"
|
|
135
|
+
:text="confirming ? t('globals.confirm') : text"
|
|
136
|
+
:truncate="truncate"
|
|
137
|
+
:underline="underline"
|
|
138
|
+
:uppercase="uppercase"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { BaseSpinner } from '../../types/bases'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<BaseSpinner>(), {
|
|
5
|
+
clickable: false,
|
|
6
|
+
color: 'black',
|
|
7
|
+
id: '',
|
|
8
|
+
reverse: false,
|
|
9
|
+
size: 'base',
|
|
10
|
+
text: '',
|
|
11
|
+
uppercase: true,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits<{
|
|
15
|
+
click: [event: MouseEvent, id: string]
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
function onClick(event: MouseEvent) {
|
|
19
|
+
emit('click', event, props.id)
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
class="flex items-center"
|
|
26
|
+
:class="{
|
|
27
|
+
'cursor-pointer': clickable,
|
|
28
|
+
'flex-row': !reverse,
|
|
29
|
+
'flex-row-reverse': reverse,
|
|
30
|
+
}"
|
|
31
|
+
@click="onClick"
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
class="relative inline-block cursor-wait flex-initial"
|
|
35
|
+
:class="{
|
|
36
|
+
'h-2 w-2': size === '3xs',
|
|
37
|
+
'h-3 w-3': size === '2xs',
|
|
38
|
+
'h-3.5 w-3.5': size === 'xs',
|
|
39
|
+
'h-4 w-4': size === 'sm',
|
|
40
|
+
'h-4.5 w-4.5': size === 'base',
|
|
41
|
+
'h-5 w-5': size === 'lg',
|
|
42
|
+
'h-6 w-6': size === 'xl',
|
|
43
|
+
'h-8 w-8': size === '2xl',
|
|
44
|
+
'h-10 w-10': size === '3xl',
|
|
45
|
+
'h-12 w-12': size === '4xl',
|
|
46
|
+
}"
|
|
47
|
+
@click="onClick"
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
v-for="wave in 2"
|
|
51
|
+
:key="wave"
|
|
52
|
+
class="base-spinner__wave absolute left-0 top-0 h-full w-full rounded-full opacity-75"
|
|
53
|
+
:class="{
|
|
54
|
+
'base-spinner__wave--delayed': wave === 2,
|
|
55
|
+
|
|
56
|
+
'bg-gray-900 dark:bg-gray-100': color === 'black' || !color,
|
|
57
|
+
'bg-gray-700 dark:bg-gray-300': color === 'gray',
|
|
58
|
+
'bg-green-700 dark:bg-green-300': color === 'green',
|
|
59
|
+
'bg-indigo-700 dark:bg-indigo-300': color === 'indigo',
|
|
60
|
+
'bg-orange-700 dark:bg-orange-300': color === 'orange',
|
|
61
|
+
'bg-red-700 dark:bg-red-300': color === 'red',
|
|
62
|
+
'bg-white dark:bg-gray-900': color === 'white',
|
|
63
|
+
}"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<BaseText
|
|
68
|
+
v-if="text"
|
|
69
|
+
class="flex-1"
|
|
70
|
+
has-margin
|
|
71
|
+
:reverse="reverse"
|
|
72
|
+
:size="size"
|
|
73
|
+
:text="text"
|
|
74
|
+
:uppercase="uppercase"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
.base-spinner__wave {
|
|
81
|
+
animation: bounce 2s infinite linear;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.base-spinner__wave--delayed {
|
|
85
|
+
animation-delay: -1s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes bounce {
|
|
89
|
+
0%,
|
|
90
|
+
100% {
|
|
91
|
+
-webkit-transform: scale(0);
|
|
92
|
+
transform: scale(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
50% {
|
|
96
|
+
-webkit-transform: scale(1);
|
|
97
|
+
transform: scale(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Important: Only use icons from the following packs
|
|
2
|
+
// - Hugeicons (brands and social medias)
|
|
3
|
+
// -> https://icones.js.org/collection/hugeicons
|
|
4
|
+
// - Solar (general UI elements)
|
|
5
|
+
// -> https://icones.js.org/collection/solar
|
|
6
|
+
// Material Design Icons (brands)
|
|
7
|
+
// -> https://icones.js.org/collection/mdi
|
|
8
|
+
// Custom icons
|
|
9
|
+
// -> ./assets/icons
|
|
10
|
+
|
|
11
|
+
const icons = {
|
|
12
|
+
checkCircle: 'hugeicons:checkmark-circle-02',
|
|
13
|
+
closeCircle: 'hugeicons:cancel-circle',
|
|
14
|
+
default: 'hugeicons:help-circle',
|
|
15
|
+
exclamationCircle: 'hugeicons:alert-circle',
|
|
16
|
+
infoCircle: 'hugeicons:information-circle',
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
export function getIcon(attribute: keyof typeof icons) {
|
|
20
|
+
return icons[attribute] || icons.default
|
|
21
|
+
}
|
package/app/types/bases.d.ts
CHANGED
|
@@ -36,6 +36,33 @@ export type BaseDividerBorderStyle = 'dashed' | 'dotted' | 'solid'
|
|
|
36
36
|
|
|
37
37
|
export type BaseDividerSize = 'base' | 'sm'
|
|
38
38
|
|
|
39
|
+
interface BaseIcon {
|
|
40
|
+
bold?: boolean
|
|
41
|
+
clickable?: boolean
|
|
42
|
+
color?: BaseColor
|
|
43
|
+
confirmation?: boolean
|
|
44
|
+
icon?: string
|
|
45
|
+
loading?: boolean
|
|
46
|
+
reverse?: boolean
|
|
47
|
+
size?: BaseSize
|
|
48
|
+
status?: BaseStatus
|
|
49
|
+
text?: BaseTextText
|
|
50
|
+
to?: RouteLocationRaw
|
|
51
|
+
truncate?: boolean
|
|
52
|
+
underline?: boolean
|
|
53
|
+
uppercase?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BaseSpinner {
|
|
57
|
+
clickable?: boolean
|
|
58
|
+
color?: BaseColor
|
|
59
|
+
id?: string
|
|
60
|
+
reverse?: boolean
|
|
61
|
+
size?: BaseSize
|
|
62
|
+
text?: BaseTextText
|
|
63
|
+
uppercase?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
39
66
|
export interface BaseText {
|
|
40
67
|
background?: BaseTextBackground
|
|
41
68
|
bold?: boolean
|
package/app/types/global.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ declare global {
|
|
|
13
13
|
type BaseDivider = Bases.BaseDivider
|
|
14
14
|
type BaseDividerBorderStyle = Bases.BaseDividerBorderStyle
|
|
15
15
|
type BaseDividerSize = Bases.BaseDividerSize
|
|
16
|
+
type BaseSpinner = Bases.BaseSpinner
|
|
17
|
+
type BaseIcon = Bases.BaseIcon
|
|
16
18
|
type BaseText = Bases.BaseText
|
|
17
19
|
type BaseTextBackground = Bases.BaseTextBackground
|
|
18
20
|
type BaseTextText = Bases.BaseTextText
|