@saasmakers/ui 0.1.59 → 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.
- package/app/components/bases/BaseAvatar.vue +5 -5
- package/app/components/bases/BaseButton.vue +15 -1
- package/app/components/bases/BaseIcon.vue +15 -1
- package/app/components/bases/BaseQuote.vue +15 -1
- package/app/components/bases/BaseTag.vue +179 -0
- package/app/components/bases/BaseTags.vue +242 -0
- package/app/components/fields/FieldCheckbox.vue +98 -0
- package/app/components/fields/FieldDays.vue +109 -0
- package/app/components/fields/FieldEmojis.vue +104 -0
- package/app/components/fields/FieldInput.vue +209 -0
- package/app/components/fields/FieldLabel.vue +64 -0
- package/app/components/fields/FieldMessage.vue +209 -0
- package/app/components/fields/FieldSelect.vue +291 -0
- package/app/components/fields/FieldTabs.vue +100 -0
- package/app/components/fields/FieldTextarea.vue +123 -0
- package/app/components/fields/FieldTime.vue +73 -0
- package/app/composables/useDevice.ts +11 -0
- package/app/composables/useIcons.ts +4 -0
- package/app/composables/useUtils.ts +7 -0
- package/app/types/bases.d.ts +42 -0
- package/app/types/fields.d.ts +179 -0
- package/app/types/global.d.ts +10 -0
- package/nuxt.config.ts +8 -4
- package/package.json +7 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldSelect, FieldSelectOption } from '../../types/fields'
|
|
3
|
+
import { vOnClickOutside } from '@vueuse/components'
|
|
4
|
+
import { getIcon } from '../../composables/useIcons'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<FieldSelect>(), {
|
|
7
|
+
border: 'full',
|
|
8
|
+
caret: true,
|
|
9
|
+
columns: () => [],
|
|
10
|
+
description: '',
|
|
11
|
+
direction: 'bottom',
|
|
12
|
+
disabled: false,
|
|
13
|
+
hideError: false,
|
|
14
|
+
label: '',
|
|
15
|
+
labelIcon: undefined,
|
|
16
|
+
maxHeight: 'xs',
|
|
17
|
+
modelValue: undefined,
|
|
18
|
+
openOnHover: false,
|
|
19
|
+
options: () => [],
|
|
20
|
+
padding: true,
|
|
21
|
+
placeholder: '',
|
|
22
|
+
required: false,
|
|
23
|
+
size: 'base',
|
|
24
|
+
validation: undefined,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
'change': [event: MouseEvent, value?: number | string]
|
|
29
|
+
'click': [event: MouseEvent, value?: number | string]
|
|
30
|
+
'optionClick': [event: MouseEvent, value?: number | string]
|
|
31
|
+
'update:modelValue': [value?: number | string]
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const { fadeIn } = useMotion()
|
|
35
|
+
|
|
36
|
+
const opened = ref(false)
|
|
37
|
+
const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
|
|
38
|
+
|
|
39
|
+
const computedColumns = computed(() => {
|
|
40
|
+
if (props.columns.length) {
|
|
41
|
+
return props.columns
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return [{ options: computedOptions.value }]
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const computedOptions = computed(() => {
|
|
48
|
+
const options = []
|
|
49
|
+
|
|
50
|
+
// Add index to each option
|
|
51
|
+
if (props.columns.length) {
|
|
52
|
+
for (const column of props.columns) {
|
|
53
|
+
for (const columnOption of column.options) {
|
|
54
|
+
options.push(columnOption)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const columnOption of props.options) {
|
|
60
|
+
options.push(columnOption)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return options
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const selectedOption = computed(() => {
|
|
68
|
+
return computedOptions.value.find((option) => {
|
|
69
|
+
return option.value === props.modelValue
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function reset() {
|
|
74
|
+
opened.value = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function selectOption(event: MouseEvent, value?: number | string) {
|
|
78
|
+
emit('change', event, value)
|
|
79
|
+
emit('update:modelValue', value)
|
|
80
|
+
|
|
81
|
+
reset()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function onClose() {
|
|
85
|
+
reset()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onContainerClick(event: MouseEvent) {
|
|
89
|
+
if (!props.disabled) {
|
|
90
|
+
opened.value = !opened.value
|
|
91
|
+
|
|
92
|
+
emit('click', event, selectedOption.value?.value)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function onContainerKeypress(event: KeyboardEvent) {
|
|
97
|
+
if (['Enter', 'Space'].includes(event.code)) {
|
|
98
|
+
(event.target as HTMLElement)?.click()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onLabelClick() {
|
|
103
|
+
if (!props.disabled) {
|
|
104
|
+
opened.value = !opened.value
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function onMouseEnter() {
|
|
109
|
+
if (props.openOnHover) {
|
|
110
|
+
opened.value = true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function onMouseLeave() {
|
|
115
|
+
if (props.openOnHover) {
|
|
116
|
+
opened.value = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function onOptionClick(event: MouseEvent, option: FieldSelectOption) {
|
|
121
|
+
// Check that the option is not currently selected
|
|
122
|
+
if ((selectedOption.value || {}).value !== option.value) {
|
|
123
|
+
selectOption(event, option.value)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
else {
|
|
127
|
+
reset()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
emit('optionClick', event, option.value)
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<div class="flex flex-col">
|
|
136
|
+
<FieldLabel
|
|
137
|
+
v-if="label"
|
|
138
|
+
:disabled="disabled"
|
|
139
|
+
:for-field="uuid"
|
|
140
|
+
has-margin-bottom
|
|
141
|
+
:icon="labelIcon"
|
|
142
|
+
:label="label"
|
|
143
|
+
:required="required"
|
|
144
|
+
:size="size"
|
|
145
|
+
@click="onLabelClick"
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
<div
|
|
149
|
+
v-on-click-outside="onClose"
|
|
150
|
+
class="text-left"
|
|
151
|
+
:class="{
|
|
152
|
+
'cursor-not-allowed': disabled,
|
|
153
|
+
'cursor-pointer': !disabled,
|
|
154
|
+
}"
|
|
155
|
+
@mouseenter="onMouseEnter"
|
|
156
|
+
@mouseleave="onMouseLeave"
|
|
157
|
+
>
|
|
158
|
+
<div
|
|
159
|
+
class="relative"
|
|
160
|
+
:class="{ 'shadow-sm': border === 'full' }"
|
|
161
|
+
>
|
|
162
|
+
<div
|
|
163
|
+
class="group flex items-center rounded-lg bg-white outline-none dark:bg-gray-900"
|
|
164
|
+
:class="{
|
|
165
|
+
'border shadow-sm border-gray-200 dark:border-gray-800': border === 'full',
|
|
166
|
+
'border-b border-gray-200 dark:border-gray-800': border === 'bottom',
|
|
167
|
+
'border-0': border === 'none',
|
|
168
|
+
'border-gray-400 dark:border-gray-600': opened,
|
|
169
|
+
'hover:border-gray-300 dark:hover:border-gray-700': !opened,
|
|
170
|
+
'focus:border-gray-400 dark:focus:border-gray-600': !disabled,
|
|
171
|
+
|
|
172
|
+
'text-2xs': size === 'xs',
|
|
173
|
+
'text-xs': size === 'sm',
|
|
174
|
+
'text-sm': size === 'base',
|
|
175
|
+
'text-base': size === 'lg',
|
|
176
|
+
|
|
177
|
+
'px-4': padding,
|
|
178
|
+
|
|
179
|
+
'h-8': size === 'xs',
|
|
180
|
+
'h-10': size === 'sm',
|
|
181
|
+
'h-12': size === 'base',
|
|
182
|
+
'h-14': size === 'lg',
|
|
183
|
+
}"
|
|
184
|
+
tabindex="0"
|
|
185
|
+
@click="onContainerClick"
|
|
186
|
+
@keypress.prevent="onContainerKeypress"
|
|
187
|
+
>
|
|
188
|
+
<template v-if="selectedOption">
|
|
189
|
+
<BaseIcon
|
|
190
|
+
v-if="selectedOption.icon"
|
|
191
|
+
class="pointer-events-none mr-2"
|
|
192
|
+
:icon="selectedOption.icon"
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<span class="flex-1 truncate font-medium uppercase">
|
|
196
|
+
{{ selectedOption.text }}
|
|
197
|
+
</span>
|
|
198
|
+
</template>
|
|
199
|
+
|
|
200
|
+
<span
|
|
201
|
+
v-else-if="placeholder"
|
|
202
|
+
class="flex-1 truncate text-gray-600 font-medium uppercase dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-100"
|
|
203
|
+
>
|
|
204
|
+
{{ placeholder }}
|
|
205
|
+
</span>
|
|
206
|
+
|
|
207
|
+
<BaseIcon
|
|
208
|
+
v-if="caret"
|
|
209
|
+
class="ml-2 flex-initial"
|
|
210
|
+
:class="{ 'rotate-180 transform': opened }"
|
|
211
|
+
color="gray"
|
|
212
|
+
:icon="getIcon('arrowDown')"
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<Motion
|
|
217
|
+
v-if="opened"
|
|
218
|
+
:animate="fadeIn.animate"
|
|
219
|
+
as="div"
|
|
220
|
+
class="absolute left-0 right-0 z-10 mt-0.5 cursor-default"
|
|
221
|
+
:class="{
|
|
222
|
+
'top-0': direction === 'bottom',
|
|
223
|
+
'bottom-0': direction === 'top',
|
|
224
|
+
}"
|
|
225
|
+
:initial="fadeIn.initial"
|
|
226
|
+
>
|
|
227
|
+
<div
|
|
228
|
+
class="options min-w-xs overflow-y-auto border border-gray-400 rounded-lg bg-white text-gray-600 font-medium dark:border-gray-600 dark:bg-gray-900 dark:text-gray-400"
|
|
229
|
+
:class="{
|
|
230
|
+
'md:min-w-xl p-1 pb-0': computedColumns.length >= 2,
|
|
231
|
+
|
|
232
|
+
'mt-8': ['xs', 'sm'].includes(size) && !padding && direction === 'bottom',
|
|
233
|
+
'mt-10': ((['xs', 'sm'].includes(size) && padding) || (size === 'base' && !padding)) && direction === 'bottom',
|
|
234
|
+
'mt-12': size === 'base' && padding && direction === 'bottom',
|
|
235
|
+
'mb-8': ['xs', 'sm'].includes(size) && !padding && direction === 'top',
|
|
236
|
+
'mb-10': ((['xs', 'sm'].includes(size) && padding) || (size === 'base' && !padding)) && direction === 'top',
|
|
237
|
+
'mb-12': size === 'base' && padding && direction === 'top',
|
|
238
|
+
|
|
239
|
+
'max-h-xs': maxHeight === 'xs',
|
|
240
|
+
'max-h-sm': maxHeight === 'sm',
|
|
241
|
+
'max-h-md': maxHeight === 'md',
|
|
242
|
+
'max-h-lg': maxHeight === 'lg',
|
|
243
|
+
}"
|
|
244
|
+
>
|
|
245
|
+
<div
|
|
246
|
+
v-for="(column, columnIndex) in computedColumns"
|
|
247
|
+
:key="columnIndex"
|
|
248
|
+
>
|
|
249
|
+
<BaseIcon
|
|
250
|
+
v-if="column.title"
|
|
251
|
+
class="px-2 py-1 text-gray-900 dark:text-gray-100"
|
|
252
|
+
:text="column.title"
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
<div
|
|
256
|
+
v-for="option in column.options"
|
|
257
|
+
:key="option.value"
|
|
258
|
+
class="group flex cursor-pointer items-center outline-none"
|
|
259
|
+
:class="{
|
|
260
|
+
'border-b border-gray-200 dark:border-gray-800 px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-900 last:border-b-0': computedColumns.length < 2,
|
|
261
|
+
'px-2 py-1 last:mb-2': computedColumns.length >= 2,
|
|
262
|
+
|
|
263
|
+
'font-medium underline': selectedOption && option.value === selectedOption.value,
|
|
264
|
+
'text-gray-900 dark:text-gray-100': selectedOption && option.value === selectedOption.value,
|
|
265
|
+
'bg-white dark:bg-gray-900': selectedOption && option.value !== selectedOption.value,
|
|
266
|
+
|
|
267
|
+
}"
|
|
268
|
+
@click="onOptionClick($event, option)"
|
|
269
|
+
>
|
|
270
|
+
<BaseIcon
|
|
271
|
+
v-if="option.text"
|
|
272
|
+
class="pointer-events-none mr-2 flex-initial"
|
|
273
|
+
:icon="option.icon"
|
|
274
|
+
:size="size"
|
|
275
|
+
:text="option.text"
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</Motion>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<FieldMessage
|
|
285
|
+
:description="description"
|
|
286
|
+
:hide-error="hideError"
|
|
287
|
+
:size="size"
|
|
288
|
+
:validation="validation"
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
</template>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldTabs, FieldTabsAction } from '../../types/fields'
|
|
3
|
+
import { NuxtLinkLocale } from '#components'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<FieldTabs>(), {
|
|
6
|
+
minimizeOnMobile: false,
|
|
7
|
+
modelValue: undefined,
|
|
8
|
+
multiple: false,
|
|
9
|
+
size: 'base',
|
|
10
|
+
tabs: undefined,
|
|
11
|
+
theme: 'rounded',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits<{
|
|
15
|
+
'change': [event: MouseEvent, tabValue: number | string, action: FieldTabsAction, activeTabs: Array<number | string>]
|
|
16
|
+
'click': [event: MouseEvent, tabValue: number | string, activeTabs: Array<number | string>]
|
|
17
|
+
'update:modelValue': [value?: Array<number | string> | number | string]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
function onTabClick(event: MouseEvent, tabValue: number | string) {
|
|
21
|
+
let activeTabs = [tabValue]
|
|
22
|
+
|
|
23
|
+
// When multiple values are not allowed and tab is not already active
|
|
24
|
+
if (!props.multiple && props.modelValue !== tabValue) {
|
|
25
|
+
emit('change', event, tabValue, 'added', activeTabs)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// When multiple values are allowed
|
|
29
|
+
if (props.multiple) {
|
|
30
|
+
// Remove the tab when already active
|
|
31
|
+
if (Array.isArray(props.modelValue) && props.modelValue.includes(tabValue)) {
|
|
32
|
+
activeTabs = props.modelValue.filter(item => item !== tabValue)
|
|
33
|
+
|
|
34
|
+
emit('change', event, tabValue, 'removed', activeTabs)
|
|
35
|
+
}
|
|
36
|
+
// Push the tab when not already active
|
|
37
|
+
else {
|
|
38
|
+
activeTabs = Array.isArray(props.modelValue) ? [...props.modelValue, tabValue] : [tabValue]
|
|
39
|
+
emit('change', event, tabValue, 'added', activeTabs)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
emit('click', event, tabValue, activeTabs)
|
|
44
|
+
emit('update:modelValue', props.multiple ? activeTabs : tabValue)
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div
|
|
50
|
+
class="item-center flex border border-gray-300 shadow-inner dark:border-gray-700"
|
|
51
|
+
:class="{
|
|
52
|
+
'gap-0 border-b border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-900': theme === 'border',
|
|
53
|
+
'gap-2 rounded-lg bg-gray-200 dark:bg-gray-800 p-0.5 sm:p-1': theme === 'rounded',
|
|
54
|
+
}"
|
|
55
|
+
>
|
|
56
|
+
<component
|
|
57
|
+
:is="tab.to ? NuxtLinkLocale : 'div'"
|
|
58
|
+
v-for="tab in tabs"
|
|
59
|
+
:key="tab.value"
|
|
60
|
+
class="flex cursor-pointer items-center justify-center text-xs"
|
|
61
|
+
:class="{
|
|
62
|
+
'text-gray-900 dark:text-gray-100': modelValue === tab.value,
|
|
63
|
+
'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100': modelValue !== tab.value,
|
|
64
|
+
'flex-1': !minimizeOnMobile || modelValue !== tab.value,
|
|
65
|
+
'flex-2 sm:flex-1': minimizeOnMobile && modelValue === tab.value,
|
|
66
|
+
|
|
67
|
+
'py-1.5 px-2.5': size === 'xs' && theme === 'rounded',
|
|
68
|
+
'py-2 px-3': size === 'sm' && theme === 'rounded',
|
|
69
|
+
'py-2.5 px-3.5': size === 'base' && theme === 'rounded' || size === 'xs' && theme === 'border',
|
|
70
|
+
'py-3 px-4': size === 'lg' && theme === 'rounded' || size === 'sm' && theme === 'border',
|
|
71
|
+
'py-3.5 px-4.5': size === 'base' && theme === 'border',
|
|
72
|
+
'py-4 px-5': size === 'lg' && theme === 'border',
|
|
73
|
+
|
|
74
|
+
'border-b-2 border-gray-300 dark:border-gray-700 transition duration-500': theme === 'border',
|
|
75
|
+
'border-gray-900 dark:border-gray-100': modelValue === tab.value && theme === 'border',
|
|
76
|
+
'border-gray-100 dark:border-gray-900': modelValue !== tab.value && theme === 'border',
|
|
77
|
+
|
|
78
|
+
'rounded-lg': theme === 'rounded',
|
|
79
|
+
'bg-white dark:bg-gray-900 shadow': modelValue === tab.value && theme === 'rounded',
|
|
80
|
+
'bg-gray-200 dark:bg-gray-800': modelValue !== tab.value && theme === 'rounded',
|
|
81
|
+
}"
|
|
82
|
+
:to="tab.to"
|
|
83
|
+
@click="onTabClick($event, tab.value)"
|
|
84
|
+
>
|
|
85
|
+
<BaseIcon
|
|
86
|
+
v-if="tab.icon"
|
|
87
|
+
class="mr-1.5 flex-initial"
|
|
88
|
+
:color="modelValue === tab.value ? tab.activeColor : 'black'"
|
|
89
|
+
:icon="tab.icon"
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<span
|
|
93
|
+
class="flex-initial"
|
|
94
|
+
:class="{ 'hidden sm:inline': minimizeOnMobile && modelValue !== tab.value }"
|
|
95
|
+
>
|
|
96
|
+
{{ tab.label }}
|
|
97
|
+
</span>
|
|
98
|
+
</component>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|
|
@@ -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>
|
|
@@ -12,12 +12,16 @@ const icons = {
|
|
|
12
12
|
arrowDown: 'solar:alt-arrow-down-bold',
|
|
13
13
|
arrowRight: 'solar:alt-arrow-right-bold',
|
|
14
14
|
arrowUp: 'solar:alt-arrow-up-bold',
|
|
15
|
+
back: 'hugeicons:arrow-turn-backward',
|
|
15
16
|
checkCircle: 'hugeicons:checkmark-circle-02',
|
|
16
17
|
close: 'hugeicons:cancel-01',
|
|
17
18
|
closeCircle: 'hugeicons:cancel-circle',
|
|
18
19
|
default: 'hugeicons:help-circle',
|
|
20
|
+
drag: 'mdi:drag-horizontal-variant',
|
|
19
21
|
exclamationCircle: 'hugeicons:alert-circle',
|
|
20
22
|
infoCircle: 'hugeicons:information-circle',
|
|
23
|
+
plus: 'hugeicons:add-01',
|
|
24
|
+
tags: 'hugeicons:tags',
|
|
21
25
|
} as const
|
|
22
26
|
|
|
23
27
|
export function getIcon(attribute: keyof typeof icons) {
|
|
@@ -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 ''
|
package/app/types/bases.d.ts
CHANGED
|
@@ -262,6 +262,48 @@ export interface BaseSpinner {
|
|
|
262
262
|
uppercase?: boolean
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
export interface BaseTag {
|
|
266
|
+
active?: boolean
|
|
267
|
+
avatar?: string
|
|
268
|
+
circle?: string
|
|
269
|
+
clickable?: boolean
|
|
270
|
+
color?: BaseColor
|
|
271
|
+
draggable?: boolean
|
|
272
|
+
editable?: boolean
|
|
273
|
+
icon?: BaseIconValue
|
|
274
|
+
iconSize?: number
|
|
275
|
+
id?: number
|
|
276
|
+
isCreation?: boolean
|
|
277
|
+
light?: boolean
|
|
278
|
+
order?: number
|
|
279
|
+
removable?: boolean
|
|
280
|
+
rounded?: boolean
|
|
281
|
+
size?: BaseTagSize
|
|
282
|
+
text: BaseTextText
|
|
283
|
+
to?: RouteLocationNamedI18n
|
|
284
|
+
truncate?: boolean
|
|
285
|
+
uppercase?: boolean
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export type BaseTagSize = 'base' | 'sm'
|
|
289
|
+
|
|
290
|
+
export interface BaseTags {
|
|
291
|
+
active?: boolean
|
|
292
|
+
clickable?: boolean
|
|
293
|
+
draggable?: boolean
|
|
294
|
+
editable?: boolean
|
|
295
|
+
hasBack?: boolean
|
|
296
|
+
hasCreation?: boolean
|
|
297
|
+
maxTags?: number
|
|
298
|
+
removable?: boolean
|
|
299
|
+
selectable?: boolean
|
|
300
|
+
selectableUnique?: boolean
|
|
301
|
+
size?: BaseTagSize
|
|
302
|
+
tags: BaseTag[]
|
|
303
|
+
value?: (number | string)[]
|
|
304
|
+
wrap?: boolean
|
|
305
|
+
}
|
|
306
|
+
|
|
265
307
|
export interface BaseText {
|
|
266
308
|
background?: BaseTextBackground
|
|
267
309
|
bold?: boolean
|