@saasmakers/ui 1.3.8 → 1.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/app/components/bases/BaseToast.stories.ts +13 -0
- package/app/components/bases/BaseToast.vue +31 -0
- package/app/components/fields/FieldInput.stories.ts +10 -0
- package/app/components/fields/FieldInput.vue +10 -1
- package/app/components/layout/LayoutToasts.vue +8 -0
- package/app/composables/useToasts.ts +4 -3
- package/app/types/bases.d.ts +6 -0
- package/app/types/fields.d.ts +1 -0
- package/app/types/global.d.ts +2 -0
- package/package.json +1 -1
|
@@ -27,3 +27,16 @@ export const Default: StoryObj<typeof BaseToast> = {
|
|
|
27
27
|
text: 'Toast message',
|
|
28
28
|
} satisfies Partial<BaseToast>,
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export const WithAction: StoryObj<typeof BaseToast> = {
|
|
32
|
+
args: {
|
|
33
|
+
action: {
|
|
34
|
+
label: 'Undo',
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
onClick: () => console.log('action clicked'),
|
|
37
|
+
},
|
|
38
|
+
id: 'toast-2',
|
|
39
|
+
status: 'success',
|
|
40
|
+
text: 'Toast message',
|
|
41
|
+
} satisfies Partial<BaseToast>,
|
|
42
|
+
}
|
|
@@ -9,11 +9,18 @@ const props = withDefaults(defineProps<BaseToast>(), {
|
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
const emit = defineEmits<{
|
|
12
|
+
action: [event: MouseEvent, toast: BaseToast]
|
|
12
13
|
close: [event: MouseEvent, toast: BaseToast]
|
|
13
14
|
}>()
|
|
14
15
|
|
|
15
16
|
const { getIcon } = useLayerIcons()
|
|
16
17
|
|
|
18
|
+
function onAction(event: KeyboardEvent | MouseEvent) {
|
|
19
|
+
if (props.action) {
|
|
20
|
+
emit('action', event as MouseEvent, props)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
function onClose(event: KeyboardEvent | MouseEvent) {
|
|
18
25
|
if (props.hasClose) {
|
|
19
26
|
emit('close', event as MouseEvent, props)
|
|
@@ -37,6 +44,30 @@ function onClose(event: KeyboardEvent | MouseEvent) {
|
|
|
37
44
|
:text="text"
|
|
38
45
|
/>
|
|
39
46
|
|
|
47
|
+
<template v-if="action">
|
|
48
|
+
<div class="ml-2 mr-1.5 h-5 border-l border-gray-700 flex-initial dark:border-gray-300" />
|
|
49
|
+
|
|
50
|
+
<button
|
|
51
|
+
class="cursor-pointer bg-transparent flex-initial"
|
|
52
|
+
type="button"
|
|
53
|
+
@click.stop="onAction"
|
|
54
|
+
>
|
|
55
|
+
<BaseText
|
|
56
|
+
bold
|
|
57
|
+
class="pointer-events-none"
|
|
58
|
+
:class="{
|
|
59
|
+
'text-red-400 dark:text-red-600': status === 'error',
|
|
60
|
+
'text-indigo-400 dark:text-indigo-600': status === 'info',
|
|
61
|
+
'text-orange-400 dark:text-orange-600': status === 'warning',
|
|
62
|
+
'text-teal-400 dark:text-teal-600': status === 'success',
|
|
63
|
+
}"
|
|
64
|
+
no-wrap
|
|
65
|
+
:text="action.label"
|
|
66
|
+
uppercase
|
|
67
|
+
/>
|
|
68
|
+
</button>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
40
71
|
<template v-if="hasClose">
|
|
41
72
|
<div class="ml-2 mr-1.5 h-5 border-l border-gray-700 flex-initial dark:border-gray-300" />
|
|
42
73
|
|
|
@@ -50,6 +50,7 @@ export default {
|
|
|
50
50
|
] satisfies FieldSize[],
|
|
51
51
|
},
|
|
52
52
|
slugOnly: { control: 'boolean' },
|
|
53
|
+
trim: { control: 'boolean' },
|
|
53
54
|
type: {
|
|
54
55
|
control: 'select',
|
|
55
56
|
options: [
|
|
@@ -88,3 +89,12 @@ export const SlugOnly: StoryObj<typeof FieldInput> = {
|
|
|
88
89
|
slugOnly: true,
|
|
89
90
|
} satisfies Partial<FieldInput>,
|
|
90
91
|
}
|
|
92
|
+
|
|
93
|
+
export const Trim: StoryObj<typeof FieldInput> = {
|
|
94
|
+
args: {
|
|
95
|
+
label: 'First name',
|
|
96
|
+
modelValue: 'Julien ',
|
|
97
|
+
placeholder: 'Enter your first name',
|
|
98
|
+
trim: true,
|
|
99
|
+
} satisfies Partial<FieldInput>,
|
|
100
|
+
}
|
|
@@ -22,6 +22,7 @@ const props = withDefaults(defineProps<Omit<FieldInput, 'modelValue'>>(), {
|
|
|
22
22
|
required: false,
|
|
23
23
|
size: 'base',
|
|
24
24
|
slugOnly: false,
|
|
25
|
+
trim: false,
|
|
25
26
|
type: 'text',
|
|
26
27
|
uppercase: false,
|
|
27
28
|
validation: undefined,
|
|
@@ -67,6 +68,10 @@ function focus() {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function onFieldBlur(event: FocusEvent) {
|
|
71
|
+
if (props.trim) {
|
|
72
|
+
modelValue.value = modelValue.value.toString().trim()
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
emit('blur', event)
|
|
71
76
|
}
|
|
72
77
|
|
|
@@ -99,7 +104,11 @@ function onFieldKeyDown(event: KeyboardEvent) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
else if (event.key === 'Enter' && modelValue.value) {
|
|
102
|
-
|
|
107
|
+
if (props.trim) {
|
|
108
|
+
modelValue.value = modelValue.value.toString().trim()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
emit('submit', event, modelValue.value.toString())
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
else if (props.type === 'number' && [
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
const { fadeInUp } = useMotion()
|
|
3
3
|
const toasts = useToasts()
|
|
4
4
|
|
|
5
|
+
async function onToastAction(_event: MouseEvent, toast: BaseToast) {
|
|
6
|
+
toasts.closeToast(toast.id)
|
|
7
|
+
|
|
8
|
+
await toast.action?.onClick()
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
function onToastClose(_event: MouseEvent, toast: BaseToast) {
|
|
6
12
|
toasts.closeToast(toast.id)
|
|
7
13
|
}
|
|
@@ -20,8 +26,10 @@ function onToastClose(_event: MouseEvent, toast: BaseToast) {
|
|
|
20
26
|
<BaseToast
|
|
21
27
|
:id="toast.id"
|
|
22
28
|
:key="toast.id"
|
|
29
|
+
:action="toast.action"
|
|
23
30
|
:status="toast.status"
|
|
24
31
|
:text="toast.text"
|
|
32
|
+
@action="onToastAction"
|
|
25
33
|
@close="onToastClose"
|
|
26
34
|
/>
|
|
27
35
|
</Motion>
|
|
@@ -17,9 +17,10 @@ export default function useToast() {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const createToast = (message: string, status: BaseStatus = 'success', i18nParams?: Record<string, number | string
|
|
20
|
+
const createToast = (message: string, status: BaseStatus = 'success', i18nParams?: Record<string, number | string>, action?: BaseToastAction) => {
|
|
21
21
|
if (import.meta.client) {
|
|
22
22
|
const toast: BaseToast = {
|
|
23
|
+
action,
|
|
23
24
|
id: crypto.randomUUID(),
|
|
24
25
|
status: status || 'success',
|
|
25
26
|
text: message.includes(' ') ? message : nuxtApp.$i18n.t(`toasts.${message}`, i18nParams || {}),
|
|
@@ -27,8 +28,8 @@ export default function useToast() {
|
|
|
27
28
|
|
|
28
29
|
toasts.value.push(toast)
|
|
29
30
|
|
|
30
|
-
//
|
|
31
|
-
setTimeout(() => closeToast(toast.id), 5 * 1000)
|
|
31
|
+
// Give more time to interact when the toast offers an action
|
|
32
|
+
setTimeout(() => closeToast(toast.id), (action ? 8 : 5) * 1000)
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
package/app/types/bases.d.ts
CHANGED
|
@@ -358,8 +358,14 @@ export type BaseTextText = string | {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
export interface BaseToast {
|
|
361
|
+
action?: BaseToastAction
|
|
361
362
|
hasClose?: boolean
|
|
362
363
|
id: string
|
|
363
364
|
status?: BaseStatus
|
|
364
365
|
text: BaseTextText
|
|
365
366
|
}
|
|
367
|
+
|
|
368
|
+
export interface BaseToastAction {
|
|
369
|
+
label: BaseTextText
|
|
370
|
+
onClick: () => Promise<void> | void
|
|
371
|
+
}
|
package/app/types/fields.d.ts
CHANGED
package/app/types/global.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ declare global {
|
|
|
57
57
|
type BaseText = Bases.BaseText
|
|
58
58
|
type BaseTextText = Bases.BaseTextText
|
|
59
59
|
type BaseToast = Bases.BaseToast
|
|
60
|
+
type BaseToastAction = Bases.BaseToastAction
|
|
61
|
+
|
|
60
62
|
// Libraries
|
|
61
63
|
type ChartistBarChartData = import('chartist').BarChartData
|
|
62
64
|
type ChartistLineChartData = import('chartist').LineChartData
|