@saasmakers/ui 1.4.34 → 1.4.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,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">
@@ -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
  }
@@ -1,102 +1,100 @@
1
1
  import type { Directive } from 'vue'
2
- import type { LayerIconIcon as LayerIconIconType, LayerIconValue as LayerIconValueType } from '../composables/useLayerIcons'
3
- import type * as Bases from './bases'
4
- import type * as Fields from './fields'
5
2
 
6
3
  declare global {
7
4
  // Bases
8
- type BaseAlert = Bases.BaseAlert
9
- type BaseAlignment = Bases.BaseAlignment
10
-
11
- type BaseAvatar = Bases.BaseAvatar
12
- type BaseBackground = Bases.BaseBackground
13
- type BaseBordered = Bases.BaseBordered
14
- type BaseBorderedColor = Bases.BaseBorderedColor
15
- type BaseButton = Bases.BaseButton
16
- type BaseButtonRounded = Bases.BaseButtonRounded
17
- type BaseButtonSize = Bases.BaseButtonSize
18
- type BaseButtonType = Bases.BaseButtonType
19
- type BaseCharacter = Bases.BaseCharacter
20
- type BaseCharacterCharacter = Bases.BaseCharacterCharacter
21
- type BaseCharacterSize = Bases.BaseCharacterSize
22
- type BaseChart = Bases.BaseChart
23
- type BaseChartType = Bases.BaseChartType
24
- type BaseColor = Bases.BaseColor
25
- type BaseDivider = Bases.BaseDivider
26
- type BaseDividerBorderStyle = Bases.BaseDividerBorderStyle
27
- type BaseDividerNavigateDirection = Bases.BaseDividerNavigateDirection
28
- type BaseDividerSize = Bases.BaseDividerSize
29
- type BaseEmoji = Bases.BaseEmoji
30
- type BaseHeading = Bases.BaseHeading
31
- type BaseHeadingAlignment = Bases.BaseHeadingAlignment
32
- type BaseHeadingSize = Bases.BaseHeadingSize
33
- type BaseHeadingTag = Bases.BaseHeadingTag
34
- type BaseIcon = Bases.BaseIcon
35
- type BaseMessage = Bases.BaseMessage
36
- type BaseMetric = Bases.BaseMetric
37
- type BaseMetricAlignment = Bases.BaseMetricAlignment
38
- type BaseMetricPerformance = Bases.BaseMetricPerformance
39
- type BaseMetricSize = Bases.BaseMetricSize
40
- type BaseOverlay = Bases.BaseOverlay
41
- type BaseOverlayOpacity = Bases.BaseOverlayOpacity
42
- type BaseOverlayPosition = Bases.BaseOverlayPosition
43
- type BaseParagraph = Bases.BaseParagraph
44
- type BaseParagraphAlignment = Bases.BaseParagraphAlignment
45
- type BaseParagraphSize = Bases.BaseParagraphSize
46
- type BasePower = Bases.BasePower
47
- type BasePowerPower = Bases.BasePowerPower
48
- type BasePowerSize = Bases.BasePowerSize
49
- type BaseQuote = Bases.BaseQuote
50
- type BaseQuoteBackground = Bases.BaseQuoteBackground
51
- type BaseQuoteSize = Bases.BaseQuoteSize
52
- type BaseSize = Bases.BaseSize
53
- type BaseSpinner = Bases.BaseSpinner
54
- type BaseStatus = Bases.BaseStatus
55
- type BaseTag = Bases.BaseTag
56
- type BaseTags = Bases.BaseTags
57
- type BaseTagSize = Bases.BaseTagSize
58
- type BaseText = Bases.BaseText
59
- type BaseTextText = Bases.BaseTextText
60
- type BaseToast = Bases.BaseToast
61
- type BaseToastAction = Bases.BaseToastAction
5
+ type BaseAlert = import('./bases').BaseAlert
6
+ type BaseAlignment = import('./bases').BaseAlignment
7
+ type BaseAvatar = import('./bases').BaseAvatar
8
+ type BaseBackground = import('./bases').BaseBackground
9
+ type BaseBordered = import('./bases').BaseBordered
10
+ type BaseBorderedColor = import('./bases').BaseBorderedColor
11
+ type BaseButton = import('./bases').BaseButton
12
+ type BaseButtonRounded = import('./bases').BaseButtonRounded
13
+ type BaseButtonSize = import('./bases').BaseButtonSize
14
+ type BaseButtonType = import('./bases').BaseButtonType
15
+ type BaseCharacter = import('./bases').BaseCharacter
16
+ type BaseCharacterCharacter = import('./bases').BaseCharacterCharacter
17
+ type BaseCharacterSize = import('./bases').BaseCharacterSize
18
+ type BaseChart = import('./bases').BaseChart
19
+ type BaseChartType = import('./bases').BaseChartType
20
+ type BaseColor = import('./bases').BaseColor
21
+ type BaseDivider = import('./bases').BaseDivider
22
+ type BaseDividerBorderStyle = import('./bases').BaseDividerBorderStyle
23
+ type BaseDividerNavigateDirection = import('./bases').BaseDividerNavigateDirection
24
+ type BaseDividerSize = import('./bases').BaseDividerSize
25
+ type BaseEmoji = import('./bases').BaseEmoji
26
+ type BaseHeading = import('./bases').BaseHeading
27
+ type BaseHeadingAlignment = import('./bases').BaseHeadingAlignment
28
+ type BaseHeadingSize = import('./bases').BaseHeadingSize
29
+ type BaseHeadingTag = import('./bases').BaseHeadingTag
30
+ type BaseIcon = import('./bases').BaseIcon
31
+ type BaseMessage = import('./bases').BaseMessage
32
+ type BaseMetric = import('./bases').BaseMetric
33
+ type BaseMetricAlignment = import('./bases').BaseMetricAlignment
34
+ type BaseMetricPerformance = import('./bases').BaseMetricPerformance
35
+ type BaseMetricSize = import('./bases').BaseMetricSize
36
+ type BaseOverlay = import('./bases').BaseOverlay
37
+ type BaseOverlayOpacity = import('./bases').BaseOverlayOpacity
38
+ type BaseOverlayPosition = import('./bases').BaseOverlayPosition
39
+ type BaseParagraph = import('./bases').BaseParagraph
40
+ type BaseParagraphAlignment = import('./bases').BaseParagraphAlignment
41
+ type BaseParagraphSize = import('./bases').BaseParagraphSize
42
+ type BasePower = import('./bases').BasePower
43
+ type BasePowerPower = import('./bases').BasePowerPower
44
+ type BasePowerSize = import('./bases').BasePowerSize
45
+ type BaseQuote = import('./bases').BaseQuote
46
+ type BaseQuoteBackground = import('./bases').BaseQuoteBackground
47
+ type BaseQuoteSize = import('./bases').BaseQuoteSize
48
+ type BaseShortcut = import('./bases').BaseShortcut
49
+ type BaseSize = import('./bases').BaseSize
50
+ type BaseSpinner = import('./bases').BaseSpinner
51
+ type BaseStatus = import('./bases').BaseStatus
52
+ type BaseTag = import('./bases').BaseTag
53
+ type BaseTags = import('./bases').BaseTags
54
+ type BaseTagSize = import('./bases').BaseTagSize
55
+ type BaseText = import('./bases').BaseText
56
+ type BaseTextText = import('./bases').BaseTextText
57
+ type BaseToast = import('./bases').BaseToast
58
+ type BaseToastAction = import('./bases').BaseToastAction
62
59
 
63
60
  // Libraries
64
61
  type ChartistBarChartData = import('chartist').BarChartData
65
62
  type ChartistLineChartData = import('chartist').LineChartData
66
-
67
63
  type ChartistOptions = import('chartist').Options
68
64
  type ChartistPieChartData = import('chartist').PieChartData
65
+
69
66
  // Fields
70
- type FieldAvatar = Fields.FieldAvatar
71
- type FieldBackground = Fields.FieldBackground
72
- type FieldCheckbox = Fields.FieldCheckbox
73
- type FieldDays = Fields.FieldDays
74
- type FieldEmojis = Fields.FieldEmojis
75
- type FieldInput = Fields.FieldInput
76
- type FieldInputAlignment = Fields.FieldInputAlignment
77
- type FieldInputBorder = Fields.FieldInputBorder
78
- type FieldInputType = Fields.FieldInputType
79
- type FieldLabel = Fields.FieldLabel
80
- type FieldMessage = Fields.FieldMessage
81
- type FieldSelect = Fields.FieldSelect
82
- type FieldSelectBorder = Fields.FieldSelectBorder
83
- type FieldSelectColumn = Fields.FieldSelectColumn
84
- type FieldSelectDirection = Fields.FieldSelectDirection
85
- type FieldSelectMaxHeight = Fields.FieldSelectMaxHeight
86
- type FieldSelectOption = Fields.FieldSelectOption
87
- type FieldSize = Fields.FieldSize
88
- type FieldStatus = Fields.FieldStatus
89
- type FieldTabs = Fields.FieldTabs
90
- type FieldTabsAction = Fields.FieldTabsAction
91
- type FieldTabsTab = Fields.FieldTabsTab
92
- type FieldTabsTheme = Fields.FieldTabsTheme
67
+ type FieldAvatar = import('./fields').FieldAvatar
68
+ type FieldBackground = import('./fields').FieldBackground
69
+ type FieldCheckbox = import('./fields').FieldCheckbox
70
+ type FieldDays = import('./fields').FieldDays
71
+ type FieldEmojis = import('./fields').FieldEmojis
72
+ type FieldInput = import('./fields').FieldInput
73
+ type FieldInputAlignment = import('./fields').FieldInputAlignment
74
+ type FieldInputBorder = import('./fields').FieldInputBorder
75
+ type FieldInputType = import('./fields').FieldInputType
76
+ type FieldLabel = import('./fields').FieldLabel
77
+ type FieldMessage = import('./fields').FieldMessage
78
+ type FieldSelect = import('./fields').FieldSelect
79
+ type FieldSelectBorder = import('./fields').FieldSelectBorder
80
+ type FieldSelectColumn = import('./fields').FieldSelectColumn
81
+ type FieldSelectDirection = import('./fields').FieldSelectDirection
82
+ type FieldSelectMaxHeight = import('./fields').FieldSelectMaxHeight
83
+ type FieldSelectOption = import('./fields').FieldSelectOption
84
+ type FieldSize = import('./fields').FieldSize
85
+ type FieldStatus = import('./fields').FieldStatus
86
+ type FieldTabs = import('./fields').FieldTabs
87
+ type FieldTabsAction = import('./fields').FieldTabsAction
88
+ type FieldTabsTab = import('./fields').FieldTabsTab
89
+ type FieldTabsTheme = import('./fields').FieldTabsTheme
90
+ type FieldTextarea = import('./fields').FieldTextarea
91
+ type FieldTime = import('./fields').FieldTime
93
92
 
94
- type FieldTextarea = Fields.FieldTextarea
95
- type FieldTime = Fields.FieldTime
96
93
  // Project
97
- type LayerIconIcon = LayerIconIconType
98
- type LayerIconValue = LayerIconValueType
94
+ type LayerIconIcon = import('../composables/useLayerIcons').LayerIconIcon
95
+ type LayerIconValue = import('../composables/useLayerIcons').LayerIconValue
99
96
  type Meta<T> = import('@nuxtjs/storybook').Meta<T>
97
+
100
98
  // Navigator
101
99
  interface Navigator {
102
100
  userAgentData?: {
@@ -108,9 +106,9 @@ declare global {
108
106
  platform?: string
109
107
  }
110
108
  }
109
+
111
110
  type RouteLocationNamedI18n = import('vue-router').RouteLocationNamedI18n
112
111
  type StoryObj<T> = import('@nuxtjs/storybook').StoryObj<T>
113
-
114
112
  type VuelidateValidation = import('@vuelidate/core').BaseValidation
115
113
  }
116
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "1.4.34",
3
+ "version": "1.4.36",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",