@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.
@@ -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 transform -translate-x-1/2 -translate-y-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
  />
@@ -45,7 +45,7 @@ defineSlots<{
45
45
 
46
46
  <div
47
47
  v-if="$slots.default"
48
- class="mt-6 flex flex-col items-center justify-center sm:flex-row sm:items-center"
48
+ class="mt-6 flex flex-col items-center justify-center sm:flex-row"
49
49
  >
50
50
  <slot />
51
51
  </div>
@@ -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-right': alignment === 'right',
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 transform"
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 as MouseEvent, props)
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 as MouseEvent, props)
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 transform': opened }"
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="group flex cursor-pointer items-center outline-none"
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
- async function onToastAction(_event: MouseEvent, toast: BaseToast) {
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',
@@ -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
  }
@@ -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.35",
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.0",
22
- "@nuxt/icon": "2.1.1",
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.2.1",
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.5.10",
33
- "@unocss/reset": "66.5.10",
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.1.0",
37
- "@vueuse/core": "14.1.0",
38
- "@vueuse/nuxt": "14.1.0",
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.5.10",
46
- "vue": "3.5.26",
45
+ "unocss": "66.7.0",
46
+ "vue": "3.5.35",
47
47
  "vue-router": "4.6.4"
48
48
  },
49
49
  "devDependencies": {