@saasmakers/ui 0.1.60 → 0.1.61

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,123 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldTextarea } from '../../types/fields'
3
+
4
+ const props = withDefaults(defineProps<FieldTextarea>(), {
5
+ autofocus: false,
6
+ background: 'gray',
7
+ border: true,
8
+ description: '',
9
+ disabled: false,
10
+ fullWidth: true,
11
+ hideError: false,
12
+ label: '',
13
+ labelIcon: undefined,
14
+ loading: false,
15
+ modelValue: '',
16
+ padding: true,
17
+ placeholder: '',
18
+ required: false,
19
+ resize: false,
20
+ rows: 3,
21
+ size: 'base',
22
+ validation: undefined,
23
+ })
24
+
25
+ const emit = defineEmits<{
26
+ 'blur': [event: FocusEvent, value: string]
27
+ 'click': [event: MouseEvent, value: string]
28
+ 'update:modelValue': [value: string]
29
+ }>()
30
+
31
+ const textarea = ref<HTMLTextAreaElement>()
32
+ const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
33
+
34
+ const { isDesktopBrowser } = useDevice()
35
+
36
+ const value = computed({
37
+ get() {
38
+ return `${props.modelValue}`
39
+ },
40
+ set(value) {
41
+ emit('update:modelValue', value)
42
+ },
43
+ })
44
+
45
+ onMounted(() => {
46
+ if (props.autofocus && textarea.value && isDesktopBrowser.value) {
47
+ textarea.value.focus()
48
+ }
49
+ })
50
+
51
+ function onFieldBlur(event: FocusEvent) {
52
+ const value = textarea.value?.value || ''
53
+
54
+ emit('blur', event, value)
55
+ }
56
+
57
+ function onFieldClick(event: MouseEvent) {
58
+ const value = textarea.value?.value || ''
59
+
60
+ emit('click', event, value)
61
+ }
62
+
63
+ function onFieldInput() {
64
+ const value = textarea.value?.value || ''
65
+
66
+ emit('update:modelValue', value)
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <div
72
+ class="relative flex flex-col"
73
+ :class="{ 'w-full': fullWidth }"
74
+ >
75
+ <FieldLabel
76
+ v-if="label"
77
+ :disabled="disabled"
78
+ :for-field="uuid"
79
+ has-margin-bottom
80
+ :icon="labelIcon"
81
+ :label="label"
82
+ :required="required"
83
+ :size="size"
84
+ />
85
+
86
+ <textarea
87
+ :id="uuid"
88
+ ref="textarea"
89
+ v-model="value"
90
+ class="w-full flex-1 appearance-none rounded-lg text-gray-900 font-medium tracking-tight uppercase outline-none dark:text-gray-100 placeholder-gray-600 dark:placeholder-gray-400 focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100"
91
+ :class="{
92
+ 'p-2.5': padding,
93
+ 'p-0': !padding,
94
+ 'resize-none': !resize,
95
+
96
+ 'bg-gray-100 dark:bg-gray-900': background === 'gray',
97
+ 'bg-white dark:bg-gray-900': background === 'white',
98
+
99
+ 'border shadow-sm border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 focus:border-gray-400 dark:focus:border-gray-600': border,
100
+ 'border-0': !border,
101
+
102
+ 'text-xs': size === 'xs',
103
+ 'text-sm': size === 'sm',
104
+ 'text-base': size === 'base',
105
+ 'text-lg': size === 'lg',
106
+ }"
107
+ data-enable-grammarly="false"
108
+ :placeholder="placeholder"
109
+ :rows="rows"
110
+ spellcheck="false"
111
+ @blur="onFieldBlur"
112
+ @click="onFieldClick"
113
+ @input="onFieldInput"
114
+ />
115
+
116
+ <FieldMessage
117
+ :description="description"
118
+ :hide-error="hideError"
119
+ :size="size"
120
+ :validation="validation"
121
+ />
122
+ </div>
123
+ </template>
@@ -0,0 +1,73 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldTime } from '../../types/fields'
3
+
4
+ const props = withDefaults(defineProps<FieldTime>(), {
5
+ background: 'gray',
6
+ description: '',
7
+ disabled: false,
8
+ hideError: true,
9
+ icon: undefined,
10
+ id: undefined,
11
+ label: '',
12
+ labelIcon: undefined,
13
+ modelValue: '',
14
+ name: undefined,
15
+ required: false,
16
+ size: 'base',
17
+ validation: undefined,
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ 'blur': [event: FocusEvent, value: string, name?: string]
22
+ 'update:modelValue': [value: string, name?: string]
23
+ }>()
24
+
25
+ const root = ref<HTMLDivElement>()
26
+ const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
27
+
28
+ function onFieldBlur(event: FocusEvent) {
29
+ emit('blur', event, props.modelValue, props.name)
30
+ }
31
+
32
+ function onFieldInput() {
33
+ const value = root.value?.querySelector('input')?.value || ''
34
+
35
+ emit('update:modelValue', value, props.name)
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <div ref="root">
41
+ <FieldLabel
42
+ v-if="label"
43
+ :disabled="disabled"
44
+ :for-field="uuid"
45
+ has-margin-bottom
46
+ :icon="labelIcon"
47
+ :label="label"
48
+ :required="required"
49
+ :size="size"
50
+ />
51
+
52
+ <div class="item-center h-[42px] flex overflow-hidden border border-gray-200 rounded-lg pl-3 pr-2 dark:border-gray-800 focus-within:border-gray-400 hover:border-gray-300 dark:focus-within:border-gray-600 dark:hover:border-gray-700">
53
+ <input
54
+ :id="`${id}`"
55
+ class="text-sm outline-none"
56
+ :class="{
57
+ 'bg-gray-100 dark:bg-gray-900': background === 'gray',
58
+ 'bg-white dark:bg-gray-900': background === 'white',
59
+ }"
60
+ type="time"
61
+ @blur="onFieldBlur"
62
+ @input="onFieldInput"
63
+ >
64
+ </div>
65
+
66
+ <FieldMessage
67
+ :description="description"
68
+ :hide-error="hideError"
69
+ :size="size"
70
+ :validation="validation"
71
+ />
72
+ </div>
73
+ </template>
@@ -0,0 +1,11 @@
1
+ export default function useDevice() {
2
+ const isDesktopBrowser = computed(() => {
3
+ if (typeof navigator === 'undefined') {
4
+ return true
5
+ }
6
+
7
+ return !navigator.userAgentData?.mobile
8
+ })
9
+
10
+ return { isDesktopBrowser }
11
+ }
@@ -1,5 +1,12 @@
1
1
  import numbroLib from 'numbro'
2
2
 
3
+ export function normalizeText(text: string) {
4
+ return text
5
+ .toLowerCase()
6
+ .normalize('NFD')
7
+ .replace(/[\u0300-\u036F]/g, '')
8
+ }
9
+
3
10
  export function numbro(number: '∞' | number | undefined, format?: string) {
4
11
  if (!number && number !== 0) {
5
12
  return ''
@@ -0,0 +1,179 @@
1
+ export type FieldBackground = 'gray' | 'white'
2
+
3
+ export type FieldSize = 'base' | 'lg' | 'sm' | 'xs'
4
+
5
+ export type FieldStatus = 'default' | 'error' | 'info' | 'success' | 'warning'
6
+
7
+ export interface FieldCheckbox {
8
+ description?: BaseTextText
9
+ disabled?: boolean
10
+ fullWidth?: boolean
11
+ hideError?: boolean
12
+ label?: BaseTextText
13
+ labelIcon?: BaseIconValue
14
+ lineThrough?: boolean
15
+ loading?: boolean
16
+ modelValue?: boolean
17
+ required?: boolean
18
+ size?: FieldSize
19
+ truncate?: boolean
20
+ uppercase?: boolean
21
+ validation?: VuelidateValidation
22
+ }
23
+
24
+ export interface FieldDays {
25
+ modelValue?: number[]
26
+ }
27
+
28
+ export interface FieldEmojis {
29
+ modelValue?: string
30
+ }
31
+
32
+ export interface FieldInput {
33
+ alignment?: FieldInputAlignment
34
+ autocomplete?: boolean
35
+ autofocus?: boolean
36
+ background?: FieldBackground
37
+ border?: FieldInputBorder
38
+ description?: BaseTextText
39
+ disabled?: boolean
40
+ fullWidth?: boolean
41
+ hideError?: boolean
42
+ label?: BaseTextText
43
+ labelIcon?: BaseIconValue
44
+ lineThrough?: boolean
45
+ loading?: boolean
46
+ lowercaseOnly?: boolean
47
+ max?: number
48
+ min?: number
49
+ modelValue?: number | string
50
+ placeholder?: string
51
+ required?: boolean
52
+ size?: FieldSize
53
+ type?: FieldInputType
54
+ uppercase?: boolean
55
+ validation?: VuelidateValidation
56
+ }
57
+
58
+ export type FieldInputAlignment = 'center' | 'left' | 'right'
59
+
60
+ export type FieldInputBorder = 'bottom' | 'full' | 'none'
61
+
62
+ export type FieldInputType = 'currency' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'time' | 'url' | 'week'
63
+
64
+ export interface FieldLabel {
65
+ disabled?: boolean
66
+ forField?: string
67
+ hasMarginBottom?: boolean
68
+ hasMarginLeft?: boolean
69
+ icon?: BaseIconValue
70
+ label: BaseTextText
71
+ lineThrough?: boolean
72
+ loading?: boolean
73
+ required?: boolean
74
+ size?: FieldSize
75
+ truncate?: boolean
76
+ }
77
+
78
+ export interface FieldMessage {
79
+ description?: BaseTextText
80
+ hideError?: boolean
81
+ size?: FieldSize
82
+ validation?: VuelidateValidation
83
+ }
84
+
85
+ export interface FieldSelect {
86
+ border?: FieldSelectBorder
87
+ caret?: boolean
88
+ columns?: FieldSelectColumn[]
89
+ description?: BaseTextText
90
+ direction?: FieldSelectDirection
91
+ disabled?: boolean
92
+ hideError?: boolean
93
+ label?: BaseTextText
94
+ labelIcon?: BaseIconValue
95
+ maxHeight?: FieldSelectMaxHeight
96
+ modelValue?: number | string
97
+ openOnHover?: boolean
98
+ options?: FieldSelectOption[]
99
+ padding?: boolean
100
+ placeholder?: string
101
+ required?: boolean
102
+ size?: FieldSize
103
+ validation?: VuelidateValidation
104
+ }
105
+
106
+ export interface FieldSelectColumn {
107
+ options: FieldSelectOption[]
108
+ title?: string
109
+ }
110
+
111
+ export interface FieldSelectOption {
112
+ icon?: BaseIconValue
113
+ text: string
114
+ value: number | string
115
+ }
116
+
117
+ export type FieldSelectBorder = 'bottom' | 'full' | 'none'
118
+
119
+ export type FieldSelectDirection = 'bottom' | 'top'
120
+
121
+ export type FieldSelectMaxHeight = 'lg' | 'md' | 'sm' | 'xs'
122
+
123
+ export interface FieldTabs {
124
+ minimizeOnMobile?: boolean
125
+ modelValue?: Array<number | string> | number | string
126
+ multiple?: boolean
127
+ size?: FieldSize
128
+ tabs: FieldTabsTab[]
129
+ theme?: FieldTabsTheme
130
+ }
131
+
132
+ export interface FieldTabsTab {
133
+ activeColor?: BaseColor
134
+ icon?: BaseIconValue
135
+ label: string
136
+ to?: RouteLocationNamedI18n
137
+ value: number | string
138
+ }
139
+
140
+ export type FieldTabsAction = 'added' | 'removed'
141
+
142
+ export type FieldTabsTheme = 'border' | 'rounded'
143
+
144
+ export interface FieldTextarea {
145
+ autofocus?: boolean
146
+ background?: FieldBackground
147
+ border?: boolean
148
+ description?: BaseTextText
149
+ disabled?: boolean
150
+ fullWidth?: boolean
151
+ hideError?: boolean
152
+ label?: BaseTextText
153
+ labelIcon?: BaseIconValue
154
+ loading?: boolean
155
+ modelValue?: string
156
+ padding?: boolean
157
+ placeholder?: string
158
+ required?: boolean
159
+ resize?: boolean
160
+ rows?: number
161
+ size?: FieldSize
162
+ validation?: VuelidateValidation
163
+ }
164
+
165
+ export interface FieldTime {
166
+ background?: FieldBackground
167
+ description?: BaseTextText
168
+ disabled?: boolean
169
+ hideError?: boolean
170
+ icon?: BaseIconValue
171
+ id: number | string
172
+ label?: BaseTextText
173
+ labelIcon?: BaseIconValue
174
+ modelValue?: string
175
+ name?: string
176
+ required?: boolean
177
+ size?: FieldSize
178
+ validation?: VuelidateValidation
179
+ }
@@ -7,6 +7,7 @@ declare global {
7
7
  type ChartistOptions = import('chartist').Options
8
8
  type ChartistPieChartData = import('chartist').PieChartData
9
9
  type RouteLocationNamedI18n = import('vue-router').RouteLocationNamedI18n
10
+ type VuelidateValidation = import('@vuelidate/core').BaseValidation
10
11
 
11
12
  // Bases
12
13
  type BaseAlert = Bases.BaseAlert
@@ -55,6 +56,15 @@ declare global {
55
56
  type BaseToast = Bases.BaseToast
56
57
  type BaseToasts = Bases.BaseToasts
57
58
  type BaseToastsAlignment = Bases.BaseToastsAlignment
59
+
60
+ // Navigator
61
+ interface Navigator {
62
+ userAgentData?: {
63
+ brands?: Array<{ brand: string, version: string }>
64
+ mobile?: boolean
65
+ platform?: string
66
+ }
67
+ }
58
68
  }
59
69
 
60
70
  export * from './bases'
package/nuxt.config.ts CHANGED
@@ -21,6 +21,10 @@ export default defineNuxtConfig({
21
21
  path: 'components/bases',
22
22
  pathPrefix: false,
23
23
  },
24
+ {
25
+ path: 'components/fields',
26
+ pathPrefix: false,
27
+ },
24
28
  {
25
29
  path: 'components/layout',
26
30
  pathPrefix: false,
@@ -35,14 +39,15 @@ export default defineNuxtConfig({
35
39
  ],
36
40
 
37
41
  modules: [
42
+ '@nuxt/icon',
38
43
  '@nuxtjs/color-mode',
39
44
  '@nuxtjs/i18n',
40
- '@nuxt/icon',
41
- '@pinia/nuxt',
42
- '@unocss/nuxt',
43
45
  '@nuxtjs/plausible',
44
46
  '@nuxtjs/robots',
45
47
  '@nuxtjs/sitemap',
48
+ '@pinia/nuxt',
49
+ '@unocss/nuxt',
50
+ '@vueuse/nuxt',
46
51
  'floating-vue/nuxt',
47
52
  'motion-v/nuxt',
48
53
  ],
@@ -75,7 +80,6 @@ export default defineNuxtConfig({
75
80
  plausible: {
76
81
  apiHost: 'https://plausible.saasmakers.dev',
77
82
  autoOutboundTracking: true,
78
- domain: 'resilience.club',
79
83
  ignoreSubDomains: true,
80
84
  proxy: true,
81
85
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
3
  "type": "module",
4
- "version": "0.1.60",
4
+ "version": "0.1.61",
5
5
  "private": false,
6
6
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
7
7
  "license": "MIT",
@@ -39,8 +39,14 @@
39
39
  "@nuxtjs/robots": "5.5.6",
40
40
  "@nuxtjs/sitemap": "7.4.7",
41
41
  "@pinia/nuxt": "0.11.2",
42
+ "@saasmakers/shared": "workspace:*",
42
43
  "@unocss/nuxt": "66.5.4",
43
44
  "@unocss/reset": "66.5.10",
45
+ "@vuelidate/core": "2.0.3",
46
+ "@vuelidate/validators": "2.0.4",
47
+ "@vueuse/components": "14.0.0",
48
+ "@vueuse/core": "14.1.0",
49
+ "@vueuse/nuxt": "14.0.0",
44
50
  "chartist": "1.5.0",
45
51
  "floating-vue": "5.2.2",
46
52
  "lottie-web": "5.13.0",