@saasmakers/ui 1.4.42 → 1.4.44

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.
@@ -21,7 +21,6 @@ defineSlots<{
21
21
 
22
22
  const { t } = useI18n()
23
23
  const { getIcon } = useLayerIcons()
24
-
25
24
  const isClosed = ref(false)
26
25
 
27
26
  const buttonColor = computed<BaseColor>(() => {
@@ -23,12 +23,10 @@ defineSlots<{
23
23
  }>()
24
24
 
25
25
  const { createToast } = useToasts()
26
-
27
26
  const error = ref(false)
28
27
  const fileInput = ref<HTMLInputElement>()
29
28
  const hovered = ref(false)
30
29
  const loaded = ref(false)
31
-
32
30
  const { t } = useI18n()
33
31
 
34
32
  const isClickable = computed(() => {
@@ -40,7 +38,10 @@ function onAvatarSelected(event: Event) {
40
38
 
41
39
  if (file) {
42
40
  if (props.maxSizeMb && file.size > props.maxSizeMb * 1024 * 1024) {
43
- return createToast(t('fileSizeTooLarge', { maxSizeMb: props.maxSizeMb }), 'error')
41
+ return createToast({
42
+ message: t('fileSizeTooLarge', { maxSizeMb: props.maxSizeMb }),
43
+ status: 'error',
44
+ })
44
45
  }
45
46
 
46
47
  emit('avatarSelected', event, file)
@@ -32,7 +32,6 @@ defineSlots<{
32
32
  }>()
33
33
 
34
34
  const confirming = ref(false)
35
-
36
35
  const { t } = useI18n()
37
36
 
38
37
  function onClick(event: MouseEvent) {
@@ -25,7 +25,6 @@ const emit = defineEmits<{
25
25
  }>()
26
26
 
27
27
  const confirming = ref(false)
28
-
29
28
  const { t } = useI18n()
30
29
  const { getIcon } = useLayerIcons()
31
30
 
@@ -45,7 +45,6 @@ function onKeyDown(event: KeyboardEvent) {
45
45
 
46
46
  onKeyStroke('Escape', (event) => {
47
47
  event.preventDefault()
48
-
49
48
  onClose(event)
50
49
  })
51
50
  </script>
@@ -30,7 +30,6 @@ defineSlots<{
30
30
  const { locale, t } = useI18n()
31
31
  const { translatedContent } = useTranslation(useSlots(), locale)
32
32
  const { getIcon } = useLayerIcons()
33
-
34
33
  const closed = ref(false)
35
34
 
36
35
  const finalBackground = computed(() => {
@@ -37,9 +37,7 @@ defineSlots<{
37
37
  }>()
38
38
 
39
39
  const { getIcon } = useLayerIcons()
40
-
41
40
  const hovered = ref(false)
42
-
43
41
  const form = reactive({ name: '' })
44
42
 
45
43
  onBeforeMount(() => {
@@ -28,13 +28,11 @@ const emit = defineEmits<{
28
28
  }>()
29
29
 
30
30
  const modelValue = defineModel<BaseTags['modelValue']>({ default: () => [] })
31
-
32
31
  const keyForTagCreation = ref(Date.now())
33
32
  const keyForTagUpdate = ref(Date.now())
34
33
  const root = ref<HTMLDivElement>()
35
34
  const showingAllTags = ref(false)
36
35
  const showingTagCreationField = ref(false)
37
-
38
36
  const { t } = useI18n()
39
37
  const { getIcon } = useLayerIcons()
40
38
 
@@ -85,7 +83,6 @@ function onShowTagField() {
85
83
  if (!showingTagCreationField.value) {
86
84
  showingTagCreationField.value = true
87
85
 
88
- // Focus contenteditable
89
86
  nextTick(() => {
90
87
  const element = root.value?.querySelector<HTMLInputElement>('input[type="text"]')
91
88
 
@@ -104,6 +101,7 @@ function onTagClick(event: MouseEvent, tagId?: number | string) {
104
101
  }
105
102
  else if (props.selectableUnique) {
106
103
  value = [tagId]
104
+
107
105
  emit('attach', event, tagId)
108
106
  }
109
107
  else {
@@ -28,7 +28,6 @@ defineSlots<{
28
28
  }>()
29
29
 
30
30
  const showAll = ref(false)
31
-
32
31
  const { t } = useI18n()
33
32
 
34
33
  const finalText = computed(() => {
@@ -26,7 +26,6 @@ const emit = defineEmits<{
26
26
 
27
27
  const { createToast } = useToasts()
28
28
  const { t } = useI18n()
29
-
30
29
  const fileInput = ref<HTMLInputElement>()
31
30
  const id = useId()
32
31
 
@@ -41,7 +40,11 @@ function onAvatarChange(event: Event) {
41
40
  }
42
41
 
43
42
  if (props.maxSizeMb && file.size > props.maxSizeMb * 1024 * 1024) {
44
- createToast(t('fileSizeTooLarge', { maxSizeMb: props.maxSizeMb }), 'error')
43
+ createToast({
44
+ message: t('fileSizeTooLarge', { maxSizeMb: props.maxSizeMb }),
45
+ status: 'error',
46
+ })
47
+
45
48
  return
46
49
  }
47
50
 
@@ -18,7 +18,6 @@ withDefaults(defineProps<Omit<FieldCheckbox, 'modelValue'>>(), {
18
18
  })
19
19
 
20
20
  const id = useId()
21
-
22
21
  const modelValue = defineModel<FieldCheckbox['modelValue']>({ default: false })
23
22
  </script>
24
23
 
@@ -8,7 +8,6 @@ const emit = defineEmits<{
8
8
  }>()
9
9
 
10
10
  const { t } = useI18n()
11
-
12
11
  const modelValue = defineModel<FieldDays['modelValue']>({ default: () => [] })
13
12
 
14
13
  const daysToDisplay = computed(() => {
@@ -11,9 +11,7 @@ const emit = defineEmits<{
11
11
 
12
12
  const { locale, t } = useI18n()
13
13
  const { normalizeText } = useLayerUtils()
14
-
15
14
  const modelValue = defineModel<FieldEmojis['modelValue']>({ default: '' })
16
-
17
15
  const searchRaw = ref('')
18
16
  const searchQuery = refDebounced(searchRaw, 250)
19
17
 
@@ -43,11 +43,8 @@ defineSlots<{
43
43
  }>()
44
44
 
45
45
  const id = useId()
46
-
47
46
  const modelValue = defineModel<FieldInput['modelValue']>({ default: '' })
48
-
49
47
  const input = ref<HTMLInputElement>()
50
-
51
48
  const { isDesktopBrowser } = useDevice()
52
49
 
53
50
  onMounted(() => {
@@ -18,6 +18,7 @@ const validationMessage = computed(() => {
18
18
  }
19
19
 
20
20
  const firstError = props.validation.$errors[0]
21
+
21
22
  if (firstError) {
22
23
  return unref(firstError.$message)
23
24
  }
@@ -36,12 +36,9 @@ const { fadeIn } = useMotion()
36
36
  const { normalizeText } = useLayerUtils()
37
37
  const { t } = useI18n()
38
38
  const id = useId()
39
-
40
39
  const modelValue = defineModel<FieldSelect['modelValue']>({ default: '' })
41
-
42
40
  const opened = ref(false)
43
41
  const searchRaw = ref('')
44
-
45
42
  const searchInput = ref<InstanceType<typeof FieldInput>>()
46
43
 
47
44
  const computedColumns = computed(() => {
@@ -55,7 +52,6 @@ const computedColumns = computed(() => {
55
52
  const computedOptions = computed(() => {
56
53
  const options: FieldSelectOption[] = []
57
54
 
58
- // Add index to each option
59
55
  if (props.columns.length > 0) {
60
56
  for (const column of props.columns) {
61
57
  for (const columnOption of column.options) {
@@ -152,8 +148,7 @@ function onMouseLeaveOrBlur() {
152
148
 
153
149
  function onOptionClick(event: MouseEvent, option: FieldSelectOption) {
154
150
  if (!props.disabled) {
155
- // Check that the option is not currently selected
156
- if ((selectedOption.value || {}).value === option.value) {
151
+ if (selectedOption.value?.value === option.value) {
157
152
  reset()
158
153
  }
159
154
  else {
@@ -348,8 +343,8 @@ function selectOption(event: MouseEvent, value: string) {
348
343
  :class="{
349
344
  '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
345
  'px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 last:mb-2': computedColumns.length >= 2,
351
- 'font-medium underline': selectedOption && option.value === selectedOption.value,
352
- 'text-gray-900 dark:text-gray-100': selectedOption && option.value === selectedOption.value,
346
+ 'font-medium underline': option.value === selectedOption?.value,
347
+ 'text-gray-900 dark:text-gray-100': option.value === selectedOption?.value,
353
348
  'bg-white dark:bg-gray-900': selectedOption && option.value !== selectedOption.value,
354
349
  }"
355
350
  role="button"
@@ -27,11 +27,8 @@ const emit = defineEmits<{
27
27
  }>()
28
28
 
29
29
  const id = useId()
30
-
31
30
  const modelValue = defineModel<FieldTextarea['modelValue']>({ default: '' })
32
-
33
31
  const textarea = ref<HTMLTextAreaElement>()
34
-
35
32
  const { isDesktopBrowser } = useDevice()
36
33
 
37
34
  onMounted(() => {
@@ -21,9 +21,7 @@ const emit = defineEmits<{
21
21
 
22
22
  const { getIcon } = useLayerIcons()
23
23
  const id = useId()
24
-
25
24
  const modelValue = defineModel<FieldTime['modelValue']>({ default: '' })
26
-
27
25
  const inputRef = ref<HTMLInputElement>()
28
26
 
29
27
  function onContainerClick() {
@@ -80,10 +80,12 @@ export default function useChartist() {
80
80
  // Animation for bar charts
81
81
  if (ctx.type === 'bar') {
82
82
  const barCtx = ctx as BarDrawEvent
83
+
83
84
  // Access private options property via type assertion
84
85
  const chartOptions = (chart as unknown as {
85
86
  options?: Options & Partial<BarChartOptions>
86
87
  }).options
88
+
87
89
  const horizontal = !!(chartOptions as BarChartOptions | undefined)?.horizontalBars
88
90
 
89
91
  // For vertical bars, y1 is baseline, y2 is top. For horizontal, x1 is baseline, x2 is end.
@@ -93,6 +95,7 @@ export default function useChartist() {
93
95
  // Start collapsed at baseline
94
96
  if (horizontal) {
95
97
  barCtx.element.attr({ x2: from })
98
+
96
99
  barCtx.element.animate({
97
100
  x2: {
98
101
  begin,
@@ -106,6 +109,7 @@ export default function useChartist() {
106
109
  }
107
110
  else {
108
111
  barCtx.element.attr({ y2: from })
112
+
109
113
  barCtx.element.animate({
110
114
  y2: {
111
115
  begin,
@@ -13,43 +13,46 @@ export default function useToast() {
13
13
  }
14
14
 
15
15
  dismissTimers.clear()
16
+
16
17
  toasts.value = []
17
18
  }
18
19
 
19
- const createToast = (
20
- message: string,
21
- status: BaseStatus = 'success',
22
- i18nParams?: Record<string, number | string>,
23
- action?: BaseToastAction,
24
- ) => {
20
+ const createToast = (params: {
21
+ action?: BaseToastAction
22
+ i18nParams?: Record<string, number | string>
23
+ message: string
24
+ status?: BaseStatus
25
+ }) => {
25
26
  if (!import.meta.client) {
26
27
  return
27
28
  }
28
29
 
29
- const text = message.includes(' ')
30
- ? message
31
- : nuxtApp.$i18n.t(`toasts.${message}`, i18nParams ?? {})
30
+ const text = params.message.includes(' ')
31
+ ? params.message
32
+ : nuxtApp.$i18n.t(`toasts.${params.message}`, params.i18nParams ?? {})
32
33
 
33
- const fingerprint = getFingerprint(status, text, action?.label)
34
+ const fingerprint = getFingerprint(params.status, text, params.action?.label)
34
35
  const existingToast = toasts.value.find(item => getFingerprint(item.status, item.text, item.action?.label) === fingerprint)
35
36
 
36
37
  if (existingToast) {
37
- if (action) {
38
- existingToast.action = action
38
+ if (params.action) {
39
+ existingToast.action = params.action
39
40
  }
41
+
40
42
  scheduleDismiss(existingToast.id, !!existingToast.action)
43
+
41
44
  return
42
45
  }
43
46
 
44
47
  const toast: BaseToast = {
45
- action,
48
+ action: params.action,
46
49
  id: crypto.randomUUID(),
47
- status,
50
+ status: params.status,
48
51
  text,
49
52
  }
50
53
 
51
54
  toasts.value.push(toast)
52
- scheduleDismiss(toast.id, !!action)
55
+ scheduleDismiss(toast.id, !!params.action)
53
56
  }
54
57
 
55
58
  return {
@@ -62,6 +65,7 @@ export default function useToast() {
62
65
 
63
66
  function clearDismissTimer(toastId: string) {
64
67
  const timer = dismissTimers.get(toastId)
68
+
65
69
  if (timer !== undefined) {
66
70
  clearTimeout(timer)
67
71
  dismissTimers.delete(toastId)
@@ -73,6 +77,7 @@ function closeToast(toastId: string) {
73
77
  return
74
78
 
75
79
  clearDismissTimer(toastId)
80
+
76
81
  toasts.value = toasts.value.filter(item => item.id !== toastId)
77
82
  }
78
83
 
@@ -106,7 +106,10 @@ declare global {
106
106
  }
107
107
  }
108
108
 
109
- type RouteLocationNamedI18n = import('vue-router').RouteLocationNamedI18n
109
+ type RouteLocationNamedI18n = string | {
110
+ name: string
111
+ params?: Record<string, number | string>
112
+ }
110
113
  type VuelidateValidation = import('@vuelidate/core').BaseValidation
111
114
  }
112
115
 
package/nuxt.config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { localeCodes } from '@saasmakers/shared'
1
2
  import path from 'node:path'
2
3
  import { fileURLToPath } from 'node:url'
3
4
  import { defineNuxtConfig } from 'nuxt/config'
@@ -71,7 +72,7 @@ export default defineNuxtConfig({
71
72
 
72
73
  i18n: {
73
74
  defaultLocale: 'en',
74
- locales: ['de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'pt-BR', 'id', 'vi'],
75
+ locales: localeCodes,
75
76
  strategy: 'prefix_except_default',
76
77
 
77
78
  detectBrowserLanguage: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "1.4.42",
3
+ "version": "1.4.44",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",
@@ -45,16 +45,16 @@
45
45
  "pinia": "3.0.4",
46
46
  "snarkdown": "2.0.0",
47
47
  "unocss": "66.7.0",
48
- "vue": "3.5.35",
48
+ "vue": "3.5.38",
49
49
  "vue-router": "4.6.4"
50
50
  },
51
51
  "devDependencies": {
52
52
  "nuxt": "4.3.1",
53
53
  "typescript": "5.9.3",
54
- "@saasmakers/shared": "0.2.5"
54
+ "@saasmakers/shared": "0.2.8"
55
55
  },
56
56
  "peerDependencies": {
57
- "@saasmakers/shared": "^0.2.0",
57
+ "@saasmakers/shared": "^0.2.8",
58
58
  "nuxt": "^4.3.1"
59
59
  },
60
60
  "publishConfig": {