@saasmakers/ui 1.4.35 → 1.4.37
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/BaseButton.vue +1 -2
- package/app/components/bases/BaseMessage.vue +1 -1
- package/app/components/bases/BaseMetric.vue +2 -2
- package/app/components/bases/BaseShortcut.stories.ts +24 -0
- package/app/components/bases/BaseShortcut.vue +63 -0
- package/app/components/bases/BaseToast.stories.ts +28 -0
- package/app/components/bases/BaseToast.vue +12 -5
- package/app/components/fields/FieldSelect.vue +2 -2
- package/app/components/layout/LayoutToasts.vue +7 -2
- package/app/composables/useLayerIcons.ts +1 -0
- package/app/types/bases.d.ts +7 -0
- package/app/types/global.d.ts +1 -0
- package/package.json +11 -11
|
@@ -58,7 +58,6 @@ function onClick(event: MouseEvent) {
|
|
|
58
58
|
:is="to ? NuxtLinkLocale : 'button'"
|
|
59
59
|
class="relative inline-block select-none appearance-none border focus-visible:outline-none"
|
|
60
60
|
:class="{
|
|
61
|
-
'inline-block': !circular,
|
|
62
61
|
'flex items-center justify-center': circular,
|
|
63
62
|
'cursor-not-allowed opacity-50': disabled,
|
|
64
63
|
'cursor-wait': loading,
|
|
@@ -131,7 +130,7 @@ function onClick(event: MouseEvent) {
|
|
|
131
130
|
|
|
132
131
|
<BaseSpinner
|
|
133
132
|
v-if="loading"
|
|
134
|
-
class="absolute left-1/2 top-1/2
|
|
133
|
+
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
135
134
|
:color="!background ? 'black' : light ? color : color === 'white' ? 'indigo' : 'white'"
|
|
136
135
|
:size="size"
|
|
137
136
|
/>
|
|
@@ -36,7 +36,7 @@ const performanceIcon = computed<string | undefined>(() => {
|
|
|
36
36
|
:class="{
|
|
37
37
|
'items-start': alignment === 'left',
|
|
38
38
|
'items-center': alignment === 'center',
|
|
39
|
-
'items-
|
|
39
|
+
'items-end': alignment === 'right',
|
|
40
40
|
}"
|
|
41
41
|
>
|
|
42
42
|
<BaseText
|
|
@@ -62,7 +62,7 @@ const performanceIcon = computed<string | undefined>(() => {
|
|
|
62
62
|
|
|
63
63
|
<BaseIcon
|
|
64
64
|
v-if="performanceIcon"
|
|
65
|
-
class="absolute right-0 translate-x-4
|
|
65
|
+
class="absolute right-0 translate-x-4"
|
|
66
66
|
:class="{
|
|
67
67
|
'-mt-0.5': performance === 'down',
|
|
68
68
|
'mt-0.25': performance === 'up',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import BaseShortcut from './BaseShortcut.vue'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
component: BaseShortcut,
|
|
5
|
+
title: 'Bases/BaseShortcut',
|
|
6
|
+
|
|
7
|
+
argTypes: {
|
|
8
|
+
active: { control: 'boolean' },
|
|
9
|
+
shortcut: { control: 'text' },
|
|
10
|
+
},
|
|
11
|
+
} satisfies Meta<typeof BaseShortcut>
|
|
12
|
+
|
|
13
|
+
export const Enter: StoryObj<typeof BaseShortcut> = { args: { shortcut: 'Enter' } satisfies Partial<BaseShortcut> }
|
|
14
|
+
|
|
15
|
+
export const Letter: StoryObj<typeof BaseShortcut> = { args: { shortcut: 'y' } satisfies Partial<BaseShortcut> }
|
|
16
|
+
|
|
17
|
+
export const Escape: StoryObj<typeof BaseShortcut> = { args: { shortcut: 'Escape' } satisfies Partial<BaseShortcut> }
|
|
18
|
+
|
|
19
|
+
export const Inactive: StoryObj<typeof BaseShortcut> = {
|
|
20
|
+
args: {
|
|
21
|
+
active: false,
|
|
22
|
+
shortcut: 'Enter',
|
|
23
|
+
} satisfies Partial<BaseShortcut>,
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { onKeyStroke } from '@vueuse/core'
|
|
3
|
+
import type { BaseShortcut } from '../../types/bases'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<BaseShortcut>(), {
|
|
6
|
+
active: true,
|
|
7
|
+
shortcut: 'Enter',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{
|
|
11
|
+
trigger: [event: KeyboardEvent]
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const { getIcon } = useLayerIcons()
|
|
15
|
+
|
|
16
|
+
function isTypingTarget(target: EventTarget | null) {
|
|
17
|
+
const element = target as HTMLElement | null
|
|
18
|
+
const typingTags = ['INPUT', 'SELECT', 'TEXTAREA']
|
|
19
|
+
|
|
20
|
+
if (!element) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (element.isContentEditable) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return typingTags.includes(element.tagName)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onKeyStroke(
|
|
32
|
+
event => event.key.toLowerCase() === props.shortcut.toLowerCase(),
|
|
33
|
+
(event) => {
|
|
34
|
+
if (!props.active) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isTypingTarget(event.target)) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
event.preventDefault()
|
|
43
|
+
emit('trigger', event)
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<kbd
|
|
50
|
+
class="h-3.5 min-w-3.5 inline-flex items-center justify-center border border-current/50 rounded px-0.5 text-2xs leading-none"
|
|
51
|
+
:class="{ 'font-mono uppercase': shortcut.toLowerCase() !== 'enter' }"
|
|
52
|
+
>
|
|
53
|
+
<Icon
|
|
54
|
+
v-if="shortcut.toLowerCase() === 'enter'"
|
|
55
|
+
class="size-2"
|
|
56
|
+
:name="getIcon('enter')"
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<template v-else>
|
|
60
|
+
{{ shortcut === 'Escape' ? 'esc' : shortcut }}
|
|
61
|
+
</template>
|
|
62
|
+
</kbd>
|
|
63
|
+
</template>
|
|
@@ -40,3 +40,31 @@ export const WithAction: StoryObj<typeof BaseToast> = {
|
|
|
40
40
|
text: 'Toast message',
|
|
41
41
|
} satisfies Partial<BaseToast>,
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
export const WithActionShortcutEnter: StoryObj<typeof BaseToast> = {
|
|
45
|
+
args: {
|
|
46
|
+
action: {
|
|
47
|
+
label: 'Yes',
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
onClick: () => console.log('action clicked'),
|
|
50
|
+
shortcut: 'Enter',
|
|
51
|
+
},
|
|
52
|
+
id: 'toast-3',
|
|
53
|
+
status: 'success',
|
|
54
|
+
text: 'Confirm this action?',
|
|
55
|
+
} satisfies Partial<BaseToast>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const WithActionShortcutY: StoryObj<typeof BaseToast> = {
|
|
59
|
+
args: {
|
|
60
|
+
action: {
|
|
61
|
+
label: 'Yes',
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
onClick: () => console.log('action clicked'),
|
|
64
|
+
shortcut: 'y',
|
|
65
|
+
},
|
|
66
|
+
id: 'toast-4',
|
|
67
|
+
status: 'info',
|
|
68
|
+
text: 'Confirm this action?',
|
|
69
|
+
} satisfies Partial<BaseToast>,
|
|
70
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { BaseToast } from '../../types/bases'
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<BaseToast>(), {
|
|
5
|
+
hasActiveShortcut: true,
|
|
5
6
|
hasClose: true,
|
|
6
7
|
id: '',
|
|
7
8
|
status: 'info',
|
|
@@ -9,21 +10,21 @@ const props = withDefaults(defineProps<BaseToast>(), {
|
|
|
9
10
|
})
|
|
10
11
|
|
|
11
12
|
const emit = defineEmits<{
|
|
12
|
-
action: [event: MouseEvent, toast: BaseToast]
|
|
13
|
-
close: [event: MouseEvent, toast: BaseToast]
|
|
13
|
+
action: [event: KeyboardEvent | MouseEvent, toast: BaseToast]
|
|
14
|
+
close: [event: KeyboardEvent | MouseEvent, toast: BaseToast]
|
|
14
15
|
}>()
|
|
15
16
|
|
|
16
17
|
const { getIcon } = useLayerIcons()
|
|
17
18
|
|
|
18
19
|
function onAction(event: KeyboardEvent | MouseEvent) {
|
|
19
20
|
if (props.action) {
|
|
20
|
-
emit('action', event
|
|
21
|
+
emit('action', event, props)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function onClose(event: KeyboardEvent | MouseEvent) {
|
|
25
26
|
if (props.hasClose) {
|
|
26
|
-
emit('close', event
|
|
27
|
+
emit('close', event, props)
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
</script>
|
|
@@ -46,7 +47,7 @@ function onClose(event: KeyboardEvent | MouseEvent) {
|
|
|
46
47
|
|
|
47
48
|
<button
|
|
48
49
|
v-if="action"
|
|
49
|
-
class="ml-1.5 min-h-6 inline-flex items-center justify-center rounded-md px-2 py-0.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-800"
|
|
50
|
+
class="ml-1.5 min-h-6 inline-flex items-center justify-center gap-1.25 rounded-md px-2 py-0.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-800"
|
|
50
51
|
:class="{
|
|
51
52
|
'bg-red-100 text-red-700 hover:bg-red-200 focus-visible:ring-red-500 dark:bg-red-400/15 dark:text-red-400 dark:hover:bg-red-400/25': status === 'error',
|
|
52
53
|
'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 focus-visible:ring-indigo-500 dark:bg-indigo-400/15 dark:text-indigo-400 dark:hover:bg-indigo-400/25': status === 'info',
|
|
@@ -64,6 +65,12 @@ function onClose(event: KeyboardEvent | MouseEvent) {
|
|
|
64
65
|
:text="action.label"
|
|
65
66
|
uppercase
|
|
66
67
|
/>
|
|
68
|
+
|
|
69
|
+
<BaseShortcut
|
|
70
|
+
:active="hasActiveShortcut"
|
|
71
|
+
:shortcut="action.shortcut ?? 'Enter'"
|
|
72
|
+
@trigger="onAction"
|
|
73
|
+
/>
|
|
67
74
|
</button>
|
|
68
75
|
|
|
69
76
|
<template v-if="hasClose">
|
|
@@ -267,7 +267,7 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
267
267
|
<BaseIcon
|
|
268
268
|
v-if="caret"
|
|
269
269
|
class="ml-2 flex-initial"
|
|
270
|
-
:class="{ 'rotate-180
|
|
270
|
+
:class="{ 'rotate-180': opened }"
|
|
271
271
|
color="gray"
|
|
272
272
|
:icon="getIcon('arrowDown')"
|
|
273
273
|
/>
|
|
@@ -344,7 +344,7 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
344
344
|
<div
|
|
345
345
|
v-for="option in column.options"
|
|
346
346
|
:key="option.value"
|
|
347
|
-
class="
|
|
347
|
+
class="flex cursor-pointer items-center outline-none"
|
|
348
348
|
:class="{
|
|
349
349
|
'border-b border-gray-200 dark:border-gray-800 p-3 hover:bg-gray-100 dark:hover:bg-gray-800 last:border-b-0': computedColumns.length < 2,
|
|
350
350
|
'px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 last:mb-2': computedColumns.length >= 2,
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
const { fadeInUp } = useMotion()
|
|
3
3
|
const toasts = useToasts()
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const activeShortcutToastId = computed(() => {
|
|
6
|
+
return [...toasts.toasts.value].reverse().find(item => item.action)?.id
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
async function onToastAction(_event: KeyboardEvent | MouseEvent, toast: BaseToast) {
|
|
6
10
|
toasts.closeToast(toast.id)
|
|
7
11
|
|
|
8
12
|
await toast.action?.onClick()
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
function onToastClose(_event: MouseEvent, toast: BaseToast) {
|
|
15
|
+
function onToastClose(_event: KeyboardEvent | MouseEvent, toast: BaseToast) {
|
|
12
16
|
toasts.closeToast(toast.id)
|
|
13
17
|
}
|
|
14
18
|
</script>
|
|
@@ -27,6 +31,7 @@ function onToastClose(_event: MouseEvent, toast: BaseToast) {
|
|
|
27
31
|
:id="toast.id"
|
|
28
32
|
:key="toast.id"
|
|
29
33
|
:action="toast.action"
|
|
34
|
+
:has-active-shortcut="toast.id === activeShortcutToastId"
|
|
30
35
|
:status="toast.status"
|
|
31
36
|
:text="toast.text"
|
|
32
37
|
@action="onToastAction"
|
|
@@ -21,6 +21,7 @@ const icons = {
|
|
|
21
21
|
closeCircle: 'hugeicons:cancel-circle',
|
|
22
22
|
default: 'hugeicons:help-circle',
|
|
23
23
|
drag: 'mdi:drag-horizontal-variant',
|
|
24
|
+
enter: 'mdi:keyboard-return',
|
|
24
25
|
exclamationCircle: 'hugeicons:alert-circle',
|
|
25
26
|
infoCircle: 'hugeicons:information-circle',
|
|
26
27
|
plus: 'hugeicons:add-01',
|
package/app/types/bases.d.ts
CHANGED
|
@@ -275,6 +275,11 @@ export type BaseQuoteBackground = 'gray' | 'gray-light' | 'white'
|
|
|
275
275
|
|
|
276
276
|
export type BaseQuoteSize = 'base' | 'sm' | 'xs'
|
|
277
277
|
|
|
278
|
+
export interface BaseShortcut {
|
|
279
|
+
active?: boolean
|
|
280
|
+
shortcut?: string
|
|
281
|
+
}
|
|
282
|
+
|
|
278
283
|
export type BaseSize
|
|
279
284
|
= | '2xl'
|
|
280
285
|
| '2xs'
|
|
@@ -365,6 +370,7 @@ export type BaseTextText = string | {
|
|
|
365
370
|
|
|
366
371
|
export interface BaseToast {
|
|
367
372
|
action?: BaseToastAction
|
|
373
|
+
hasActiveShortcut?: boolean
|
|
368
374
|
hasClose?: boolean
|
|
369
375
|
id: string
|
|
370
376
|
status?: BaseStatus
|
|
@@ -374,4 +380,5 @@ export interface BaseToast {
|
|
|
374
380
|
export interface BaseToastAction {
|
|
375
381
|
label: BaseTextText
|
|
376
382
|
onClick: () => Promise<void> | void
|
|
383
|
+
shortcut?: string
|
|
377
384
|
}
|
package/app/types/global.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ declare global {
|
|
|
45
45
|
type BaseQuote = import('./bases').BaseQuote
|
|
46
46
|
type BaseQuoteBackground = import('./bases').BaseQuoteBackground
|
|
47
47
|
type BaseQuoteSize = import('./bases').BaseQuoteSize
|
|
48
|
+
type BaseShortcut = import('./bases').BaseShortcut
|
|
48
49
|
type BaseSize = import('./bases').BaseSize
|
|
49
50
|
type BaseSpinner = import('./bases').BaseSpinner
|
|
50
51
|
type BaseStatus = import('./bases').BaseStatus
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasmakers/ui",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.37",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Nuxt UI components for SaaS Makers projects",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,32 +18,32 @@
|
|
|
18
18
|
"uno.config.ts"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@capacitor/preferences": "8.0.
|
|
22
|
-
"@nuxt/icon": "2.
|
|
21
|
+
"@capacitor/preferences": "8.0.1",
|
|
22
|
+
"@nuxt/icon": "2.2.3",
|
|
23
23
|
"@nuxt/scripts": "0.13.2",
|
|
24
24
|
"@nuxtjs/color-mode": "3.5.2",
|
|
25
|
-
"@nuxtjs/i18n": "10.
|
|
25
|
+
"@nuxtjs/i18n": "10.4.0",
|
|
26
26
|
"@nuxtjs/plausible": "2.0.1",
|
|
27
27
|
"@nuxtjs/robots": "5.6.7",
|
|
28
28
|
"@nuxtjs/sitemap": "7.5.0",
|
|
29
29
|
"@nuxtjs/storybook": "9.0.1",
|
|
30
30
|
"@pinia/nuxt": "0.11.3",
|
|
31
31
|
"@unhead/vue": "2.0.19",
|
|
32
|
-
"@unocss/nuxt": "66.
|
|
33
|
-
"@unocss/reset": "66.
|
|
32
|
+
"@unocss/nuxt": "66.7.0",
|
|
33
|
+
"@unocss/reset": "66.7.0",
|
|
34
34
|
"@vuelidate/core": "2.0.3",
|
|
35
35
|
"@vuelidate/validators": "2.0.4",
|
|
36
|
-
"@vueuse/components": "14.
|
|
37
|
-
"@vueuse/core": "14.
|
|
38
|
-
"@vueuse/nuxt": "14.
|
|
36
|
+
"@vueuse/components": "14.3.0",
|
|
37
|
+
"@vueuse/core": "14.3.0",
|
|
38
|
+
"@vueuse/nuxt": "14.3.0",
|
|
39
39
|
"chartist": "1.5.0",
|
|
40
40
|
"floating-vue": "5.2.2",
|
|
41
41
|
"motion-v": "1.7.4",
|
|
42
42
|
"numbro": "2.5.0",
|
|
43
43
|
"snarkdown": "2.0.0",
|
|
44
44
|
"storybook": "9.0.5",
|
|
45
|
-
"unocss": "66.
|
|
46
|
-
"vue": "3.5.
|
|
45
|
+
"unocss": "66.7.0",
|
|
46
|
+
"vue": "3.5.35",
|
|
47
47
|
"vue-router": "4.6.4"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|